mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
feat: отполировать главную панель редакторов (#660)
Issue: #660 User-Visible: yes
This commit is contained in:
committed by
claude[bot]
parent
cff83e43ec
commit
d3c88f06f9
@@ -15,8 +15,8 @@ const res = await page.evaluate(async () => {
|
|||||||
// 1) две вкладки, Просмотра нет, крестиков в неактивных нет
|
// 1) две вкладки, Просмотра нет, крестиков в неактивных нет
|
||||||
out.twoTabs = tabs().length === 3; // третья — Редактор подложки (v1.33.0)
|
out.twoTabs = tabs().length === 3; // третья — Редактор подложки (v1.33.0)
|
||||||
out.labels = tabs().map((t) => t.textContent.trim());
|
out.labels = tabs().map((t) => t.textContent.trim());
|
||||||
// #647: X no longer lives inside a mode tab; its own slot after the tabs is
|
// #660: X does not live inside a mode button; its slot follows the active
|
||||||
// empty (but keeps its size) outside an editor.
|
// button and moves to the group end (empty but same size) in View.
|
||||||
out.noCrossIdle = sr().querySelectorAll('.modetab .closex').length === 0
|
out.noCrossIdle = sr().querySelectorAll('.modetab .closex').length === 0
|
||||||
&& !sr().querySelector('.editor-close-slot .closex');
|
&& !sr().querySelector('.editor-close-slot .closex');
|
||||||
out.startView = c._mode === 'view';
|
out.startView = c._mode === 'view';
|
||||||
@@ -51,6 +51,14 @@ const res = await page.evaluate(async () => {
|
|||||||
&& Math.abs((barCloseIconRect.top + barCloseIconRect.height / 2)
|
&& Math.abs((barCloseIconRect.top + barCloseIconRect.height / 2)
|
||||||
- (barCloseRect.top + barCloseRect.height / 2)) <= 1;
|
- (barCloseRect.top + barCloseRect.height / 2)) <= 1;
|
||||||
out.tabCross = !!headerCross() && !tabs()[0].querySelector('.closex');
|
out.tabCross = !!headerCross() && !tabs()[0].querySelector('.closex');
|
||||||
|
const planSummary = sr().querySelector('.summary-control');
|
||||||
|
const summaryToggle = planSummary?.querySelector('button:last-child');
|
||||||
|
const summaryBefore = summaryToggle?.getAttribute('aria-pressed');
|
||||||
|
summaryToggle?.click(); await c.updateComplete;
|
||||||
|
out.summaryControlsStayInPlan = planSummary?.querySelectorAll('button').length === 2
|
||||||
|
&& summaryToggle?.getAttribute('aria-pressed') !== summaryBefore
|
||||||
|
&& !sr().querySelector('.summary-overlay');
|
||||||
|
summaryToggle?.click(); await c.updateComplete;
|
||||||
// 3) повторный клик по активной вкладке — ничего
|
// 3) повторный клик по активной вкладке — ничего
|
||||||
tabs()[0].click(); await c.updateComplete;
|
tabs()[0].click(); await c.updateComplete;
|
||||||
out.reclickNoop = c._mode === 'plan';
|
out.reclickNoop = c._mode === 'plan';
|
||||||
@@ -198,8 +206,8 @@ const res = await page.evaluate(async () => {
|
|||||||
out.barCloseWorks = c._mode === 'view'
|
out.barCloseWorks = c._mode === 'view'
|
||||||
&& getComputedStyle(chrome).visibility === 'hidden'
|
&& getComputedStyle(chrome).visibility === 'hidden'
|
||||||
&& chrome.getBoundingClientRect().height < 1;
|
&& chrome.getBoundingClientRect().height < 1;
|
||||||
// 7) #647 (+#195): the header X sits in a fixed 24 x 24 slot after the mode
|
// 7) #660 (+#195): the header X sits in a fixed 24 x 24 slot after the active
|
||||||
// tabs; the slot is its hit target around a 13 px glyph and has the same size
|
// mode; the slot is its hit target around a 13 px glyph and has the same size
|
||||||
// outside an editor, so the header never changes width. Exercise the slot's
|
// outside an editor, so the header never changes width. Exercise the slot's
|
||||||
// edge rather than calling the element's centre programmatically.
|
// edge rather than calling the element's centre programmatically.
|
||||||
const tabCrossChecks = [];
|
const tabCrossChecks = [];
|
||||||
@@ -210,6 +218,8 @@ const res = await page.evaluate(async () => {
|
|||||||
const slot = sr().querySelector('.editor-close-slot').getBoundingClientRect();
|
const slot = sr().querySelector('.editor-close-slot').getBoundingClientRect();
|
||||||
const glyph = cross.querySelector('ha-icon');
|
const glyph = cross.querySelector('ha-icon');
|
||||||
const hit = sr().elementFromPoint(rect.left + 1, rect.top + rect.height / 2);
|
const hit = sr().elementFromPoint(rect.left + 1, rect.top + rect.height / 2);
|
||||||
|
const editorSummaryVisible = sr().querySelectorAll('.summary-control button').length === 2
|
||||||
|
&& !sr().querySelector('.summary-overlay');
|
||||||
const glyphStays13 = getComputedStyle(glyph).getPropertyValue('--mdc-icon-size').trim() === '13px';
|
const glyphStays13 = getComputedStyle(glyph).getPropertyValue('--mdc-icon-size').trim() === '13px';
|
||||||
hit?.dispatchEvent(new MouseEvent('click', {
|
hit?.dispatchEvent(new MouseEvent('click', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
@@ -226,6 +236,7 @@ const res = await page.evaluate(async () => {
|
|||||||
footprint: Math.abs(slot.width - idleSlot.width) <= 0.5
|
footprint: Math.abs(slot.width - idleSlot.width) <= 0.5
|
||||||
&& Math.abs(slot.height - idleSlot.height) <= 0.5 && Math.abs(slot.width - 24) <= 0.5,
|
&& Math.abs(slot.height - idleSlot.height) <= 0.5 && Math.abs(slot.width - 24) <= 0.5,
|
||||||
edgeHit: !!hit && (hit === cross || cross.contains(hit)),
|
edgeHit: !!hit && (hit === cross || cross.contains(hit)),
|
||||||
|
summary: editorSummaryVisible,
|
||||||
closed: c._mode === 'view',
|
closed: c._mode === 'view',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -234,6 +245,7 @@ const res = await page.evaluate(async () => {
|
|||||||
out.tabCrossKeepsLayoutFootprint = tabCrossChecks.every((check) => check.footprint);
|
out.tabCrossKeepsLayoutFootprint = tabCrossChecks.every((check) => check.footprint);
|
||||||
out.tabCrossExpandedEdgeWorks = tabCrossChecks.every((check) => check.edgeHit && check.closed);
|
out.tabCrossExpandedEdgeWorks = tabCrossChecks.every((check) => check.edgeHit && check.closed);
|
||||||
out.tabCrossWorks = tabCrossChecks.every((check) => check.closed);
|
out.tabCrossWorks = tabCrossChecks.every((check) => check.closed);
|
||||||
|
out.summaryControlsStayInEveryEditor = tabCrossChecks.every((check) => check.summary);
|
||||||
|
|
||||||
// Closing the editor only drops session state: every accepted segment was
|
// Closing the editor only drops session state: every accepted segment was
|
||||||
// already persisted as an ordinary partition by its own click.
|
// already persisted as an ordinary partition by its own click.
|
||||||
@@ -358,6 +370,24 @@ const res = await page.evaluate(async () => {
|
|||||||
c._roomDialog = savedRoomDialog;
|
c._roomDialog = savedRoomDialog;
|
||||||
c._nameSel = savedNameSel;
|
c._nameSel = savedNameSel;
|
||||||
c._areaSel = savedAreaSel;
|
c._areaSel = savedAreaSel;
|
||||||
|
|
||||||
|
// #660: an internal Plan/Background action consumes the first Escape; the
|
||||||
|
// next neutral Escape leaves the editor. Device editor exits immediately.
|
||||||
|
c._setMode('plan', false); await settleMode();
|
||||||
|
c._tool = 'opening'; c.requestUpdate(); await c.updateComplete;
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await c.updateComplete;
|
||||||
|
out.planEscapeKeepsInternalPriority = c._mode === 'plan' && c._tool === 'draw';
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await settleMode();
|
||||||
|
out.planNeutralEscapeExits = c._mode === 'view';
|
||||||
|
c._setMode('devices', false); await settleMode();
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await settleMode();
|
||||||
|
out.deviceNeutralEscapeExits = c._mode === 'view';
|
||||||
|
c._setMode('decor', false); await settleMode();
|
||||||
|
c._decorTool = 'line'; c.requestUpdate(); await c.updateComplete;
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await c.updateComplete;
|
||||||
|
out.decorEscapeKeepsInternalPriority = c._mode === 'decor' && c._decorTool === 'select';
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await settleMode();
|
||||||
|
out.decorNeutralEscapeExits = c._mode === 'view';
|
||||||
return out;
|
return out;
|
||||||
});
|
});
|
||||||
// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1)
|
// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1)
|
||||||
|
|||||||
@@ -28,9 +28,12 @@ const header = () => page.evaluate(() => {
|
|||||||
title: shown(head.querySelector(':scope > .title')),
|
title: shown(head.querySelector(':scope > .title')),
|
||||||
tabs: shown(head.querySelector(':scope > .tabs')),
|
tabs: shown(head.querySelector(':scope > .tabs')),
|
||||||
zoom: shown(head.querySelector(':scope > .zoomctl')),
|
zoom: shown(head.querySelector(':scope > .zoomctl')),
|
||||||
|
modeSlot: shown(head.querySelector(':scope > .modes'))
|
||||||
|
&& Math.abs(head.querySelector(':scope > .modes').getBoundingClientRect().width - 24) <= 0.5,
|
||||||
|
cross: shown(head.querySelector('[data-hp="editor-close"]')),
|
||||||
gear: shown(gear),
|
gear: shown(gear),
|
||||||
gear44: !!g && g.width >= 44 && g.height >= 44,
|
gear44: !!g && g.width >= 44 && g.height >= 44,
|
||||||
hiddenInline: ['.modes', '.header-action', '.summary-control', '.tabedit', '.tabadd']
|
hiddenInline: ['.modes .modetab', '.header-action', '.summary-control', '.tabedit', '.tabadd']
|
||||||
.filter((selector) => [...head.querySelectorAll(selector)].some(shown)),
|
.filter((selector) => [...head.querySelectorAll(selector)].some(shown)),
|
||||||
tabEditInDom: !!head.querySelector('.tabedit, .tabadd'),
|
tabEditInDom: !!head.querySelector('.tabedit, .tabadd'),
|
||||||
inlineVisible: ['.header-action', '.summary-control'].filter((selector) => [...head.querySelectorAll(selector)].some(shown)),
|
inlineVisible: ['.header-action', '.summary-control'].filter((selector) => [...head.querySelectorAll(selector)].some(shown)),
|
||||||
@@ -85,7 +88,7 @@ for (const width of [390, 320]) {
|
|||||||
await size(width);
|
await size(width);
|
||||||
const h = await header();
|
const h = await header();
|
||||||
out[`admin${width}_oneRow56`] = h.oneRow && h.height <= 56;
|
out[`admin${width}_oneRow56`] = h.oneRow && h.height <= 56;
|
||||||
out[`admin${width}_rowContent`] = !h.title && h.tabs && h.zoom && h.gear && h.gear44;
|
out[`admin${width}_rowContent`] = !h.title && h.tabs && h.modeSlot && h.zoom && h.gear && h.gear44;
|
||||||
out[`admin${width}_inlineHidden`] = h.hiddenInline.length === 0 ? true : h.hiddenInline.join(',');
|
out[`admin${width}_inlineHidden`] = h.hiddenInline.length === 0 ? true : h.hiddenInline.join(',');
|
||||||
await openMenu();
|
await openMenu();
|
||||||
const m = await menuState();
|
const m = await menuState();
|
||||||
@@ -135,7 +138,8 @@ await clickAt('[data-hp="header-menu-item"][data-id="mode-plan"]');
|
|||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
out.editorFromMenu = (await mode()) === 'plan';
|
out.editorFromMenu = (await mode()) === 'plan';
|
||||||
const inEditor = await header();
|
const inEditor = await header();
|
||||||
out.editorOneRow56 = inEditor.oneRow && inEditor.height <= 56 && inEditor.hiddenInline.length === 0;
|
out.editorOneRow56 = inEditor.oneRow && inEditor.height <= 56 && inEditor.modeSlot
|
||||||
|
&& inEditor.cross && inEditor.hiddenInline.length === 0;
|
||||||
await openMenu();
|
await openMenu();
|
||||||
const editorMenu = await menuState();
|
const editorMenu = await menuState();
|
||||||
out.editorMenuMarksCurrent = editorMenu.current.join() === 'mode-plan' && !editorMenu.ids.includes('summary-toggle');
|
out.editorMenuMarksCurrent = editorMenu.current.join() === 'mode-plan' && !editorMenu.ids.includes('summary-toggle');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// #647: основная панель — нет счётчика устройств, крестик редактора в своём
|
// #647/#660: основная панель — нет счётчика устройств, крестик редактора в
|
||||||
// слоте, ширина шапки и положение кнопок режимов не меняются при входе,
|
// постоянном слоте внутри группы режимов, ширина шапки и группы не меняются,
|
||||||
// выходе и переключении редакторов; кликабельная зона × не меньше 24 × 24
|
// выходе и переключении редакторов; кликабельная зона × не меньше 24 × 24
|
||||||
// (#195); × виден на обычных ширинах (721–1100 px — там раньше скрывался
|
// (#195); × виден на обычных ширинах (721–1100 px — там раньше скрывался
|
||||||
// счётчик). Режимы переключаются настоящими кнопками через фасад #629;
|
// счётчик). Режимы переключаются настоящими кнопками через фасад #629;
|
||||||
@@ -8,7 +8,7 @@ import { launch, check, finish } from './serve.mjs';
|
|||||||
|
|
||||||
const { page, browser } = await launch({ width: 1400, height: 820 }, 1);
|
const { page, browser } = await launch({ width: 1400, height: 820 }, 1);
|
||||||
|
|
||||||
const WIDTHS = [1400, 1000, 768, 390];
|
const WIDTHS = [1400, 1200, 1000, 768, 620, 481, 390];
|
||||||
const out = {};
|
const out = {};
|
||||||
|
|
||||||
for (const width of WIDTHS) {
|
for (const width of WIDTHS) {
|
||||||
@@ -31,13 +31,22 @@ for (const width of WIDTHS) {
|
|||||||
// Позиции — относительно левого края шапки: прокрутка страницы их не меняет.
|
// Позиции — относительно левого края шапки: прокрутка страницы их не меняет.
|
||||||
const measure = () => {
|
const measure = () => {
|
||||||
const origin = head().getBoundingClientRect().left;
|
const origin = head().getBoundingClientRect().left;
|
||||||
let nextEl = slot()?.nextElementSibling;
|
const modes = sr().querySelector('.modes');
|
||||||
while (nextEl && !nextEl.getClientRects().length) nextEl = nextEl.nextElementSibling;
|
const zoom = sr().querySelector('.zoomctl');
|
||||||
|
const modesBox = modes?.getBoundingClientRect();
|
||||||
|
const zoomBox = zoom?.getBoundingClientRect();
|
||||||
|
const active = modes?.querySelector('.modetab.active');
|
||||||
return {
|
return {
|
||||||
head: head().getBoundingClientRect().width,
|
head: head().getBoundingClientRect().width,
|
||||||
tabs: tabs().map((t) => t.getBoundingClientRect().left - origin),
|
modesLeft: modesBox ? modesBox.left - origin : null,
|
||||||
|
modesWidth: modesBox?.width ?? null,
|
||||||
|
zoomLeft: zoomBox ? zoomBox.left - origin : null,
|
||||||
|
gap: modesBox && zoomBox ? zoomBox.left - modesBox.right : null,
|
||||||
slot: slot()?.getBoundingClientRect(),
|
slot: slot()?.getBoundingClientRect(),
|
||||||
next: nextEl ? nextEl.getBoundingClientRect().left - origin : null,
|
slotInside: slot()?.parentElement === modes,
|
||||||
|
slotOrder: c._mode === 'view'
|
||||||
|
? slot() === modes?.lastElementChild
|
||||||
|
: slot()?.previousElementSibling === active,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const visibleTabs = tabs().filter((t) => t.getBoundingClientRect().width > 0).length === 3
|
const visibleTabs = tabs().filter((t) => t.getBoundingClientRect().width > 0).length === 3
|
||||||
@@ -56,8 +65,9 @@ for (const width of WIDTHS) {
|
|||||||
o.idleSlotKeepsSize = !!idle.slot && Math.abs(idle.slot.width - 24) <= 0.5 && Math.abs(idle.slot.height - 24) <= 0.5;
|
o.idleSlotKeepsSize = !!idle.slot && Math.abs(idle.slot.width - 24) <= 0.5 && Math.abs(idle.slot.height - 24) <= 0.5;
|
||||||
|
|
||||||
const same = (a, b) => Math.abs(a.head - b.head) <= 1
|
const same = (a, b) => Math.abs(a.head - b.head) <= 1
|
||||||
&& a.tabs.every((left, i) => Math.abs(left - b.tabs[i]) <= 1)
|
&& Math.abs(a.modesLeft - b.modesLeft) <= 1
|
||||||
&& (a.next === null || Math.abs(a.next - b.next) <= 1);
|
&& Math.abs(a.modesWidth - b.modesWidth) <= 1
|
||||||
|
&& Math.abs(a.zoomLeft - b.zoomLeft) <= 1;
|
||||||
const steps = [];
|
const steps = [];
|
||||||
const hits = [];
|
const hits = [];
|
||||||
// View → каждый редактор → View, и прямые переключения между редакторами.
|
// View → каждый редактор → View, и прямые переключения между редакторами.
|
||||||
@@ -66,7 +76,9 @@ for (const width of WIDTHS) {
|
|||||||
await hp.setMode(next);
|
await hp.setMode(next);
|
||||||
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
||||||
const now = measure();
|
const now = measure();
|
||||||
steps.push({ next, stable: same(idle, now) });
|
const expectedGap = width <= 620 ? 40.5 : 50;
|
||||||
|
steps.push({ next, stable: same(idle, now), inside: now.slotInside,
|
||||||
|
order: now.slotOrder, gap: phone || Math.abs(now.gap - expectedGap) <= 1 });
|
||||||
if (next !== 'view' && cross()) {
|
if (next !== 'view' && cross()) {
|
||||||
// На 390 px карточка демо-стенда шире окна (так и на dev) — докрутить × в видимую область.
|
// На 390 px карточка демо-стенда шире окна (так и на dev) — докрутить × в видимую область.
|
||||||
cross().scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
cross().scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||||
@@ -81,8 +93,12 @@ for (const width of WIDTHS) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// AC2/AC3: ширина шапки и левые границы кнопок режимов и контрола справа стабильны.
|
// AC2/AC3: ширина шапки, группы режимов и контрола справа стабильны.
|
||||||
o.widthAndTabsStable = steps.every((s) => s.stable);
|
o.widthAndTabsStable = steps.every((s) => s.stable);
|
||||||
|
o.closeSlotLivesInsideModes = idle.slotInside && idle.slotOrder
|
||||||
|
&& steps.every((s) => s.inside && s.order);
|
||||||
|
o.modeToZoomGapMatchesSpec = (phone || Math.abs(idle.gap - (width <= 620 ? 40.5 : 50)) <= 1)
|
||||||
|
&& steps.every((s) => s.gap);
|
||||||
// AC5/AC6: × виден, ≥ 24 × 24, глиф 13 px, центр слота — это ×, кнопки режимов без ×.
|
// AC5/AC6: × виден, ≥ 24 × 24, глиф 13 px, центр слота — это ×, кнопки режимов без ×.
|
||||||
o.crossVisibleAndSized = hits.length === 7 && hits.every((h) => h.visible && h.glyph13);
|
o.crossVisibleAndSized = hits.length === 7 && hits.every((h) => h.visible && h.glyph13);
|
||||||
o.crossHitTarget = hits.every((h) => h.centreHitsCross);
|
o.crossHitTarget = hits.every((h) => h.centreHitsCross);
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- The main toolbar is steadier and more compact: its editor close **×** now
|
||||||
|
occupies the reserved place immediately after the active editor button, the
|
||||||
|
gap before zoom is halved, and the header width stays fixed while switching
|
||||||
|
modes. On desktop the summary-panel settings and show/hide buttons remain
|
||||||
|
available in every editor (the panel itself still appears only in View),
|
||||||
|
while the phone menu remains View-only. Escape now leaves any editor after
|
||||||
|
first dismissing its active tool, selection, drag, popover or dialog
|
||||||
|
([#660](https://github.com/Matysh/houseplan-card/issues/660)).
|
||||||
- A cold dashboard load with 2.5D enabled no longer briefly shows the flat plan
|
- A cold dashboard load with 2.5D enabled no longer briefly shows the flat plan
|
||||||
or classifies every floor as dark on its first frame. The neutral House Plan
|
or classifies every floor as dark on its first frame. The neutral House Plan
|
||||||
loading surface now stays in place until the 2.5D renderer and the actual plan
|
loading surface now stays in place until the 2.5D renderer and the actual plan
|
||||||
|
|||||||
@@ -8,6 +8,15 @@
|
|||||||
|
|
||||||
## Не выпущено
|
## Не выпущено
|
||||||
|
|
||||||
|
- Основная панель стала компактнее и перестала дёргаться: крестик **×**
|
||||||
|
редактора теперь занимает зарезервированное место сразу после активной
|
||||||
|
кнопки редактора, расстояние до масштаба уменьшено вдвое, а общая ширина не
|
||||||
|
меняется при переключении режимов. На широком экране настройки и показ
|
||||||
|
сводной панели доступны во всех редакторах (сама панель появляется только в
|
||||||
|
Просмотре), а меню телефона остаётся прежним и показывает их только в
|
||||||
|
Просмотре. Escape теперь выходит из любого редактора, предварительно закрыв
|
||||||
|
активный инструмент, выбор, перетаскивание, меню или диалог
|
||||||
|
([#660](https://github.com/Matysh/houseplan-card/issues/660)).
|
||||||
- При холодной загрузке дашборда включённый объёмный план (2.5D) больше не
|
- При холодной загрузке дашборда включённый объёмный план (2.5D) больше не
|
||||||
успевает показать обычный плоский вид и не считает все полы тёмными на первом
|
успевает показать обычный плоский вид и не считает все полы тёмными на первом
|
||||||
кадре. Нейтральная заставка House Plan остаётся до готовности 2.5D-рендера и
|
кадре. Нейтральная заставка House Plan остаётся до готовности 2.5D-рендера и
|
||||||
|
|||||||
+7
-2
@@ -240,6 +240,8 @@ product surfaces after setup.
|
|||||||
## 5. Interface modes
|
## 5. Interface modes
|
||||||
|
|
||||||
View is the state with no editor open. Close the active editor to return to it.
|
View is the state with no editor open. Close the active editor to return to it.
|
||||||
|
Escape first cancels the current dialog, popover, drag, selection or tool; press
|
||||||
|
it again after the editor is neutral to return to View.
|
||||||
|
|
||||||
| Mode | Devices | Geometry | Background/openings |
|
| Mode | Devices | Geometry | Background/openings |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@@ -298,8 +300,11 @@ bottom otherwise. It temporarily hides when the card cannot fit a readable
|
|||||||
panel, and restores itself after the card grows. Turning off **Display on
|
panel, and restores itself after the card grows. Turning off **Display on
|
||||||
mobile devices** also hides it whenever Home Assistant reports a narrow view;
|
mobile devices** also hides it whenever Home Assistant reports a narrow view;
|
||||||
the show/hide button remains pressed because the local choice was not erased.
|
the show/hide button remains pressed because the local choice was not erased.
|
||||||
The panel and its controls are absent from all three editors and from the
|
The panel itself is absent from all three editors and from the static space
|
||||||
static space card.
|
card. On a screen wider than 480 px, its two header buttons stay available in
|
||||||
|
the editors: you can open settings or change the local show/hide choice there,
|
||||||
|
but the panel appears only after you return to View. On a phone those two gear
|
||||||
|
menu items remain available only in View.
|
||||||
|
|
||||||
The room highlight remains available in View and kiosk. To keep that highlight
|
The room highlight remains available in View and kiosk. To keep that highlight
|
||||||
but hide the floating room summary, turn off **General settings → Show the room
|
but hide the floating room summary, turn off **General settings → Show the room
|
||||||
|
|||||||
@@ -252,6 +252,8 @@ title: План дома
|
|||||||
Редакторы переключаются напрямую. Новая панель инструментов коротко проявляется,
|
Редакторы переключаются напрямую. Новая панель инструментов коротко проявляется,
|
||||||
а её фактическая высота плавно подстраивается даже при переносе кнопок на несколько строк;
|
а её фактическая высота плавно подстраивается даже при переносе кнопок на несколько строк;
|
||||||
при системной настройке уменьшения движения переход отключается.
|
при системной настройке уменьшения движения переход отключается.
|
||||||
|
Escape сначала отменяет активный диалог, меню, перетаскивание, выбор или
|
||||||
|
инструмент; следующее нажатие в нейтральном состоянии возвращает в Просмотр.
|
||||||
|
|
||||||
| Режим | Основное назначение | Устройства | Комнаты/стены | Декор и проёмы |
|
| Режим | Основное назначение | Устройства | Комнаты/стены | Декор и проёмы |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
@@ -274,7 +276,8 @@ title: План дома
|
|||||||
просмотре любому пользователю — **Настройки сводной панели** и показ или
|
просмотре любому пользователю — **Настройки сводной панели** и показ или
|
||||||
скрытие сводной панели. Пункт делает то же, что прежняя кнопка, и закрывает
|
скрытие сводной панели. Пункт делает то же, что прежняя кнопка, и закрывает
|
||||||
меню; Escape или касание вне меню просто закрывают его — касание при этом не
|
меню; Escape или касание вне меню просто закрывают его — касание при этом не
|
||||||
нажимает ничего на плане. В редакторе крестик закрытия остаётся в строке. На
|
нажимает ничего на плане. В редакторе крестик закрытия остаётся в строке, хотя
|
||||||
|
сами кнопки редакторов находятся только в меню. На
|
||||||
более широком экране шапка прежняя, в киоске её нет.
|
более широком экране шапка прежняя, в киоске её нет.
|
||||||
|
|
||||||
### Сводная панель
|
### Сводная панель
|
||||||
@@ -327,8 +330,11 @@ Escape и закрытие диалога отбрасывают изменен
|
|||||||
снизу. В слишком маленькой карточке она временно скрывается и возвращается при
|
снизу. В слишком маленькой карточке она временно скрывается и возвращается при
|
||||||
увеличении. Выключенная общая опция **Отображать на мобильных устройствах**
|
увеличении. Выключенная общая опция **Отображать на мобильных устройствах**
|
||||||
также скрывает панель, когда Home Assistant сообщает об узком режиме; кнопка
|
также скрывает панель, когда Home Assistant сообщает об узком режиме; кнопка
|
||||||
остаётся включённой, потому что локальный выбор не сброшен. В трёх редакторах
|
остаётся включённой, потому что локальный выбор не сброшен. Сама панель в трёх
|
||||||
и статической карточке панели и её кнопок нет.
|
редакторах и статической карточке не показывается. На экране шире 480 px обе её
|
||||||
|
кнопки в шапке остаются доступны и в редакторах: можно открыть настройки или
|
||||||
|
изменить локальный выбор показа, но панель появится только после возврата в
|
||||||
|
Просмотр. В телефонном меню эти два пункта по-прежнему есть только в Просмотре.
|
||||||
|
|
||||||
Подсветка комнаты сохраняется в Просмотре и киоске. Чтобы оставить подсветку,
|
Подсветка комнаты сохраняется в Просмотре и киоске. Чтобы оставить подсветку,
|
||||||
но убрать всплывающее описание комнаты, выключите **Общие настройки →
|
но убрать всплывающее описание комнаты, выключите **Общие настройки →
|
||||||
|
|||||||
+17
-9
@@ -53,12 +53,14 @@ inert everywhere outside its editor).
|
|||||||
background and editor-specific layers move on one short timeline, so opening
|
background and editor-specific layers move on one short timeline, so opening
|
||||||
an editor never flashes a default scale or a mixed dark/white frame. A rapid
|
an editor never flashes a default scale or a mixed dark/white frame. A rapid
|
||||||
second choice retargets from the visible intermediate frame. Reduced-motion
|
second choice retargets from the visible intermediate frame. Reduced-motion
|
||||||
preferences apply the same final state immediately. The header X sits in
|
preferences apply the same final state immediately. The header X sits in a
|
||||||
its own fixed 24 × 24 px slot right after the mode tabs (#647): the slot keeps
|
fixed 24 × 24 px slot inside the segmented control, immediately after the
|
||||||
its size, empty and hidden from assistive technology, outside the editors,
|
active editor (#660); in View the same empty, assistive-technology-hidden
|
||||||
so entering, leaving or switching editors never changes the header width or
|
reserve sits at the end. The group width therefore stays fixed while buttons
|
||||||
moves a tab; the X keeps its compact 13 px glyph and the whole slot is its
|
to the right of the active editor yield the reserved place. The X keeps its
|
||||||
hit target (≥ 24 × 24 px, #195).
|
compact 13 px glyph and the whole slot is its hit target (≥ 24 × 24 px,
|
||||||
|
#195). On a phone the mode buttons remain in the gear menu, while this one
|
||||||
|
slot stays visible in the header so the editor still has a direct exit.
|
||||||
- An editor's primary toolbar contains only persistent tools. Close is pinned
|
- An editor's primary toolbar contains only persistent tools. Close is pinned
|
||||||
in its own end cap. Selection actions, active-tool parameters, operation
|
in its own end cap. Selection actions, active-tool parameters, operation
|
||||||
hints and palettes appear in one translucent context tray over the top of the
|
hints and palettes appear in one translucent context tray over the top of the
|
||||||
@@ -121,8 +123,11 @@ local show and View-size controls only. In a landscape/square House Plan stage
|
|||||||
the panel is on the right; in a portrait stage it is centred at the bottom.
|
the panel is on the right; in a portrait stage it is centred at the bottom.
|
||||||
Native HA `narrow` controls the independent mobile permission. Insufficient
|
Native HA `narrow` controls the independent mobile permission. Insufficient
|
||||||
measured stage space temporarily hides the overlay without changing the saved
|
measured stage space temporarily hides the overlay without changing the saved
|
||||||
local choice. All summary surfaces disappear in Plan, Devices and Background;
|
local choice. The overlay remains View-only. On windows wider than 480 px its
|
||||||
`houseplan-space-card` never renders them.
|
settings and local show/hide buttons stay in the header in all three editors:
|
||||||
|
settings can be opened there and the local choice can be changed, but the
|
||||||
|
overlay appears only after returning to View. The phone gear keeps those two
|
||||||
|
entries View-only. `houseplan-space-card` never renders any summary surface.
|
||||||
|
|
||||||
The admin-only **General settings → Show Zigbee links on device hover** option
|
The admin-only **General settings → Show Zigbee links on device hover** option
|
||||||
is off by default. Once an admin explicitly reads cached ZHA data or updates a
|
is off by default. Once an admin explicitly reads cached ZHA data or updates a
|
||||||
@@ -142,7 +147,10 @@ Removed from this mode (they move, not die):
|
|||||||
- every edit button in the header (+device, 👁 show-all, ↺ reset, ⬡ rules, ⚙ general,
|
- every edit button in the header (+device, 👁 show-all, ↺ reset, ⬡ rules, ⚙ general,
|
||||||
per-space gear, markup toggle).
|
per-space gear, markup toggle).
|
||||||
|
|
||||||
Header in View: space tabs, summary control and zoom cluster; on a phone (≤ 480 px) space tabs, zoom and one gear whose menu holds the rest (#616).
|
Header in View: space tabs, summary control and zoom cluster. On wider screens
|
||||||
|
the summary control also remains available in editors; the fixed distance from
|
||||||
|
the editor group to zoom is half the former gap. On a phone (≤ 480 px): space
|
||||||
|
tabs, the editor-close reserve, zoom and one gear whose menu holds the rest.
|
||||||
|
|
||||||
## Plan — geometry and appearance of the space
|
## Plan — geometry and appearance of the space
|
||||||
|
|
||||||
|
|||||||
@@ -7568,8 +7568,8 @@ const MUTANT_DEFINITIONS = [
|
|||||||
because: '#647 AC1: the header must not show a device count in any mode or locale',
|
because: '#647 AC1: the header must not show a device count in any mode or locale',
|
||||||
patches: [{
|
patches: [{
|
||||||
file: 'src/houseplan-card.ts',
|
file: 'src/houseplan-card.ts',
|
||||||
find: " <span class=\"editor-close-slot\" aria-hidden=",
|
find: " const editorClose = html`<span class=\"editor-close-slot\" aria-hidden=",
|
||||||
replace: " <span class=\"count\">${this._devices.length} dev.</span><span class=\"editor-close-slot\" aria-hidden=",
|
replace: " const editorClose = html`<span class=\"count\">${this._devices.length} dev.</span><span class=\"editor-close-slot\" aria-hidden=",
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -7588,8 +7588,8 @@ const MUTANT_DEFINITIONS = [
|
|||||||
because: '#647 AC3: an X inside the active mode tab widens that tab and moves its neighbours',
|
because: '#647 AC3: an X inside the active mode tab widens that tab and moves its neighbours',
|
||||||
patches: [{
|
patches: [{
|
||||||
file: 'src/houseplan-card.ts',
|
file: 'src/houseplan-card.ts',
|
||||||
find: " <ha-icon icon=${ic}></ha-icon><span class=\"ml\">${this._t(('mode.' + m) as any)}</span>\n </button>`,",
|
find: " <ha-icon icon=${ic}></ha-icon><span class=\"ml\">${this._t(('mode.' + m) as any)}</span>\n </button>${this._mode === m ? editorClose : nothing}`",
|
||||||
replace: " <ha-icon icon=${ic}></ha-icon><span class=\"ml\">${this._t(('mode.' + m) as any)}</span>${this._mode === m ? html`<ha-icon class=\"closex\" icon=\"mdi:close\"></ha-icon>` : nothing}\n </button>`,",
|
replace: " <ha-icon icon=${ic}></ha-icon><span class=\"ml\">${this._t(('mode.' + m) as any)}</span>${this._mode === m ? html`<ha-icon class=\"closex\" icon=\"mdi:close\"></ha-icon>` : nothing}\n </button>${this._mode === m ? editorClose : nothing}`",
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -7598,8 +7598,8 @@ const MUTANT_DEFINITIONS = [
|
|||||||
because: '#647 AC4: outside an editor the reserve is inert and hidden from assistive technology',
|
because: '#647 AC4: outside an editor the reserve is inert and hidden from assistive technology',
|
||||||
patches: [{
|
patches: [{
|
||||||
file: 'src/houseplan-card.ts',
|
file: 'src/houseplan-card.ts',
|
||||||
find: " <span class=\"editor-close-slot\" aria-hidden=${this._mode === 'view' ? 'true' : nothing}>",
|
find: "<span class=\"editor-close-slot\" aria-hidden=${this._mode === 'view' ? 'true' : nothing}>",
|
||||||
replace: " <span class=\"editor-close-slot\" tabindex=\"0\">",
|
replace: "<span class=\"editor-close-slot\" tabindex=\"0\">",
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -7622,14 +7622,70 @@ const MUTANT_DEFINITIONS = [
|
|||||||
replace: " .editor-close-slot .closex ha-icon { --mdc-icon-size: 13px; }\n @media (max-width: 1100px) { .editor-close-slot .closex { display: none; } }",
|
replace: " .editor-close-slot .closex ha-icon { --mdc-icon-size: 13px; }\n @media (max-width: 1100px) { .editor-close-slot .closex { display: none; } }",
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
// #660: compact stable header, desktop summary controls and neutral Escape.
|
||||||
|
{
|
||||||
|
id: 'toolbar-close-slot-leaves-mode-group',
|
||||||
|
guard: 'node demo/smoke_toolbar_stable_width.mjs',
|
||||||
|
because: '#660 AC2: the one fixed X reserve belongs inside the segmented mode group, after the active button',
|
||||||
|
patches: [{
|
||||||
|
file: 'src/houseplan-card.ts',
|
||||||
|
find: ' </button>${this._mode === m ? editorClose : nothing}`',
|
||||||
|
replace: ' </button>`',
|
||||||
|
}, {
|
||||||
|
file: 'src/houseplan-card.ts',
|
||||||
|
find: " ${this._mode === 'view' ? editorClose : nothing}\n </div>`",
|
||||||
|
replace: ' </div>${editorClose}`',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'toolbar-mode-zoom-gap-regresses',
|
||||||
|
guard: 'node demo/smoke_toolbar_stable_width.mjs',
|
||||||
|
because: '#660 AC3: the modes-to-zoom distance is the specified compact 50/40.5 px, not the old flexible gap',
|
||||||
|
patches: [{
|
||||||
|
file: 'src/styles/chrome.styles.ts',
|
||||||
|
find: ' .head > .spacer { flex: 0 0 30px; }',
|
||||||
|
replace: ' .head > .spacer { flex: 0 0 80px; }',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'editor-summary-controls-return-to-view-only',
|
||||||
|
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||||
|
+ '&& node --test test/summary-panel-runtime.test.mjs',
|
||||||
|
because: '#660 AC1: wide-screen inline summary actions remain usable in all three editors',
|
||||||
|
patches: [{
|
||||||
|
file: 'src/summary-panel-runtime-loaded.ts',
|
||||||
|
find: " if (kiosk && this.host._mode !== 'view') return nothing;",
|
||||||
|
replace: " if (this.host._mode !== 'view') return nothing;",
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'phone-editor-close-hidden-with-mode-buttons',
|
||||||
|
guard: 'node demo/smoke_mobile_view_header.mjs',
|
||||||
|
because: '#660 AC4: hiding desktop mode buttons on a phone must leave the editor X slot visible',
|
||||||
|
patches: [{
|
||||||
|
file: 'src/styles/chrome.styles.ts',
|
||||||
|
find: ' .head > .title, .head > .spacer, .head > .header-action,',
|
||||||
|
replace: ' .head > .title, .head > .modes, .head > .spacer, .head > .header-action,',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'editor-neutral-escape-does-not-exit',
|
||||||
|
guard: 'node demo/smoke_editor_tabs.mjs',
|
||||||
|
because: '#660 AC5: after inner actions have had priority, a neutral Escape returns every editor to View',
|
||||||
|
patches: [{
|
||||||
|
file: 'src/houseplan-card.ts',
|
||||||
|
find: " e.preventDefault();\n this._setMode('view');\n }\n /** Remove the last transient point",
|
||||||
|
replace: " e.preventDefault();\n }\n /** Remove the last transient point",
|
||||||
|
}],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'phone-header-shows-title',
|
id: 'phone-header-shows-title',
|
||||||
guard: 'node demo/smoke_mobile_view_header.mjs',
|
guard: 'node demo/smoke_mobile_view_header.mjs',
|
||||||
because: '#616 AC1: on a phone the card title alone takes a row; the one-row header must hide it',
|
because: '#616 AC1: on a phone the card title alone takes a row; the one-row header must hide it',
|
||||||
patches: [{
|
patches: [{
|
||||||
file: 'src/styles/chrome.styles.ts',
|
file: 'src/styles/chrome.styles.ts',
|
||||||
find: ' .head > .title, .head > .modes, .head > .spacer, .head > .header-action,',
|
find: ' .head > .title, .head > .spacer, .head > .header-action,',
|
||||||
replace: ' .head > .modes, .head > .spacer, .head > .header-action,',
|
replace: ' .head > .spacer, .head > .header-action,',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ export type HeaderMenuInput = {
|
|||||||
kiosk: boolean;
|
kiosk: boolean;
|
||||||
mode: HeaderMode;
|
mode: HeaderMode;
|
||||||
hasFixedFloor: boolean;
|
hasFixedFloor: boolean;
|
||||||
/** Пункты сводной панели (её рантайм отдаёт их только в просмотре). */
|
/** Пункты сводной панели; мобильное меню добавляет их только в просмотре. */
|
||||||
summary: HeaderMenuItem[];
|
summary: HeaderMenuItem[];
|
||||||
t: (key: I18nKey) => string;
|
t: (key: I18nKey) => string;
|
||||||
actions: HeaderMenuActions;
|
actions: HeaderMenuActions;
|
||||||
|
|||||||
+13
-13
@@ -2764,7 +2764,6 @@ export class HouseplanCard extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
// close the topmost open dialog; info popups first, then editors
|
|
||||||
if (this._tapConfirm) { this._tapConfirm = null; return; }
|
if (this._tapConfirm) { this._tapConfirm = null; return; }
|
||||||
if (this._vacCalConfirm) {
|
if (this._vacCalConfirm) {
|
||||||
if (!this._vacCalConfirm.busy) this._vacCalConfirm = null;
|
if (!this._vacCalConfirm.busy) this._vacCalConfirm = null;
|
||||||
@@ -2858,8 +2857,6 @@ export class HouseplanCard extends LitElement {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this._decorDraft) this._decorDraft = null;
|
if (this._decorDraft) this._decorDraft = null;
|
||||||
else if (this._decorMove || this._dtDrag || this._bdDrag) this._cancelDecorGesture();
|
else if (this._decorMove || this._dtDrag || this._bdDrag) this._cancelDecorGesture();
|
||||||
// The palette is one explicit surface: Escape closes it and returns
|
|
||||||
// to Select in one step, regardless of whether a symbol was armed.
|
|
||||||
else if (this._decorTool === 'furniture') {
|
else if (this._decorTool === 'furniture') {
|
||||||
this._editorRuntime?._clearFurniturePreview();
|
this._editorRuntime?._clearFurniturePreview();
|
||||||
this._editorRuntime?._furnShiftDetach(); // #369(д) r2-H1
|
this._editorRuntime?._furnShiftDetach(); // #369(д) r2-H1
|
||||||
@@ -2885,9 +2882,10 @@ export class HouseplanCard extends LitElement {
|
|||||||
this._undoDevicePosition();
|
this._undoDevicePosition();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key === 'Escape' && this._deviceDrag) {
|
if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._cancelDeviceDrag();
|
if (this._deviceDrag) this._cancelDeviceDrag();
|
||||||
|
else this._setMode('view');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2998,9 +2996,11 @@ export class HouseplanCard extends LitElement {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this._tool === 'opening') this._clearOpeningPlacement(true);
|
if (this._tool === 'opening') this._clearOpeningPlacement(true);
|
||||||
this._tool = 'draw';
|
this._tool = 'draw';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
this._setMode('view');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Remove the last transient point or undo the terminal persisted wall. */
|
/** Remove the last transient point or undo the terminal persisted wall. */
|
||||||
private _undoPoint(): void {
|
private _undoPoint(): void {
|
||||||
if (!this._path.length) return;
|
if (!this._path.length) return;
|
||||||
@@ -10743,6 +10743,10 @@ export class HouseplanCard extends LitElement {
|
|||||||
);
|
);
|
||||||
const transitionStageBg = modeVisual?.stageColor || stageBg;
|
const transitionStageBg = modeVisual?.stageColor || stageBg;
|
||||||
const transitionBrightness = modeVisual?.sceneBrightness ?? 1;
|
const transitionBrightness = modeVisual?.sceneBrightness ?? 1;
|
||||||
|
const editorClose = html`<span class="editor-close-slot" aria-hidden=${this._mode === 'view' ? 'true' : nothing}>${this._mode !== 'view' ? html`<button
|
||||||
|
class="closex" title=${this._t('title.close_editor')} aria-label=${this._t('title.close_editor')} data-hp="editor-close" data-editor-navigation="view"
|
||||||
|
@click=${(e: Event) => { e.stopPropagation(); this._setMode('view'); }}><ha-icon icon="mdi:close"></ha-icon></button>`
|
||||||
|
: nothing}</span>`;
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
data-hp-state=${this._booting ? 'booting' : 'ready'} data-hp-mode=${this._mode}
|
data-hp-state=${this._booting ? 'booting' : 'ready'} data-hp-mode=${this._mode}
|
||||||
@@ -10816,14 +10820,10 @@ export class HouseplanCard extends LitElement {
|
|||||||
title=${this._t(('mode.' + m + '_tip') as any)}
|
title=${this._t(('mode.' + m + '_tip') as any)}
|
||||||
@click=${() => this._setMode(m)}>
|
@click=${() => this._setMode(m)}>
|
||||||
<ha-icon icon=${ic}></ha-icon><span class="ml">${this._t(('mode.' + m) as any)}</span>
|
<ha-icon icon=${ic}></ha-icon><span class="ml">${this._t(('mode.' + m) as any)}</span>
|
||||||
</button>`,
|
</button>${this._mode === m ? editorClose : nothing}`,
|
||||||
)}
|
)}
|
||||||
</div>${''/* #647: X owns a fixed slot (the old device-count place): header width never changes */}
|
${this._mode === 'view' ? editorClose : nothing}
|
||||||
<span class="editor-close-slot" aria-hidden=${this._mode === 'view' ? 'true' : nothing}>${this._mode !== 'view'
|
</div>`
|
||||||
? html`<button class="closex" title=${this._t('title.close_editor')} aria-label=${this._t('title.close_editor')}
|
|
||||||
data-hp="editor-close" data-editor-navigation="view"
|
|
||||||
@click=${(e: Event) => { e.stopPropagation(); this._setMode('view'); }}><ha-icon icon="mdi:close"></ha-icon></button>`
|
|
||||||
: nothing}</span>`
|
|
||||||
: nothing}
|
: nothing}
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<div class="zoomctl">
|
<div class="zoomctl">
|
||||||
|
|||||||
@@ -147,10 +147,22 @@ const chromeCoreStyles = css`
|
|||||||
.header-menu-item.on { color: var(--hp-accent); font-weight: 600; }
|
.header-menu-item.on { color: var(--hp-accent); font-weight: 600; }
|
||||||
:host([data-pointer-hover]) .header-menu-item:hover { background: rgba(127, 127, 127, 0.14); }
|
:host([data-pointer-hover]) .header-menu-item:hover { background: rgba(127, 127, 127, 0.14); }
|
||||||
.header-menu-item:focus-visible { outline: 2px solid var(--hp-accent); outline-offset: -2px; }
|
.header-menu-item:focus-visible { outline: 2px solid var(--hp-accent); outline-offset: -2px; }
|
||||||
|
.head > .spacer { flex: 0 0 30px; }
|
||||||
|
@media (max-width: 620px) and (min-width: 481px) {
|
||||||
|
.head > .spacer { flex-basis: 28.5px; }
|
||||||
|
}
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.hdr > .head { flex-wrap: nowrap; padding: 5px 8px; gap: 6px; }
|
.hdr > .head { flex-wrap: nowrap; padding: 5px 8px; gap: 6px; }
|
||||||
.head > .title, .head > .modes, .head > .spacer, .head > .header-action,
|
.head > .title, .head > .spacer, .head > .header-action,
|
||||||
.head > .summary-control { display: none; }
|
.head > .summary-control { display: none; }
|
||||||
|
.head > .modes {
|
||||||
|
flex: 0 0 24px;
|
||||||
|
width: 24px;
|
||||||
|
padding: 0;
|
||||||
|
gap: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.head > .modes .modetab { display: none; }
|
||||||
.head > .tabs {
|
.head > .tabs {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -163,7 +175,7 @@ const chromeCoreStyles = css`
|
|||||||
.head > .tabs .tab { flex: none; white-space: nowrap; max-width: 100%; }
|
.head > .tabs .tab { flex: none; white-space: nowrap; max-width: 100%; }
|
||||||
.head > .tabs .tabtitle { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
.head > .tabs .tabtitle { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.head > .tabs .tabedit, .head > .tabs .tabadd { display: none; }
|
.head > .tabs .tabedit, .head > .tabs .tabadd { display: none; }
|
||||||
.head > .zoomctl, .head > .editor-close-slot { flex: none; }
|
.head > .zoomctl, .head > .modes > .editor-close-slot { flex: none; }
|
||||||
.header-menu-wrap { display: inline-flex; }
|
.header-menu-wrap { display: inline-flex; }
|
||||||
}
|
}
|
||||||
.decorbar .dcolor {
|
.decorbar .dcolor {
|
||||||
@@ -221,10 +233,8 @@ const chromeCoreStyles = css`
|
|||||||
.tab.dragging { cursor: grabbing; opacity: 0.55; }
|
.tab.dragging { cursor: grabbing; opacity: 0.55; }
|
||||||
.tab.drop-before { box-shadow: inset 2px 0 0 0 var(--primary-color, #03a9f4); }
|
.tab.drop-before { box-shadow: inset 2px 0 0 0 var(--primary-color, #03a9f4); }
|
||||||
.tab.drop-after { box-shadow: inset -2px 0 0 0 var(--primary-color, #03a9f4); }
|
.tab.drop-after { box-shadow: inset -2px 0 0 0 var(--primary-color, #03a9f4); }
|
||||||
/* #647: the editor X owns a fixed slot right after the mode tabs (where
|
/* #660: the fixed X slot lives inside the mode group, immediately after
|
||||||
the device count used to be). One size for both states, so entering,
|
the active editor; in View the same reserve sits at the group end. */
|
||||||
leaving or switching editors never changes the header width; the slot is
|
|
||||||
the X's pointer target (>= 24 x 24, #195) around a 13 px glyph. */
|
|
||||||
.editor-close-slot {
|
.editor-close-slot {
|
||||||
--hp-editor-close-size: 24px;
|
--hp-editor-close-size: 24px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@@ -234,8 +234,7 @@ export class LoadedSummaryPanelRuntime {
|
|||||||
</aside>`;
|
</aside>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** #616: the same two actions as `renderControls` — items of the phone header menu
|
/** #616: the same two actions as `renderControls`; the phone menu offers them only in View. */
|
||||||
* (the menu itself offers them only in View, as `renderControls` does). */
|
|
||||||
public menuItems(): HeaderMenuItem[] {
|
public menuItems(): HeaderMenuItem[] {
|
||||||
const toggleTitle = this.toggleTitle();
|
const toggleTitle = this.toggleTitle();
|
||||||
return [
|
return [
|
||||||
@@ -260,7 +259,7 @@ export class LoadedSummaryPanelRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public renderControls(kiosk = false): TemplateResult | typeof nothing {
|
public renderControls(kiosk = false): TemplateResult | typeof nothing {
|
||||||
if (this.host._mode !== 'view') return nothing;
|
if (kiosk && this.host._mode !== 'view') return nothing;
|
||||||
const toggleTitle = this.toggleTitle();
|
const toggleTitle = this.toggleTitle();
|
||||||
const stop = (event: Event) => event.stopPropagation();
|
const stop = (event: Event) => event.stopPropagation();
|
||||||
return html`<div class="summary-control ${kiosk ? 'kiosk' : ''}" role="group"
|
return html`<div class="summary-control ${kiosk ? 'kiosk' : ''}" role="group"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
import { nothing } from 'lit';
|
||||||
|
|
||||||
import { SUMMARY_PANEL_API_VERSION } from '../test-build/summary-panel-api.js';
|
import { SUMMARY_PANEL_API_VERSION } from '../test-build/summary-panel-api.js';
|
||||||
import { SUMMARY_PANEL_LEGACY_SCALE_KEY, summaryLocalKey } from '../test-build/summary-panel.js';
|
import { SUMMARY_PANEL_LEGACY_SCALE_KEY, summaryLocalKey } from '../test-build/summary-panel.js';
|
||||||
@@ -72,6 +73,16 @@ const hostFixture = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test('#660 desktop summary controls stay available in editors while kiosk controls stay View-only', () => {
|
||||||
|
const host = hostFixture();
|
||||||
|
const runtime = new LoadedSummaryPanelRuntime(host);
|
||||||
|
for (const mode of ['plan', 'devices', 'decor']) {
|
||||||
|
host._mode = mode;
|
||||||
|
assert.notEqual(runtime.renderControls(false), nothing, `${mode}: desktop controls`);
|
||||||
|
assert.equal(runtime.renderControls(true), nothing, `${mode}: kiosk controls`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('#561 unresolved Masonry stays session-only and never imports an old DOM-path preference', () => {
|
test('#561 unresolved Masonry stays session-only and never imports an old DOM-path preference', () => {
|
||||||
const browser = installBrowserGlobals();
|
const browser = installBrowserGlobals();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user