Archived
fix(ui): guard resize observers against missing elements
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@jmfederico/pi-web": patch
|
||||
---
|
||||
|
||||
Prevent resize observers from attaching to missing UI elements during panel rerenders.
|
||||
@@ -48,8 +48,8 @@ export class PiWebApp extends LitElement {
|
||||
@state() private state: AppState = initialAppState();
|
||||
@query("chat-view") private chatView?: ChatView;
|
||||
@query("prompt-editor") private promptEditor?: PromptEditor;
|
||||
@query(".context-items") private contextItems?: HTMLElement;
|
||||
@query(".mobile-tabs") private mobileTabs?: HTMLElement;
|
||||
@query(".context-items") private contextItems?: HTMLElement | null;
|
||||
@query(".mobile-tabs") private mobileTabs?: HTMLElement | null;
|
||||
|
||||
private readonly sessions = new SessionController(
|
||||
() => this.state,
|
||||
@@ -696,7 +696,7 @@ export class PiWebApp extends LitElement {
|
||||
}
|
||||
|
||||
private observeContextItems(): void {
|
||||
const contextItems = this.contextItems;
|
||||
const contextItems = this.contextItemsElement();
|
||||
if (this.observedContextItems === contextItems) return;
|
||||
this.contextItemsResizeObserver?.disconnect();
|
||||
this.observedContextItems = contextItems;
|
||||
@@ -709,7 +709,7 @@ export class PiWebApp extends LitElement {
|
||||
}
|
||||
|
||||
private updateContextScrollState(): void {
|
||||
const contextItems = this.contextItems;
|
||||
const contextItems = this.contextItemsElement();
|
||||
const maxScrollLeft = contextItems === undefined ? 0 : Math.max(0, contextItems.scrollWidth - contextItems.clientWidth);
|
||||
const canScrollLeft = contextItems !== undefined && contextItems.scrollLeft > 1;
|
||||
const canScrollRight = contextItems !== undefined && maxScrollLeft - contextItems.scrollLeft > 1;
|
||||
@@ -717,8 +717,13 @@ export class PiWebApp extends LitElement {
|
||||
if (this.contextCanScrollRight !== canScrollRight) this.contextCanScrollRight = canScrollRight;
|
||||
}
|
||||
|
||||
private contextItemsElement(): HTMLElement | undefined {
|
||||
const contextItems = this.contextItems;
|
||||
return contextItems instanceof HTMLElement ? contextItems : undefined;
|
||||
}
|
||||
|
||||
private observeMobileTabs(): void {
|
||||
const mobileTabs = this.mobileTabs;
|
||||
const mobileTabs = this.mobileTabsElement();
|
||||
if (this.observedMobileTabs === mobileTabs) return;
|
||||
this.mobileTabsResizeObserver?.disconnect();
|
||||
this.observedMobileTabs = mobileTabs;
|
||||
@@ -731,7 +736,7 @@ export class PiWebApp extends LitElement {
|
||||
}
|
||||
|
||||
private updateMobileTabsScrollState(): void {
|
||||
const mobileTabs = this.mobileTabs;
|
||||
const mobileTabs = this.mobileTabsElement();
|
||||
const maxScrollLeft = mobileTabs === undefined ? 0 : Math.max(0, mobileTabs.scrollWidth - mobileTabs.clientWidth);
|
||||
const canScrollLeft = mobileTabs !== undefined && mobileTabs.scrollLeft > 1;
|
||||
const canScrollRight = mobileTabs !== undefined && maxScrollLeft - mobileTabs.scrollLeft > 1;
|
||||
@@ -739,6 +744,11 @@ export class PiWebApp extends LitElement {
|
||||
if (this.mobileTabsCanScrollRight !== canScrollRight) this.mobileTabsCanScrollRight = canScrollRight;
|
||||
}
|
||||
|
||||
private mobileTabsElement(): HTMLElement | undefined {
|
||||
const mobileTabs = this.mobileTabs;
|
||||
return mobileTabs instanceof HTMLElement ? mobileTabs : undefined;
|
||||
}
|
||||
|
||||
override render() {
|
||||
const state = this.state;
|
||||
return html`
|
||||
|
||||
@@ -18,7 +18,7 @@ const DEFAULT_TERMINAL_SIZE: TerminalSize = { cols: 100, rows: 30 };
|
||||
export class TerminalPanel extends LitElement {
|
||||
@property({ attribute: false }) workspace: Workspace | undefined;
|
||||
@property({ type: Boolean }) autoStart = false;
|
||||
@query(".terminal-host") private terminalHost?: HTMLDivElement;
|
||||
@query(".terminal-host") private terminalHost?: HTMLDivElement | null;
|
||||
@state() private terminals: TerminalInfo[] = [];
|
||||
@state() private selectedId: string | undefined;
|
||||
@state() private loading = false;
|
||||
@@ -133,15 +133,16 @@ export class TerminalPanel extends LitElement {
|
||||
|
||||
private ensureTerminalView(): void {
|
||||
const workspace = this.workspace;
|
||||
if (!this.visible || this.terminal !== undefined || this.selectedId === undefined || this.terminalHost === undefined || workspace === undefined) return;
|
||||
const terminalHost = this.terminalHostElement();
|
||||
if (!this.visible || this.terminal !== undefined || this.selectedId === undefined || terminalHost === undefined || workspace === undefined) return;
|
||||
const terminal = new Terminal(terminalOptions(this));
|
||||
const fitAddon = new FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
terminal.open(this.terminalHost);
|
||||
terminal.open(terminalHost);
|
||||
this.terminal = terminal;
|
||||
this.fitAddon = fitAddon;
|
||||
this.resizeObserver = new ResizeObserver(() => { this.fitAndNotify(); });
|
||||
this.resizeObserver.observe(this.terminalHost);
|
||||
this.resizeObserver.observe(terminalHost);
|
||||
terminal.onData((data) => {
|
||||
if (this.suppressTerminalInput) return;
|
||||
const filtered = filterTerminalInput(data);
|
||||
@@ -211,17 +212,23 @@ export class TerminalPanel extends LitElement {
|
||||
private measureTerminalSize(): TerminalSize | undefined {
|
||||
const currentSize = this.fitTerminal();
|
||||
if (currentSize !== undefined) return currentSize;
|
||||
if (this.terminal !== undefined || this.terminalHost === undefined) return undefined;
|
||||
const terminalHost = this.terminalHostElement();
|
||||
if (this.terminal !== undefined || terminalHost === undefined) return undefined;
|
||||
|
||||
const measuringTerminal = new Terminal(terminalOptions(this));
|
||||
const measuringFitAddon = new FitAddon();
|
||||
measuringTerminal.loadAddon(measuringFitAddon);
|
||||
measuringTerminal.open(this.terminalHost);
|
||||
measuringTerminal.open(terminalHost);
|
||||
const size = terminalSizeFromDimensions(measuringFitAddon.proposeDimensions());
|
||||
measuringTerminal.dispose();
|
||||
return size;
|
||||
}
|
||||
|
||||
private terminalHostElement(): HTMLDivElement | undefined {
|
||||
const terminalHost = this.terminalHost;
|
||||
return terminalHost instanceof HTMLDivElement ? terminalHost : undefined;
|
||||
}
|
||||
|
||||
private applyTerminalTheme(): void {
|
||||
if (this.terminal !== undefined) this.terminal.options.theme = terminalTheme(this);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export class WorkspacePanel extends LitElement {
|
||||
@property({ attribute: false }) onSelectDiff: (path: string) => void = () => undefined;
|
||||
@property({ type: Number }) activeTerminalCount = 0;
|
||||
@property({ type: Boolean }) terminalAutoStart = false;
|
||||
@query(".workspace-header-strip") private workspaceHeaderStrip?: HTMLElement;
|
||||
@query(".workspace-header-strip") private workspaceHeaderStrip?: HTMLElement | null;
|
||||
@state() private workspaceHeaderCanScrollLeft = false;
|
||||
@state() private workspaceHeaderCanScrollRight = false;
|
||||
|
||||
@@ -99,7 +99,7 @@ export class WorkspacePanel extends LitElement {
|
||||
}
|
||||
|
||||
private observeWorkspaceHeaderStrip(): void {
|
||||
const strip = this.workspaceHeaderStrip;
|
||||
const strip = this.workspaceHeaderStripElement();
|
||||
if (this.observedWorkspaceHeaderStrip === strip) return;
|
||||
this.workspaceHeaderResizeObserver?.disconnect();
|
||||
this.observedWorkspaceHeaderStrip = strip;
|
||||
@@ -112,7 +112,7 @@ export class WorkspacePanel extends LitElement {
|
||||
}
|
||||
|
||||
private updateWorkspaceHeaderScrollState(): void {
|
||||
const strip = this.workspaceHeaderStrip;
|
||||
const strip = this.workspaceHeaderStripElement();
|
||||
const maxScrollLeft = strip === undefined ? 0 : Math.max(0, strip.scrollWidth - strip.clientWidth);
|
||||
const canScrollLeft = strip !== undefined && strip.scrollLeft > 1;
|
||||
const canScrollRight = strip !== undefined && maxScrollLeft - strip.scrollLeft > 1;
|
||||
@@ -120,6 +120,11 @@ export class WorkspacePanel extends LitElement {
|
||||
if (this.workspaceHeaderCanScrollRight !== canScrollRight) this.workspaceHeaderCanScrollRight = canScrollRight;
|
||||
}
|
||||
|
||||
private workspaceHeaderStripElement(): HTMLElement | undefined {
|
||||
const strip = this.workspaceHeaderStrip;
|
||||
return strip instanceof HTMLElement ? strip : undefined;
|
||||
}
|
||||
|
||||
private createPanelContext(workspace: Workspace): WorkspacePanelContext {
|
||||
return {
|
||||
workspace,
|
||||
|
||||
Reference in New Issue
Block a user