diff --git a/demo/smoke_esc_dialogs.mjs b/demo/smoke_esc_dialogs.mjs index a146e6dc..47c78264 100644 --- a/demo/smoke_esc_dialogs.mjs +++ b/demo/smoke_esc_dialogs.mjs @@ -10,6 +10,15 @@ const res = await page.evaluate(async () => { while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement; return active; }; + const composedContains = (root, target) => { + let node = target; + while (node) { + if (node === root) return true; + const tree = node.getRootNode?.(); + node = node.parentNode || (tree instanceof ShadowRoot ? tree.host : null); + } + return false; + }; // A11Y-02: the shared native fallback carries the same modal/focus contract // that ha-dialog supplies in Home Assistant. @@ -26,7 +35,9 @@ const res = await page.evaluate(async () => { && native?.getAttribute('aria-modal') === 'true' && !!titleId && !!hp.shadowRoot.getElementById(titleId)?.textContent.trim(); let active = deepActive(); - out.initialFocus = !!active && (hp.contains(active) || hp.shadowRoot.contains(active)); + // Unified colour controls put the first focus target in their own shadow + // root. Test composed ownership, not only light-DOM containment. + out.initialFocus = !!active && composedContains(hp, active); const focusable = [...hp.querySelectorAll('button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])')]; const last = focusable.at(-1); last?.focus(); diff --git a/demo/smoke_ha_controls.mjs b/demo/smoke_ha_controls.mjs index 49dc9eec..b430856d 100644 --- a/demo/smoke_ha_controls.mjs +++ b/demo/smoke_ha_controls.mjs @@ -137,6 +137,7 @@ const res = await page.evaluate(async () => { c._markerDialog = { ...c._markerDialog, name: 'Passive lamp', bindingMode: 'virtual', binding: 'virtual', lightRole: 'always', lightRoleTouched: true }; await c.updateComplete; + c._setMarkerGlowMode('fixed'); await c.updateComplete; const glowRadios = [...sr().querySelectorAll('hp-dialog input[name="marker-glow-mode"]')]; out.passiveLiveModeDisabled = glowRadios.find((radio) => radio.value === 'auto')?.disabled === true; out.passiveManualModesEnabled = glowRadios.filter((radio) => radio.value !== 'auto') @@ -151,14 +152,18 @@ const res = await page.evaluate(async () => { && passiveFields.glow_color?.bri === 0.42; c._markerDialog = null; await c.updateComplete; - // --- 9) general settings: opacity sliders take the ha branch too ------- + // --- 9) #180: general colour opacity now belongs to the shared picker --- c._openSettingsDialog(); await c.updateComplete; - out.gsHaSliders = sr().querySelectorAll('hp-dialog ha-slider').length >= 9; - const gsl = sr().querySelector('hp-dialog ha-slider'); - gsl.value = 55; - gsl.dispatchEvent(new Event('input', { bubbles: true })); + const generalPickers = [...sr().querySelectorAll('hp-dialog hp-color-opacity')]; + out.gsUnifiedPickers = generalPickers.length === 12 + && generalPickers.filter((picker) => picker.showOpacity).length === 11; + out.gsNoLegacyHaOpacitySliders = sr().querySelectorAll('hp-dialog ha-slider').length === 0; + generalPickers[0].dispatchEvent(new CustomEvent('hp-color-opacity-change', { + detail: { color: '#123456', opacity: 0.55 }, bubbles: true, composed: true, + })); await c.updateComplete; - out.gsUpstream = Math.round((Object.values(c._settingsDialog.colors)[0].a) * 100) === 55; + out.gsUpstream = Object.values(c._settingsDialog.colors) + .some((value) => value.c === '#123456' && Math.round(value.a * 100) === 55); c._settingsDialog = null; await c.updateComplete; return out; });