fix: stabilize summary panel transitions (#437)

Issue: #437
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-09-08 20:23:20 +03:00
parent b371b68309
commit f87d3dc545
49 changed files with 423 additions and 419 deletions
+9 -17
View File
@@ -1296,7 +1296,7 @@ export class HouseplanCard extends LitElement {
stageWidth: number, stageHeight: number,
): ModeViewBox {
const vb = this._baseVb();
const fit = fitView(vb, stageWidth / Math.max(1, stageHeight));
const fit = fitView(vb, stageWidth / stageHeight);
const z = Math.min(HouseplanCard.ZOOM_MAX, Math.max(HouseplanCard.ZOOM_MIN, zoom));
const w = fit.w / z, h = fit.h / z;
const cx = centerX ?? fit.x + fit.w / 2;
@@ -1442,19 +1442,11 @@ export class HouseplanCard extends LitElement {
}
return;
}
const chrome = this.renderRoot.querySelector('.editorchrome') as HTMLElement | null;
const inner = chrome?.querySelector('.editorchrome-inner') as HTMLElement | null;
const targetChromeHeight = targetMode === 'view' ? 0 : inner?.scrollHeight || inner?.getBoundingClientRect().height || 0;
const totalHeight = Math.max(1, from.stageHeight + from.editorChromeHeight);
const targetStageHeight = Math.max(1, totalHeight - targetChromeHeight);
const inner = this.renderRoot.querySelector('.editorchrome-inner') as HTMLElement | null;
const targetChromeHeight = targetMode === 'view' ? 0 : inner?.scrollHeight || 0;
// updateComplete guarantees that this stage already belongs to the rendered ha-card.
const targetStageHeight = Math.max(1, (this.panelHost ? this.clientHeight : innerHeight) - measuredCardHeaderHeight(this.renderRoot, this._stageEl!, this.panelHost)!);
const targetStageWidth = this._stageEl?.clientWidth || from.stageWidth;
if (targetStageWidth <= 0 || targetStageHeight <= 0) {
this._modeTransitionPreparing = false;
this._modeTransitionVisual = null;
this._applyView(targetZoom, targetCenterX, targetCenterY);
this.requestUpdate();
return;
}
const targetView = this._viewForModeTarget(
targetZoom, targetCenterX, targetCenterY, targetStageWidth, targetStageHeight,
);
@@ -2006,7 +1998,7 @@ export class HouseplanCard extends LitElement {
* working tool (zoom in to grab a vertex), not the user's intention for
* viewing — leaving any editor brings the view-mode viewport back.
*/
private _viewModeSnap: { space: string; zoom: number; cx?: number; cy?: number } | null = null;
private _viewModeSnap: { space: string; zoom: number; cx?: number; cy?: number; w?: number } | null = null;
/** Session-only View intent. It is deliberately absent from warm/LS/config state. */
private _roomFocus: { spaceId: string; roomId: string } | null = null;
/** Pointer owner captured from the actually painted event path. */
@@ -6418,11 +6410,11 @@ export class HouseplanCard extends LitElement {
*/
private _roomLabelReferenceViewWidth(view: { w: number }): number {
if (!this._markup) return view.w;
const snap = this._viewModeSnap; if (snap?.space === this._space && snap.w) return snap.w * snap.zoom / this._zoom;
const stage = this._stageEl;
const chrome = this.renderRoot.querySelector('.editorchrome') as HTMLElement | null;
if (!stage || stage.clientWidth <= 0 || stage.clientHeight <= 0) return view.w;
const totalHeight = stage.clientHeight + (chrome?.getBoundingClientRect().height || 0);
if (totalHeight <= 0) return view.w;
if (!stage?.clientWidth || !stage.clientHeight) return view.w;
const totalHeight = stage.clientHeight + (chrome?.offsetHeight || 0);
return this._viewForModeTarget(
this._zoom, undefined, undefined, stage.clientWidth, totalHeight,
).w;
+2 -2
View File
@@ -1151,7 +1151,7 @@ export interface HouseplanEditorHostPort {
_vacSrvTrails: Record<string, any>;
_view: { x: number; y: number; w: number; h: number; } | null;
_viewportGestureDirty: boolean;
_viewModeSnap: { space: string; zoom: number; cx?: number; cy?: number; } | null;
_viewModeSnap: { space: string; zoom: number; cx?: number; cy?: number; w?: number; } | null;
_viewOr: (vb: number[]) => { x: number; y: number; w: number; h: number; };
_viewPreference: Record<string, "flat" | "iso">;
_virtualLights: VirtualLightSnapshot;
@@ -1328,7 +1328,7 @@ public _setMode(mode: 'view' | 'plan' | 'devices' | 'decor', animate = true): vo
space: this.host._space,
zoom: this.host._zoom,
cx: v ? this.host._logicalViewCenter(previousProjection)?.x : undefined,
cy: v ? this.host._logicalViewCenter(previousProjection)?.y : undefined,
cy: v ? this.host._logicalViewCenter(previousProjection)?.y : undefined, w: previousProjection === 'flat' ? v?.w : undefined,
};
if (previousProjection === 'iso') {
const logical = this.host._logicalViewCenter('iso');
+10 -2
View File
@@ -78,6 +78,7 @@ export class LoadedSummaryPanelRuntime {
private editorLoad: Promise<SummaryPanelEditorRenderer> | null = null;
private metricsModule: typeof import('./summary-panel-metrics') | null = null;
private metricsLoad: Promise<typeof import('./summary-panel-metrics')> | null = null;
private styleSheet: CSSStyleSheet | null = null;
public constructor(host: unknown) { this.host = host as SummaryPanelHost; }
@@ -227,10 +228,17 @@ export class LoadedSummaryPanelRuntime {
private ensureStyle(): void {
const root = this.host.renderRoot as ShadowRoot;
if (this.styleSheet && root.adoptedStyleSheets.includes(this.styleSheet)) return;
const Sheet = this.host.ownerDocument.defaultView?.CSSStyleSheet;
if (Sheet && 'adoptedStyleSheets' in root) {
const sheet = new Sheet(); sheet.replaceSync(summaryPanelCss);
this.styleSheet = sheet;
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
return;
}
if (root.querySelector('style[data-hp-summary]')) return;
const style = this.host.ownerDocument.createElement('style');
style.dataset.hpSummary = 'true';
style.textContent = summaryPanelCss;
style.dataset.hpSummary = 'true'; style.textContent = summaryPanelCss;
root.insertBefore(style, root.firstChild);
}