From f385bed33619b81ec417c8de69a5cdb71fed578a Mon Sep 17 00:00:00 2001 From: Koen Reefman <44577373+Koen-Reefman@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:41:05 +0100 Subject: [PATCH 1/5] Fix wrong property in docs (#2593) Co-authored-by: Koen Reefman --- docs/components/dialog/use-cases.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/components/dialog/use-cases.md b/docs/components/dialog/use-cases.md index 7a73f52d8..aabfa66d1 100644 --- a/docs/components/dialog/use-cases.md +++ b/docs/components/dialog/use-cases.md @@ -162,7 +162,7 @@ No backdrop, hides on escape, prevents scrolling while opened, and focuses the b export const otherOverrides = () => { const cfg = { hasBackdrop: false, - hidesOnEscape: true, + hidesOnEsc: true, preventsScroll: true, elementToFocusAfterHide: document.body, }; @@ -192,7 +192,7 @@ Configuration passed to `config` property: ```js { hasBackdrop: false, - hidesOnEscape: true, + hidesOnEsc: true, preventsScroll: true, elementToFocusAfterHide: document.body } From e6a8fa70e5911eb04bfa4d3bede3af854cd32f11 Mon Sep 17 00:00:00 2001 From: gerjanvangeest Date: Tue, 28 Oct 2025 09:10:11 +0100 Subject: [PATCH 2/5] fix(input-amount-dropdown): add translations (#2598) --- .changeset/beige-apes-march.md | 5 ++ .../src/localizeNamespaceLoader.js | 53 +++++++++++++++++++ .../input-amount-dropdown/translations/bg.js | 5 ++ .../input-amount-dropdown/translations/cs.js | 5 ++ .../input-amount-dropdown/translations/de.js | 5 ++ .../input-amount-dropdown/translations/es.js | 5 ++ .../input-amount-dropdown/translations/fr.js | 5 ++ .../input-amount-dropdown/translations/hu.js | 5 ++ .../input-amount-dropdown/translations/id.js | 5 ++ .../input-amount-dropdown/translations/it.js | 5 ++ .../input-amount-dropdown/translations/nl.js | 5 ++ .../input-amount-dropdown/translations/pl.js | 5 ++ .../input-amount-dropdown/translations/ro.js | 5 ++ .../input-amount-dropdown/translations/ru.js | 5 ++ .../input-amount-dropdown/translations/sk.js | 5 ++ .../input-amount-dropdown/translations/uk.js | 5 ++ .../input-amount-dropdown/translations/zh.js | 5 ++ 17 files changed, 133 insertions(+) create mode 100644 .changeset/beige-apes-march.md create mode 100644 packages/ui/components/input-amount-dropdown/translations/bg.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/cs.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/de.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/es.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/fr.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/hu.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/id.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/it.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/nl.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/pl.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/ro.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/ru.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/sk.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/uk.js create mode 100644 packages/ui/components/input-amount-dropdown/translations/zh.js diff --git a/.changeset/beige-apes-march.md b/.changeset/beige-apes-march.md new file mode 100644 index 000000000..f5dae9a21 --- /dev/null +++ b/.changeset/beige-apes-march.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +[input-amount-dropdown] add translations diff --git a/packages/ui/components/input-amount-dropdown/src/localizeNamespaceLoader.js b/packages/ui/components/input-amount-dropdown/src/localizeNamespaceLoader.js index b5adcad76..535bb4055 100644 --- a/packages/ui/components/input-amount-dropdown/src/localizeNamespaceLoader.js +++ b/packages/ui/components/input-amount-dropdown/src/localizeNamespaceLoader.js @@ -1,6 +1,59 @@ /* eslint-disable import/no-extraneous-dependencies */ export const localizeNamespaceLoader = /** @param {string} locale */ locale => { switch (locale) { + case 'bg-BG': + case 'bg': + return import('@lion/ui/input-amount-dropdown-translations/bg.js'); + case 'cs-CZ': + case 'cs': + return import('@lion/ui/input-amount-dropdown-translations/cs.js'); + case 'de-DE': + case 'de': + return import('@lion/ui/input-amount-dropdown-translations/de.js'); + case 'en-AU': + case 'en-GB': + case 'en-US': + case 'en-PH': + case 'en': + return import('@lion/ui/input-amount-dropdown-translations/en.js'); + case 'es-ES': + case 'es': + return import('@lion/ui/input-amount-dropdown-translations/es.js'); + case 'fr-FR': + case 'fr-BE': + case 'fr': + return import('@lion/ui/input-amount-dropdown-translations/fr.js'); + case 'hu-HU': + case 'hu': + return import('@lion/ui/input-amount-dropdown-translations/hu.js'); + case 'id-ID': + case 'id': + return import('@lion/ui/input-amount-dropdown-translations/id.js'); + case 'it-IT': + case 'it': + return import('@lion/ui/input-amount-dropdown-translations/it.js'); + case 'nl-BE': + case 'nl-NL': + case 'nl': + return import('@lion/ui/input-amount-dropdown-translations/nl.js'); + case 'pl-PL': + case 'pl': + return import('@lion/ui/input-amount-dropdown-translations/pl.js'); + case 'ro-RO': + case 'ro': + return import('@lion/ui/input-amount-dropdown-translations/ro.js'); + case 'ru-RU': + case 'ru': + return import('@lion/ui/input-amount-dropdown-translations/ru.js'); + case 'sk-SK': + case 'sk': + return import('@lion/ui/input-amount-dropdown-translations/sk.js'); + case 'uk-UA': + case 'uk': + return import('@lion/ui/input-amount-dropdown-translations/uk.js'); + case 'zh-CN': + case 'zh': + return import('@lion/ui/input-amount-dropdown-translations/zh.js'); default: return import('@lion/ui/input-amount-dropdown-translations/en.js'); } diff --git a/packages/ui/components/input-amount-dropdown/translations/bg.js b/packages/ui/components/input-amount-dropdown/translations/bg.js new file mode 100644 index 000000000..1748b4503 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/bg.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Всички валути', + selectCurrency: 'Избор на валута', + suggestedCurrencies: 'Предпочитани валути', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/cs.js b/packages/ui/components/input-amount-dropdown/translations/cs.js new file mode 100644 index 000000000..0de789f38 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/cs.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Všechny měny', + selectCurrency: 'Vybrat měnu', + suggestedCurrencies: 'Preferované měny', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/de.js b/packages/ui/components/input-amount-dropdown/translations/de.js new file mode 100644 index 000000000..1075e3a14 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/de.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Alle Währungen', + selectCurrency: 'Währung auswählen', + suggestedCurrencies: 'Bevorzugte Währungen', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/es.js b/packages/ui/components/input-amount-dropdown/translations/es.js new file mode 100644 index 000000000..f1caa3e17 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/es.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Todas las divisas', + selectCurrency: 'Seleccione divisa', + suggestedCurrencies: 'Divisas preferidas', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/fr.js b/packages/ui/components/input-amount-dropdown/translations/fr.js new file mode 100644 index 000000000..b8bd8d8b7 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/fr.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Toutes les devises', + selectCurrency: 'Sélectionner une devise', + suggestedCurrencies: 'Devises préférées', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/hu.js b/packages/ui/components/input-amount-dropdown/translations/hu.js new file mode 100644 index 000000000..77dee3e9c --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/hu.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Minden pénznem', + selectCurrency: 'Pénznem kiválasztása', + suggestedCurrencies: 'Előnyben részesített pénznemek', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/id.js b/packages/ui/components/input-amount-dropdown/translations/id.js new file mode 100644 index 000000000..e5961aed2 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/id.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Semua mata uang', + selectCurrency: 'Pilih mata uang', + suggestedCurrencies: 'Mata uang pilihan', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/it.js b/packages/ui/components/input-amount-dropdown/translations/it.js new file mode 100644 index 000000000..22699b3de --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/it.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Tutte le valute', + selectCurrency: 'Seleziona valuta', + suggestedCurrencies: 'Valute preferite', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/nl.js b/packages/ui/components/input-amount-dropdown/translations/nl.js new file mode 100644 index 000000000..d279af859 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/nl.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: "Alle valuta's", + selectCurrency: 'Selecteer valuta', + suggestedCurrencies: "Voorkeurs valuta's", +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/pl.js b/packages/ui/components/input-amount-dropdown/translations/pl.js new file mode 100644 index 000000000..13f634af2 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/pl.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Wszystkie waluty', + selectCurrency: 'Wybierz walutę', + suggestedCurrencies: 'Preferowane waluty', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/ro.js b/packages/ui/components/input-amount-dropdown/translations/ro.js new file mode 100644 index 000000000..e3cbd0ca7 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/ro.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Toate valutele', + selectCurrency: 'Selectare valută', + suggestedCurrencies: 'Valute preferate', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/ru.js b/packages/ui/components/input-amount-dropdown/translations/ru.js new file mode 100644 index 000000000..0f20073ec --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/ru.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Все валюты', + selectCurrency: 'Выбрать валюту', + suggestedCurrencies: 'Предпочтительные валюты', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/sk.js b/packages/ui/components/input-amount-dropdown/translations/sk.js new file mode 100644 index 000000000..d7e8dcfd9 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/sk.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Všetky meny', + selectCurrency: 'Zvoliť menu', + suggestedCurrencies: 'Uprednostňované meny', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/uk.js b/packages/ui/components/input-amount-dropdown/translations/uk.js new file mode 100644 index 000000000..e7e957332 --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/uk.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: 'Усі валюти', + selectCurrency: 'Вибрати валюту', + suggestedCurrencies: 'Обрані валюти', +}; diff --git a/packages/ui/components/input-amount-dropdown/translations/zh.js b/packages/ui/components/input-amount-dropdown/translations/zh.js new file mode 100644 index 000000000..660e1297f --- /dev/null +++ b/packages/ui/components/input-amount-dropdown/translations/zh.js @@ -0,0 +1,5 @@ +export default { + allCurrencies: '所有货币', + selectCurrency: '选择货币', + suggestedCurrencies: '首选货币', +}; From abcc6fdd6949397ce4690551f453d238c2802edb Mon Sep 17 00:00:00 2001 From: Oleksii Kadurin Date: Thu, 30 Oct 2025 12:47:53 +0100 Subject: [PATCH 3/5] fix: hidesOnEsc for nested overlays --- .changeset/yellow-hotels-sell.md | 5 + .../test/lion-dialog.integration.test.js | 170 ++++++++++++++++++ .../overlays/src/OverlayController.js | 41 ++++- .../test-suites/OverlayMixin.suite.js | 57 ++++++ .../overlays/test/OverlayMixin.test.js | 4 +- 5 files changed, 268 insertions(+), 9 deletions(-) create mode 100644 .changeset/yellow-hotels-sell.md create mode 100644 packages/ui/components/dialog/test/lion-dialog.integration.test.js diff --git a/.changeset/yellow-hotels-sell.md b/.changeset/yellow-hotels-sell.md new file mode 100644 index 000000000..17b6ddad7 --- /dev/null +++ b/.changeset/yellow-hotels-sell.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +fix hidesOnEsc for nested overlays diff --git a/packages/ui/components/dialog/test/lion-dialog.integration.test.js b/packages/ui/components/dialog/test/lion-dialog.integration.test.js new file mode 100644 index 000000000..35e547f9e --- /dev/null +++ b/packages/ui/components/dialog/test/lion-dialog.integration.test.js @@ -0,0 +1,170 @@ +/* eslint-disable lit-a11y/no-autofocus */ +import { expect, fixture as _fixture, html, waitUntil } from '@open-wc/testing'; +import { sendKeys } from '@web/test-runner-commands'; + +import '@lion/ui/define/lion-button.js'; +import '@lion/ui/define/lion-combobox.js'; +import '@lion/ui/define/lion-select-rich.js'; +import '@lion/ui/define/lion-option.js'; +import '@lion/ui/define/lion-dialog.js'; +import '@lion/ui/define/lion-tooltip.js'; + +import { mimicUserTyping } from '@lion/ui/combobox-test-helpers.js'; +import sinon from 'sinon'; + +/** + * @typedef {import('../src/LionDialog.js').LionDialog} LionDialog + * @typedef {import('lit').TemplateResult} TemplateResult + */ +const fixture = /** @type {(arg: TemplateResult) => Promise} */ (_fixture); + +const dropDownEntries = ['Apple', 'Banana']; + +describe('lion-dialog', () => { + describe('lion-combobox integration', () => { + it('should close lion-combobox dropdown on Escape and should not close the parent lion-dialog', async () => { + const el = await fixture(html` + + Open Dialog +
Combobox example
+
+ + ${dropDownEntries.map( + (/** @type { string } */ entry) => + html` ${entry}`, + )} + +
+
+ `); + const dialogInvoker = /** @type {HTMLElement} */ (el.querySelector('[slot="invoker"]')); + dialogInvoker.click(); + const combobox = /** @type {HTMLElement} */ el.querySelector('lion-combobox'); + const isComboboxRendered = () => !!combobox?.shadowRoot?.childNodes.length; + await waitUntil(isComboboxRendered); + // @ts-ignore + await mimicUserTyping(combobox, 'a'); + const firstOption = /** @type { HTMLElement | undefined } */ ( + el.querySelectorAll('lion-option')?.[0] + ); + // @ts-ignore + const isComboboxFirstOptionInDropdownVisible = () => firstOption?.checkVisibility(); + await waitUntil(isComboboxFirstOptionInDropdownVisible); + const comboboxDialog = combobox?.shadowRoot?.querySelector('dialog'); + // Note, do not remove `console.log` down below. There is a bug in test engine. + // Referring dialog.close from the test environement fixes the bug + console.log(sinon.spy(comboboxDialog?.close)); + const comboboxInput = combobox?.querySelector('input'); + comboboxInput?.focus(); + const dropdownDialog = combobox?.shadowRoot?.querySelector('dialog'); + // @ts-ignore + const dropdownDialogCloseSpy = sinon.spy(dropdownDialog, 'close'); + await sendKeys({ + press: 'Escape', + }); + // @ts-ignore + const isDialogVisible = () => el?.shadowRoot?.querySelector('dialog')?.checkVisibility(); + await waitUntil(() => dropdownDialogCloseSpy.called); + expect(isDialogVisible()).to.equal(true); + }); + }); + + describe('lion-select-rich integration', () => { + it('should close lion-select-rich dropdown on Escape and should not close the parent lion-dialog', async () => { + const el = await fixture(html` + + Open Dialog +
Select rich example
+
+ + ${dropDownEntries.map( + (/** @type { string } */ entry) => + html` ${entry}`, + )} + +
+
+ `); + const dialogInvoker = /** @type {HTMLElement} */ (el.querySelector('[slot="invoker"]')); + dialogInvoker.click(); + const selectRichInvoker = /** @type {HTMLElement} */ ( + el.querySelector('lion-select-invoker') + ); + const isSelectRichInvokerRendered = () => !!selectRichInvoker?.shadowRoot?.childNodes.length; + await waitUntil(isSelectRichInvokerRendered); + const selectRich = el?.querySelector('lion-select-rich'); + const selectRichDialog = selectRich?.shadowRoot?.querySelector('dialog'); + // Note, do not remove `console.log` down below. There is a bug in test engine. + // Referring dialog.close from the test environement fixes the bug + console.log(selectRichDialog?.close); + + selectRichInvoker?.click(); + const dropdownDialog = el + ?.querySelector('lion-select-rich') + ?.shadowRoot?.querySelector('dialog'); + // @ts-ignore + const dropdownDialogCloseSpy = sinon.spy(dropdownDialog, 'close'); + const isDropdownVisible = () => + el + ?.querySelector('lion-select-rich') + ?.shadowRoot?.querySelector('dialog') + // @ts-ignore + ?.checkVisibility(); + await waitUntil(isDropdownVisible); + const lionOptions = /** @type {HTMLElement} */ (el.querySelector('lion-options')); + lionOptions?.focus(); + await sendKeys({ + press: 'Escape', + }); + // @ts-ignore + const isDialogVisible = () => el?.shadowRoot?.querySelector('dialog')?.checkVisibility(); + await waitUntil(() => dropdownDialogCloseSpy.called); + expect(isDialogVisible()).to.equal(true); + }); + }); + + describe('lion-tooltip integration', () => { + it('should close lion-tooltip on first Escape and should close the parent lion-dialog on the second Escape', async () => { + const el = await fixture(html` + + Open Dialog +
Tooltip example
+
+ + +
This is a tooltip
+
+
+
+ `); + + await el.updateComplete; + const dialogInvoker = /** @type {HTMLElement} */ (el.querySelector('.dialog-invoker')); + dialogInvoker.click(); + const tooltip = /** @type {HTMLElement} */ el.querySelector('lion-tooltip'); + const isTooltipRendered = () => !!tooltip?.shadowRoot?.childNodes.length; + await waitUntil(isTooltipRendered); + const tooltipButton = /** @type {HTMLElement} */ (el.querySelector('.demo-tooltip-invoker')); + tooltipButton?.focus(); + const getTooltipContent = () => + el + .querySelector('lion-tooltip') + ?.shadowRoot?.querySelector('#overlay-content-node-wrapper'); + // @ts-ignore + const isTooltipContentVisible = () => getTooltipContent()?.checkVisibility(); + await waitUntil(isTooltipContentVisible); + await sendKeys({ + press: 'Escape', + }); + // @ts-ignore + const isDialogVisible = () => el?.shadowRoot?.querySelector('dialog')?.checkVisibility(); + expect(isTooltipContentVisible()).to.equal(false); + expect(isDialogVisible()).to.equal(true); + await sendKeys({ + press: 'Escape', + }); + expect(isTooltipContentVisible()).to.equal(false); + expect(isDialogVisible()).to.equal(false); + }); + }); +}); diff --git a/packages/ui/components/overlays/src/OverlayController.js b/packages/ui/components/overlays/src/OverlayController.js index f6eebe038..050274973 100644 --- a/packages/ui/components/overlays/src/OverlayController.js +++ b/packages/ui/components/overlays/src/OverlayController.js @@ -197,15 +197,24 @@ export class OverlayController extends EventTarget { this._contentId = `overlay-content--${Math.random().toString(36).slice(2, 10)}`; /** @private */ this.__originalAttrs = new Map(); + /** @private */ + this.__escKeyHandler = this.__escKeyHandler.bind(this); this.updateConfig(config); /** @private */ this.__hasActiveTrapsKeyboardFocus = false; /** @private */ this.__hasActiveBackdrop = true; /** @private */ - this.__escKeyHandler = this.__escKeyHandler.bind(this); - /** @private */ this.__cancelHandler = this.__cancelHandler.bind(this); + /** + * The property is used to skip `__escKeyHandler` handler for overlays that have been closed + * by `__escKeyHandlerCalled` previously. + * `__escKeyHandlerCalled` is set to `false` right before overlay show up + * `__escKeyHandlerCalled` is set to `true` when `__escKeyHandler` is called at least one time + * after each 'show' phase + * @private + */ + this.__escKeyHandlerCalled = false; } /** @@ -1211,13 +1220,21 @@ export class OverlayController extends EventTarget { * @returns {void} */ __escKeyHandler(event) { - if (event.key !== 'Escape' || childDialogsClosedInEventLoopWeakmap.has(event)) return; + if ( + event.key !== 'Escape' || + childDialogsClosedInEventLoopWeakmap.has(event) || + (!this.isShown && this.__escKeyHandlerCalled) + ) { + return; + } const hasPressedInside = event.composedPath().includes(this.contentNode) || + (this.invokerNode && event.composedPath().includes(this.invokerNode)) || deepContains(this.contentNode, /** @type {HTMLElement|ShadowRoot} */ (event.target)); if (hasPressedInside) { + this.__escKeyHandlerCalled = true; this.hide(); // We could do event.stopPropagation() here, but we don't want to hide info for // the outside world about user interactions. Instead, we store the event in a WeakMap @@ -1245,12 +1262,21 @@ export class OverlayController extends EventTarget { * @protected */ _handleHidesOnEsc({ phase }) { - if (phase === 'show') { + if (phase === 'init') { + // we remove previously added (if any) event listener to guarantee + // there is only one Escape handler added here. + // Note `init` phase triggered on every `updateConfig` call and that + // could happen multiple times during the component life cycle + this.contentNode.removeEventListener('keyup', this.__escKeyHandler); this.contentNode.addEventListener('keyup', this.__escKeyHandler); if (this.invokerNode) { this.invokerNode.addEventListener('keyup', this.__escKeyHandler); } - } else if (phase === 'hide' || phase === 'teardown') { + } + if (phase === 'show') { + this.__escKeyHandlerCalled = false; + } + if (phase === 'teardown') { this.contentNode.removeEventListener('keyup', this.__escKeyHandler); if (this.invokerNode) { this.invokerNode.removeEventListener('keyup', this.__escKeyHandler); @@ -1263,9 +1289,10 @@ export class OverlayController extends EventTarget { * @protected */ _handleHidesOnOutsideEsc({ phase }) { - if (phase === 'show') { + if (phase === 'init') { + document.removeEventListener('keyup', this.#outsideEscKeyHandler); document.addEventListener('keyup', this.#outsideEscKeyHandler); - } else if (phase === 'hide' || phase === 'teardown') { + } else if (phase === 'teardown') { document.removeEventListener('keyup', this.#outsideEscKeyHandler); } } diff --git a/packages/ui/components/overlays/test-suites/OverlayMixin.suite.js b/packages/ui/components/overlays/test-suites/OverlayMixin.suite.js index 983f55cb7..9918a0a2e 100644 --- a/packages/ui/components/overlays/test-suites/OverlayMixin.suite.js +++ b/packages/ui/components/overlays/test-suites/OverlayMixin.suite.js @@ -111,6 +111,63 @@ export function runOverlayMixinSuite({ tagString, tag, suffix = '' }) { expect(el.opened).to.be.false; }); + /** + * In case OverlayMixin has a handler for Escape which sets `opened` to `false`, + * make sure it does not conflict with the Escape handler in OverlayController. + * */ + it('syncs opened between overlayController and OverlayMixin on Escape', async () => { + const config = { hidesOnEsc: true }; + const el = /** @type {OverlayEl} */ ( + await fixture(html` + <${tag} id="main-dialog" .config="${config}"> +
+ open nested overlay: + <${tag} id="sub-dialog" .config="${config}"> +
+ +
+ + +
+ + + `) + ); + await el.updateComplete; + const subDialog = /** @type {OverlayEl} */ (el.querySelector('#sub-dialog')); + const nestedContentInput = /** @type {OverlayEl} */ (el.querySelector('#nestedContentInput')); + + // Note, OverlayController has Escape key listener added to "#nestedContent". + // And the sub-dialog adds the Escape key listener to "#nestedContentInput" which is a child evelement of "#nestedContent" + // In this case when "#nestedContentInput" is focused and Escape is pressed, the listener of "#nestedContentInput" + // is always called before the listener set for "#nestedContent" inside OverlayController. + // This way we check the scenario when OverlayController's `hide()` is called first from + // The OverlayMixin and it does not prevent OverlayController `hidesOnEsc` handler from being called + nestedContentInput?.addEventListener('keyup', (/** @type {* & KeyboardEvent} */ event) => { + if (event.key === 'Escape') { + subDialog.opened = false; + } + }); + + el.opened = true; + await el.updateComplete; + await el._overlayCtrl._showComplete; + expect(el.opened).to.be.true; + expect(el._overlayCtrl.isShown).to.be.true; + + subDialog.opened = true; + await subDialog.updateComplete; + await subDialog._overlayCtrl._showComplete; + expect(subDialog.opened).to.be.true; + expect(subDialog._overlayCtrl.isShown).to.be.true; + + await mimicEscapePress(nestedContentInput); + expect(subDialog.opened).to.be.false; + expect(subDialog._overlayCtrl.isShown).to.be.false; + expect(el.opened).to.be.true; + expect(el._overlayCtrl.isShown).to.be.true; + }); + // TODO: put this tests in OverlayController.test.js instead? it('does not change the body size when opened', async () => { const parentNode = document.createElement('div'); diff --git a/packages/ui/components/overlays/test/OverlayMixin.test.js b/packages/ui/components/overlays/test/OverlayMixin.test.js index 0232fac18..20a719b5d 100644 --- a/packages/ui/components/overlays/test/OverlayMixin.test.js +++ b/packages/ui/components/overlays/test/OverlayMixin.test.js @@ -7,9 +7,9 @@ const tagString = defineCE( class extends OverlayMixin(LitElement) { render() { return html` - +
-
content of the overlay
+
`; } From 014a136cb197f258d6ec53188e1fb64e286a59ba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:38:25 +0100 Subject: [PATCH 4/5] Version Packages (#2599) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/beige-apes-march.md | 5 ----- .changeset/yellow-hotels-sell.md | 5 ----- packages/ui/CHANGELOG.md | 7 +++++++ packages/ui/package.json | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 .changeset/beige-apes-march.md delete mode 100644 .changeset/yellow-hotels-sell.md diff --git a/.changeset/beige-apes-march.md b/.changeset/beige-apes-march.md deleted file mode 100644 index f5dae9a21..000000000 --- a/.changeset/beige-apes-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@lion/ui': patch ---- - -[input-amount-dropdown] add translations diff --git a/.changeset/yellow-hotels-sell.md b/.changeset/yellow-hotels-sell.md deleted file mode 100644 index 17b6ddad7..000000000 --- a/.changeset/yellow-hotels-sell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@lion/ui': patch ---- - -fix hidesOnEsc for nested overlays diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 86982239d..4dcfc4476 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,12 @@ # @lion/ui +## 0.15.2 + +### Patch Changes + +- e6a8fa7: [input-amount-dropdown] add translations +- abcc6fd: fix hidesOnEsc for nested overlays + ## 0.15.1 ### Patch Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index 979616104..6c6dc8cc0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@lion/ui", - "version": "0.15.1", + "version": "0.15.2", "description": "A package of extendable web components", "license": "MIT", "author": "ing-bank", From eb8b7bd73fda44f2b07f9fead15d803b029d7935 Mon Sep 17 00:00:00 2001 From: Tobi Date: Wed, 5 Nov 2025 08:52:01 +0100 Subject: [PATCH 5/5] Tobi fixed todo in readme (#2604) * Removed/Commented out TODO section in readme * Removed TODO section in readme --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c92ab3a4d..73473e7ca 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,6 @@ They provide an unopinionated, white-label layer that can be extended to your ow Note. There is the same error on master. This issue is not caused by the migration -### TODO - ## How to install ```bash