@lion/ui 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/combobox/test/lion-combobox.test.js +2 -0
- package/components/core/test/ScopedElementsMixin.test.js +3 -3
- package/components/dialog/test/lion-dialog.test.js +54 -0
- package/components/dialog/test-helpers/test-router.js +43 -0
- package/components/form-core/src/FormatMixin.js +66 -18
- package/components/form-core/test-suites/FormatMixin.suite.js +107 -8
- package/components/form-core/types/FormatMixinTypes.ts +4 -1
- package/components/form-integrations/test/dialog-integrations.test.js +6 -2
- package/components/input-amount/src/parsers.js +3 -3
- package/components/input-amount/test/lion-input-amount.test.js +36 -2
- package/components/input-amount/test/parsers.test.js +11 -0
- package/components/localize/src/number/getDecimalSeparator.js +1 -1
- package/components/localize/src/number/parseNumber.js +9 -6
- package/components/localize/test/number/parseNumber.test.js +11 -0
- package/components/localize/types/LocalizeMixinTypes.ts +3 -3
- package/components/overlays/src/OverlayController.js +4 -5
- package/components/overlays/src/OverlayMixin.js +20 -14
- package/components/overlays/test/OverlayController.test.js +11 -8
- package/components/overlays/test-suites/OverlayMixin.suite.js +184 -9
- package/components/overlays/types/ArrowMixinTypes.ts +11 -2
- package/components/overlays/types/OverlayConfig.ts +15 -13
- package/components/select-rich/src/LionSelectRich.js +3 -1
- package/components/select-rich/test/lion-select-rich.test.js +44 -0
- package/custom-elements.json +1302 -76
- package/dist-types/components/dialog/test/lion-dialog.test.d.ts.map +1 -1
- package/dist-types/components/dialog/test-helpers/test-router.d.ts +2 -0
- package/dist-types/components/dialog/test-helpers/test-router.d.ts.map +1 -0
- package/dist-types/components/form-core/src/FormatMixin.d.ts +1 -1
- package/dist-types/components/form-core/src/FormatMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/test-suites/FormatMixin.suite.d.ts.map +1 -1
- package/dist-types/components/form-core/types/FormatMixinTypes.d.ts +3 -2
- package/dist-types/components/form-core/types/FormatMixinTypes.d.ts.map +1 -1
- package/dist-types/components/form-integrations/test/dialog-integrations.test.d.ts.map +1 -1
- package/dist-types/components/input-amount/src/parsers.d.ts +2 -2
- package/dist-types/components/input-amount/src/parsers.d.ts.map +1 -1
- package/dist-types/components/input-amount/test/lion-input-amount.test.d.ts.map +1 -1
- package/dist-types/components/localize/src/number/parseNumber.d.ts.map +1 -1
- package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts +3 -2
- package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
- package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts +4 -4
- package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts.map +1 -1
- package/dist-types/components/overlays/types/ArrowMixinTypes.d.ts +2 -2
- package/dist-types/components/overlays/types/ArrowMixinTypes.d.ts.map +1 -1
- package/dist-types/components/overlays/types/OverlayConfig.d.ts +4 -4
- package/dist-types/components/overlays/types/OverlayConfig.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
- package/dist-types/components/select-rich/test/lion-select-rich.test.d.ts.map +1 -1
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/checkbox-group/api-table.md +2 -2
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/combobox/api-table.md +1 -0
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +1 -1
- package/docs/components/dialog/api-table.md +1 -0
- package/docs/components/dialog/overview.md +1 -1
- package/docs/components/fieldset/overview.md +5 -5
- package/docs/components/form/overview.md +3 -3
- package/docs/components/form/use-cases.md +2 -2
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/api-table.md +1 -1
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input-amount/api-table.md +1 -1
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/api-table.md +1 -1
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/api-table.md +2 -1
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/api-table.md +1 -1
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/api-table.md +1 -1
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/api-table.md +1 -1
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/api-table.md +1 -1
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/api-table.md +1 -1
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-tel/api-table.md +1 -1
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +6 -6
- package/docs/components/input-tel-dropdown/api-table.md +1 -1
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/listbox/api-table.md +1 -1
- package/docs/components/listbox/use-cases.md +1 -1
- package/docs/components/radio-group/api-table.md +1 -1
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select/api-table.md +1 -1
- package/docs/components/select/use-cases.md +1 -1
- package/docs/components/select-rich/api-table.md +1 -0
- package/docs/components/select-rich/use-cases.md +1 -1
- package/docs/components/switch/api-table.md +1 -1
- package/docs/components/tabs/examples.md +1 -1
- package/docs/components/textarea/api-table.md +1 -1
- package/docs/components/tooltip/api-table.md +1 -0
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/providence-analytics/overview.md +5 -5
- package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
- package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
- package/docs/fundamentals/rationales/accessibility.md +1 -1
- package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
- package/docs/fundamentals/systems/core/overview.md +1 -1
- package/docs/fundamentals/systems/form/api-table.md +1 -1
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/validate.md +2 -2
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/overlays/configuration.md +2 -2
- package/docs/fundamentals/systems/overlays/overview.md +5 -5
- package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +5 -5
- package/docs/guides/how-to/create-a-form.md +4 -4
- package/docs/guides/how-to/extend-a-native-input.md +2 -2
- package/docs/guides/index.md +8 -8
- package/docs/guides/principles/index.md +5 -5
- package/docs/guides/principles/styling.md +1 -1
- package/package.json +1 -1
|
@@ -16,14 +16,18 @@ import { getDecimalSeparator } from './getDecimalSeparator.js';
|
|
|
16
16
|
* parseNumber('1,234.56'); // method: heuristic => 1234.56
|
|
17
17
|
*
|
|
18
18
|
* @param {string} value Clean number (only [0-9 ,.]) to be parsed
|
|
19
|
-
* @param {
|
|
20
|
-
* @param {string?} [options.mode] auto|pasted
|
|
19
|
+
* @param {{mode?:'auto'|'pasted'|'user-edited'; viewValueStates?:string[]}} options
|
|
21
20
|
* @return {string} unparseable|withLocale|heuristic
|
|
22
21
|
*/
|
|
23
|
-
function getParseMode(value, { mode = 'auto' } = {}) {
|
|
22
|
+
function getParseMode(value, { mode = 'auto', viewValueStates } = {}) {
|
|
24
23
|
const separators = value.match(/[., ]/g);
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
// When a user edits an existing value, we already formatted it with a certain locale.
|
|
26
|
+
// For best UX, we stick with this locale
|
|
27
|
+
const shouldAlignWithExistingSeparators =
|
|
28
|
+
viewValueStates?.includes('formatted') && mode === 'user-edited';
|
|
29
|
+
|
|
30
|
+
if (!separators || shouldAlignWithExistingSeparators) {
|
|
27
31
|
return 'withLocale';
|
|
28
32
|
}
|
|
29
33
|
if (mode === 'auto' && separators.length === 1) {
|
|
@@ -52,8 +56,7 @@ function getParseMode(value, { mode = 'auto' } = {}) {
|
|
|
52
56
|
* @param {import('../../types/LocalizeMixinTypes.js').FormatNumberOptions} options Locale Options
|
|
53
57
|
*/
|
|
54
58
|
function parseWithLocale(value, options) {
|
|
55
|
-
const
|
|
56
|
-
const separator = getDecimalSeparator(locale, options);
|
|
59
|
+
const separator = getDecimalSeparator(options?.locale, options);
|
|
57
60
|
const regexNumberAndLocaleSeparator = new RegExp(`[0-9${separator}-]`, 'g');
|
|
58
61
|
let numberAndLocaleSeparator = value.match(regexNumberAndLocaleSeparator)?.join('');
|
|
59
62
|
if (separator === ',') {
|
|
@@ -74,6 +74,17 @@ describe('parseNumber()', () => {
|
|
|
74
74
|
expect(parseNumber('123456.78', { mode: 'pasted' })).to.equal(123456.78);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
it('detects separators withLocale when "user-edited" mode combined with viewValueStates "formatted" used e.g. 123.456,78', async () => {
|
|
78
|
+
expect(parseNumber('123,456.78', { mode: 'auto' })).to.equal(123456.78);
|
|
79
|
+
expect(
|
|
80
|
+
parseNumber('123,456.78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
|
|
81
|
+
).to.equal(123456.78);
|
|
82
|
+
expect(parseNumber('123.456,78', { mode: 'auto' })).to.equal(123456.78);
|
|
83
|
+
expect(
|
|
84
|
+
parseNumber('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
|
|
85
|
+
).to.equal(123.45678);
|
|
86
|
+
});
|
|
87
|
+
|
|
77
88
|
it('detects separators unparseable when there are 2 same ones e.g. 1.234.56', () => {
|
|
78
89
|
expect(parseNumber('1.234.56')).to.equal(123456);
|
|
79
90
|
expect(parseNumber('1,234,56')).to.equal(123456);
|
|
@@ -21,7 +21,7 @@ export declare interface FormatDateOptions extends Intl.DateTimeFormatOptions {
|
|
|
21
21
|
|
|
22
22
|
roundMode?: string;
|
|
23
23
|
returnIfNaN?: string;
|
|
24
|
-
mode?: 'pasted' | 'auto';
|
|
24
|
+
mode?: 'pasted' | 'auto' | 'user-edited';
|
|
25
25
|
|
|
26
26
|
postProcessors?: Map<string, DatePostProcessor>;
|
|
27
27
|
}
|
|
@@ -41,8 +41,8 @@ export declare interface FormatNumberOptions extends Intl.NumberFormatOptions {
|
|
|
41
41
|
// https://en.wikipedia.org/wiki/Decimal_separator#Digit_grouping
|
|
42
42
|
// note the half space in there as well
|
|
43
43
|
groupSeparator?: ',' | '.' | ' ' | '_' | ' ' | "'";
|
|
44
|
-
mode?: 'pasted' | 'auto';
|
|
45
|
-
|
|
44
|
+
mode?: 'pasted' | 'auto' | 'user-edited';
|
|
45
|
+
viewValueStates?: 'formatted'[];
|
|
46
46
|
postProcessors?: Map<string, NumberPostProcessor>;
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -1191,9 +1191,8 @@ export class OverlayController extends EventTarget {
|
|
|
1191
1191
|
const hasPressedInside =
|
|
1192
1192
|
event.composedPath().includes(this.contentNode) ||
|
|
1193
1193
|
deepContains(this.contentNode, /** @type {HTMLElement|ShadowRoot} */ (event.target));
|
|
1194
|
-
if (
|
|
1195
|
-
|
|
1196
|
-
}
|
|
1194
|
+
if (hasPressedInside) return;
|
|
1195
|
+
this.hide();
|
|
1197
1196
|
};
|
|
1198
1197
|
|
|
1199
1198
|
/**
|
|
@@ -1206,7 +1205,7 @@ export class OverlayController extends EventTarget {
|
|
|
1206
1205
|
if (this.invokerNode) {
|
|
1207
1206
|
this.invokerNode.addEventListener('keyup', this.__escKeyHandler);
|
|
1208
1207
|
}
|
|
1209
|
-
} else if (phase === 'hide') {
|
|
1208
|
+
} else if (phase === 'hide' || phase === 'teardown') {
|
|
1210
1209
|
this.contentNode.removeEventListener('keyup', this.__escKeyHandler);
|
|
1211
1210
|
if (this.invokerNode) {
|
|
1212
1211
|
this.invokerNode.removeEventListener('keyup', this.__escKeyHandler);
|
|
@@ -1221,7 +1220,7 @@ export class OverlayController extends EventTarget {
|
|
|
1221
1220
|
_handleHidesOnOutsideEsc({ phase }) {
|
|
1222
1221
|
if (phase === 'show') {
|
|
1223
1222
|
document.addEventListener('keyup', this.#outsideEscKeyHandler);
|
|
1224
|
-
} else if (phase === 'hide') {
|
|
1223
|
+
} else if (phase === 'hide' || phase === 'teardown') {
|
|
1225
1224
|
document.removeEventListener('keyup', this.#outsideEscKeyHandler);
|
|
1226
1225
|
}
|
|
1227
1226
|
}
|
|
@@ -24,6 +24,8 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
#hasSetup = false;
|
|
28
|
+
|
|
27
29
|
constructor() {
|
|
28
30
|
super();
|
|
29
31
|
/**
|
|
@@ -173,22 +175,23 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
*/
|
|
179
|
-
firstUpdated(changedProperties) {
|
|
180
|
-
super.firstUpdated(changedProperties);
|
|
178
|
+
connectedCallback() {
|
|
179
|
+
super.connectedCallback();
|
|
181
180
|
|
|
182
|
-
this.
|
|
181
|
+
this.updateComplete.then(() => {
|
|
182
|
+
if (this.#hasSetup) return;
|
|
183
|
+
this._setupOverlayCtrl();
|
|
184
|
+
this.#hasSetup = true;
|
|
185
|
+
});
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
async disconnectedCallback() {
|
|
186
189
|
super.disconnectedCallback();
|
|
187
190
|
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
if (await this._isPermanentlyDisconnected()) {
|
|
192
|
+
this._teardownOverlayCtrl();
|
|
193
|
+
this.#hasSetup = false;
|
|
194
|
+
}
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
/**
|
|
@@ -238,6 +241,8 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
238
241
|
|
|
239
242
|
/** @protected */
|
|
240
243
|
_setupOverlayCtrl() {
|
|
244
|
+
if (this._overlayCtrl) return;
|
|
245
|
+
|
|
241
246
|
/** @type {OverlayController} */
|
|
242
247
|
this._overlayCtrl = this._defineOverlay({
|
|
243
248
|
contentNode: this._overlayContentNode,
|
|
@@ -253,11 +258,12 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
253
258
|
|
|
254
259
|
/** @protected */
|
|
255
260
|
_teardownOverlayCtrl() {
|
|
261
|
+
if (!this._overlayCtrl) return;
|
|
262
|
+
|
|
256
263
|
this._teardownOpenCloseListeners();
|
|
257
264
|
this.__teardownSyncFromOverlayController();
|
|
258
265
|
|
|
259
|
-
/** @type {OverlayController} */
|
|
260
|
-
(this._overlayCtrl).teardown();
|
|
266
|
+
/** @type {OverlayController} */ (this._overlayCtrl).teardown();
|
|
261
267
|
}
|
|
262
268
|
|
|
263
269
|
/**
|
|
@@ -396,9 +402,9 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
396
402
|
* Before we decide to teardown, let's wait to see if we were not just moving nodes around.
|
|
397
403
|
* @return {Promise<boolean>}
|
|
398
404
|
*/
|
|
399
|
-
async
|
|
405
|
+
async _isPermanentlyDisconnected() {
|
|
400
406
|
await this.updateComplete;
|
|
401
|
-
return this.isConnected;
|
|
407
|
+
return !this.isConnected;
|
|
402
408
|
}
|
|
403
409
|
};
|
|
404
410
|
export const OverlayMixin = dedupeMixin(OverlayMixinImplementation);
|
|
@@ -103,12 +103,12 @@ const withLocalTestConfig = () =>
|
|
|
103
103
|
async function createNestedEscControllers(parentContent) {
|
|
104
104
|
const childContent = /** @type {HTMLDivElement} */ (parentContent.querySelector('div[id]'));
|
|
105
105
|
// Assert valid fixure
|
|
106
|
-
const
|
|
107
|
-
(parentContent.id
|
|
108
|
-
parentContent.id
|
|
109
|
-
(childContent.id
|
|
110
|
-
childContent.id
|
|
111
|
-
if (!
|
|
106
|
+
const isValidFixture =
|
|
107
|
+
(parentContent.id.startsWith('parent-overlay--hidesOnEsc') ||
|
|
108
|
+
parentContent.id.startsWith('parent-overlay--hidesOnOutsideEsc')) &&
|
|
109
|
+
(childContent.id.startsWith('child-overlay--hidesOnEsc') ||
|
|
110
|
+
childContent.id.startsWith('child-overlay--hidesOnOutsideEsc'));
|
|
111
|
+
if (!isValidFixture) {
|
|
112
112
|
throw new Error('Provide a valid fixture');
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -117,8 +117,8 @@ async function createNestedEscControllers(parentContent) {
|
|
|
117
117
|
shadowRootParent.appendChild(childContent);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const parentHasOutsideOnEsc = parentContent.id
|
|
121
|
-
const childHasOutsideOnEsc = childContent.id
|
|
120
|
+
const parentHasOutsideOnEsc = parentContent.id.startsWith('parent-overlay--hidesOnOutsideEsc');
|
|
121
|
+
const childHasOutsideOnEsc = childContent.id.startsWith('child-overlay--hidesOnOutsideEsc');
|
|
122
122
|
|
|
123
123
|
const parentConfig = parentHasOutsideOnEsc ? { hidesOnOutsideEsc: true } : { hidesOnEsc: true };
|
|
124
124
|
const childConfig = childHasOutsideOnEsc ? { hidesOnOutsideEsc: true } : { hidesOnEsc: true };
|
|
@@ -787,6 +787,9 @@ describe('OverlayController', () => {
|
|
|
787
787
|
|
|
788
788
|
expect(parentOverlay.isShown).to.be.false;
|
|
789
789
|
expect(childOverlay.isShown).to.be.true;
|
|
790
|
+
|
|
791
|
+
await childOverlay.teardown();
|
|
792
|
+
await parentOverlay.teardown();
|
|
790
793
|
});
|
|
791
794
|
|
|
792
795
|
it('on [Escape] press outside overlays: parent stays shown, child hides', async () => {
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
unsafeStatic,
|
|
3
|
+
nextFrame,
|
|
4
|
+
aTimeout,
|
|
5
|
+
defineCE,
|
|
6
|
+
fixture,
|
|
7
|
+
expect,
|
|
8
|
+
html,
|
|
9
|
+
} from '@open-wc/testing';
|
|
4
10
|
import { overlays as overlaysManager, OverlayController } from '@lion/ui/overlays.js';
|
|
5
|
-
|
|
6
|
-
import '@lion/ui/define/lion-dialog.js';
|
|
7
11
|
import { browserDetection } from '@lion/ui/core.js';
|
|
12
|
+
import { cache } from 'lit/directives/cache.js';
|
|
13
|
+
import '@lion/ui/define/lion-dialog.js';
|
|
14
|
+
import { LitElement } from 'lit';
|
|
15
|
+
import sinon from 'sinon';
|
|
8
16
|
|
|
9
17
|
/**
|
|
10
|
-
* @typedef {import('../types/OverlayConfig.js').OverlayConfig} OverlayConfig
|
|
11
18
|
* @typedef {import('../types/OverlayMixinTypes.js').DefineOverlayConfig} DefineOverlayConfig
|
|
12
|
-
* @typedef {import('../types/OverlayMixinTypes.js').OverlayHost} OverlayHost
|
|
13
|
-
* @typedef {import('../types/OverlayMixinTypes.js').OverlayMixin} OverlayMixin
|
|
14
|
-
* @typedef {import('lit').LitElement} LitElement
|
|
15
19
|
* @typedef {LitElement & OverlayHost & {_overlayCtrl:OverlayController}} OverlayEl
|
|
20
|
+
* @typedef {import('../types/OverlayMixinTypes.js').OverlayMixin} OverlayMixin
|
|
21
|
+
* @typedef {import('../types/OverlayMixinTypes.js').OverlayHost} OverlayHost
|
|
22
|
+
* @typedef {import('../types/OverlayConfig.js').OverlayConfig} OverlayConfig
|
|
16
23
|
*/
|
|
17
24
|
|
|
18
25
|
function getGlobalOverlayCtrls() {
|
|
@@ -331,6 +338,173 @@ export function runOverlayMixinSuite({ tagString, tag, suffix = '' }) {
|
|
|
331
338
|
}).to.not.throw;
|
|
332
339
|
stub.restore();
|
|
333
340
|
});
|
|
341
|
+
|
|
342
|
+
describe('Teardown', () => {
|
|
343
|
+
it('does not teardown when moving dom nodes', async () => {
|
|
344
|
+
const el = /** @type {OverlayEl} */ (
|
|
345
|
+
await fixture(html`
|
|
346
|
+
<${tag}>
|
|
347
|
+
<div slot="content">content</div>
|
|
348
|
+
<button slot="invoker">invoker button</button>
|
|
349
|
+
</${tag}>
|
|
350
|
+
`)
|
|
351
|
+
);
|
|
352
|
+
// @ts-expect-error [allow-protected] in tests
|
|
353
|
+
const teardownSpy = sinon.spy(el, '_teardownOverlayCtrl');
|
|
354
|
+
// @ts-expect-error [allow-protected] in tests
|
|
355
|
+
const spyDisconnected = sinon.spy(el, '_isPermanentlyDisconnected');
|
|
356
|
+
|
|
357
|
+
// move around in dom
|
|
358
|
+
const newParent = document.createElement('div');
|
|
359
|
+
document.body.appendChild(newParent);
|
|
360
|
+
newParent.appendChild(el);
|
|
361
|
+
expect(spyDisconnected.callCount).to.equal(1);
|
|
362
|
+
|
|
363
|
+
const otherParent = document.createElement('div');
|
|
364
|
+
document.body.appendChild(otherParent);
|
|
365
|
+
otherParent.appendChild(el);
|
|
366
|
+
await aTimeout(0);
|
|
367
|
+
|
|
368
|
+
expect(spyDisconnected.callCount).to.equal(2);
|
|
369
|
+
expect(teardownSpy.callCount).to.equal(0);
|
|
370
|
+
|
|
371
|
+
// simulate a permanent disconnect
|
|
372
|
+
otherParent.removeChild(el);
|
|
373
|
+
await aTimeout(0);
|
|
374
|
+
|
|
375
|
+
expect(teardownSpy.callCount).to.equal(1);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('allows to disconnect and reconnect later', async () => {
|
|
379
|
+
const el = /** @type {OverlayEl} */ (
|
|
380
|
+
await fixture(html`
|
|
381
|
+
<${tag}>
|
|
382
|
+
<div slot="content">content</div>
|
|
383
|
+
<button slot="invoker">invoker button</button>
|
|
384
|
+
</${tag}>
|
|
385
|
+
`)
|
|
386
|
+
);
|
|
387
|
+
// @ts-expect-error [allow-protected] in tests
|
|
388
|
+
const teardownSpy = sinon.spy(el, '_teardownOverlayCtrl');
|
|
389
|
+
// @ts-expect-error [allow-protected] in tests
|
|
390
|
+
const spyDisconnected = sinon.spy(el, '_isPermanentlyDisconnected');
|
|
391
|
+
// @ts-expect-error [allow-protected] in tests
|
|
392
|
+
const setupSpy = sinon.spy(el, '_setupOverlayCtrl');
|
|
393
|
+
|
|
394
|
+
// 'permanently' disconnect (although we will reconnect later)
|
|
395
|
+
const offlineParent = document.createElement('div');
|
|
396
|
+
offlineParent.appendChild(el);
|
|
397
|
+
await aTimeout(0);
|
|
398
|
+
|
|
399
|
+
expect(spyDisconnected.callCount).to.equal(1);
|
|
400
|
+
expect(teardownSpy.callCount).to.equal(1);
|
|
401
|
+
|
|
402
|
+
// reconnect
|
|
403
|
+
document.body.appendChild(offlineParent);
|
|
404
|
+
await aTimeout(0);
|
|
405
|
+
|
|
406
|
+
expect(setupSpy.callCount).to.equal(1);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('works with cache directive when reconnected', async () => {
|
|
410
|
+
class CachingContext extends LitElement {
|
|
411
|
+
static properties = {
|
|
412
|
+
switched: { type: Boolean },
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
constructor() {
|
|
416
|
+
super();
|
|
417
|
+
|
|
418
|
+
this.switched = false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
get overlayEl() {
|
|
422
|
+
return this.shadowRoot?.querySelector('#myOverlay');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
render() {
|
|
426
|
+
return html`${cache(
|
|
427
|
+
this.switched
|
|
428
|
+
? html`something else`
|
|
429
|
+
: html`<${tag} id="myOverlay">
|
|
430
|
+
<div slot="content">content b</div>
|
|
431
|
+
<button slot="invoker">invoker button b</button>
|
|
432
|
+
</${tag}>`,
|
|
433
|
+
)}`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const cachingTagName = defineCE(CachingContext);
|
|
438
|
+
const cachingTag = unsafeStatic(cachingTagName);
|
|
439
|
+
|
|
440
|
+
const el = /** @type {CachingContext} */ (
|
|
441
|
+
await fixture(html`
|
|
442
|
+
<${cachingTag}></${cachingTag}>
|
|
443
|
+
`)
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
// @ts-expect-error [allow-protected] in tests
|
|
447
|
+
const teardownSpy = sinon.spy(el.overlayEl, '_teardownOverlayCtrl');
|
|
448
|
+
// @ts-expect-error [allow-protected] in tests
|
|
449
|
+
const spyDisconnected = sinon.spy(el.overlayEl, '_isPermanentlyDisconnected');
|
|
450
|
+
// @ts-expect-error [allow-protected] in tests
|
|
451
|
+
const setupSpy = sinon.spy(el.overlayEl, '_setupOverlayCtrl');
|
|
452
|
+
|
|
453
|
+
el.switched = true;
|
|
454
|
+
// render the new content
|
|
455
|
+
await el.updateComplete;
|
|
456
|
+
// wait for the teardown to complete
|
|
457
|
+
await el.updateComplete;
|
|
458
|
+
|
|
459
|
+
expect(spyDisconnected.callCount).to.equal(1);
|
|
460
|
+
expect(teardownSpy.callCount).to.equal(1);
|
|
461
|
+
expect(setupSpy.callCount).to.equal(0);
|
|
462
|
+
|
|
463
|
+
el.switched = false;
|
|
464
|
+
await el.updateComplete;
|
|
465
|
+
|
|
466
|
+
expect(setupSpy.callCount).to.equal(1);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it('correctly removes event listeners when disconnected from dom', async () => {
|
|
470
|
+
const el = /** @type {OverlayEl} */ (
|
|
471
|
+
await fixture(html`
|
|
472
|
+
<${tag}>
|
|
473
|
+
<div slot="content">content</div>
|
|
474
|
+
<button slot="invoker">invoker button</button>
|
|
475
|
+
</${tag}>
|
|
476
|
+
`)
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
const eventRemoveSpy = sinon.spy(el._overlayCtrl, 'removeEventListener');
|
|
480
|
+
el.parentNode?.removeChild(el);
|
|
481
|
+
await el.updateComplete;
|
|
482
|
+
|
|
483
|
+
expect(eventRemoveSpy.callCount).to.equal(0);
|
|
484
|
+
|
|
485
|
+
await el.updateComplete;
|
|
486
|
+
const hasOneInstanceFor = (
|
|
487
|
+
/** @type {string} */ eventName,
|
|
488
|
+
/** @type {any[][]} */ eventsToSearch,
|
|
489
|
+
/** @type {string} */ methodToRemove,
|
|
490
|
+
) =>
|
|
491
|
+
Boolean(
|
|
492
|
+
eventsToSearch.filter(
|
|
493
|
+
([eventToSearch, methodToSearch]) =>
|
|
494
|
+
eventToSearch === eventName && methodToSearch === methodToRemove,
|
|
495
|
+
).length,
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// @ts-expect-error [allow-private] in tests
|
|
499
|
+
expect(hasOneInstanceFor('show', eventRemoveSpy.args, el.__onOverlayCtrlShow)).to.be.true;
|
|
500
|
+
// @ts-expect-error [allow-private] in tests
|
|
501
|
+
expect(hasOneInstanceFor('hide', eventRemoveSpy.args, el.__onOverlayCtrlHide)).to.be.true;
|
|
502
|
+
// @ts-expect-error [allow-private] in tests
|
|
503
|
+
expect(hasOneInstanceFor('before-show', eventRemoveSpy.args, el.__onBeforeShow)).to.be.true;
|
|
504
|
+
// @ts-expect-error [allow-private] in tests
|
|
505
|
+
expect(hasOneInstanceFor('before-hide', eventRemoveSpy.args, el.__onBeforeHide)).to.be.true;
|
|
506
|
+
});
|
|
507
|
+
});
|
|
334
508
|
});
|
|
335
509
|
|
|
336
510
|
describe(`OverlayMixin${suffix} nested`, () => {
|
|
@@ -388,6 +562,7 @@ export function runOverlayMixinSuite({ tagString, tag, suffix = '' }) {
|
|
|
388
562
|
const moveTarget = /** @type {OverlayEl} */ (await fixture('<div id="target"></div>'));
|
|
389
563
|
moveTarget.appendChild(el);
|
|
390
564
|
await el.updateComplete;
|
|
565
|
+
|
|
391
566
|
expect(getGlobalOverlayCtrls().length).to.equal(1);
|
|
392
567
|
}
|
|
393
568
|
});
|
|
@@ -4,25 +4,34 @@ import { Options as PopperOptions, State } from '@popperjs/core/lib/popper.js';
|
|
|
4
4
|
import { OverlayConfig } from './OverlayConfig.js';
|
|
5
5
|
|
|
6
6
|
export declare class ArrowHost {
|
|
7
|
-
static
|
|
7
|
+
static properties: {
|
|
8
8
|
hasArrow: {
|
|
9
9
|
type: BooleanConstructor;
|
|
10
10
|
reflect: boolean;
|
|
11
11
|
attribute: string;
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
|
+
|
|
14
15
|
hasArrow: boolean;
|
|
16
|
+
|
|
15
17
|
repositionComplete: Promise<void>;
|
|
16
18
|
|
|
17
|
-
static
|
|
19
|
+
static styles: CSSResultArray;
|
|
18
20
|
|
|
19
21
|
render(): TemplateResult;
|
|
22
|
+
|
|
20
23
|
protected _arrowTemplate(): TemplateResult;
|
|
24
|
+
|
|
21
25
|
protected _arrowNodeTemplate(): TemplateResult;
|
|
26
|
+
|
|
22
27
|
protected _defineOverlayConfig(): OverlayConfig;
|
|
28
|
+
|
|
23
29
|
protected _getPopperArrowConfig(popperConfigToExtendFrom: Partial<PopperOptions>): Partial<PopperOptions>;
|
|
30
|
+
|
|
24
31
|
private __setupRepositionCompletePromise(): void;
|
|
32
|
+
|
|
25
33
|
get _arrowNode(): Element | null;
|
|
34
|
+
|
|
26
35
|
private __syncFromPopperState(data: Partial<State>): void;
|
|
27
36
|
}
|
|
28
37
|
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { Options } from '@popperjs/core';
|
|
2
2
|
|
|
3
|
+
export type ViewportPlacement =
|
|
4
|
+
| 'center'
|
|
5
|
+
| 'top-left'
|
|
6
|
+
| 'top'
|
|
7
|
+
| 'top-right'
|
|
8
|
+
| 'right'
|
|
9
|
+
| 'bottom-right'
|
|
10
|
+
| 'bottom'
|
|
11
|
+
| 'bottom-left'
|
|
12
|
+
| 'left';
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export interface ViewportConfig {
|
|
16
|
+
placement: ViewportPlacement;
|
|
17
|
+
}
|
|
3
18
|
export interface OverlayConfig {
|
|
4
19
|
// Positioning
|
|
5
20
|
|
|
@@ -80,17 +95,4 @@ export interface OverlayConfig {
|
|
|
80
95
|
visibilityTriggerFunction?: Function;
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
export type ViewportPlacement =
|
|
84
|
-
| 'center'
|
|
85
|
-
| 'top-left'
|
|
86
|
-
| 'top'
|
|
87
|
-
| 'top-right'
|
|
88
|
-
| 'right'
|
|
89
|
-
| 'bottom-right'
|
|
90
|
-
| 'bottom'
|
|
91
|
-
| 'bottom-left'
|
|
92
|
-
| 'left';
|
|
93
98
|
|
|
94
|
-
export interface ViewportConfig {
|
|
95
|
-
placement: ViewportPlacement;
|
|
96
|
-
}
|
|
@@ -423,6 +423,7 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|
|
423
423
|
*/
|
|
424
424
|
_teardownOverlayCtrl() {
|
|
425
425
|
super._teardownOverlayCtrl();
|
|
426
|
+
|
|
426
427
|
this._overlayCtrl.removeEventListener('show', this.__overlayOnShow);
|
|
427
428
|
this._overlayCtrl.removeEventListener('before-show', this.__overlayBeforeShow);
|
|
428
429
|
this._overlayCtrl.removeEventListener('hide', this.__overlayOnHide);
|
|
@@ -434,11 +435,12 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|
|
434
435
|
* @protected
|
|
435
436
|
*/
|
|
436
437
|
async _alignInvokerWidth() {
|
|
438
|
+
await this.updateComplete;
|
|
439
|
+
|
|
437
440
|
if (!this._overlayCtrl?.content) {
|
|
438
441
|
return;
|
|
439
442
|
}
|
|
440
443
|
|
|
441
|
-
await this.updateComplete;
|
|
442
444
|
const initContentDisplay = this._overlayCtrl.content.style.display;
|
|
443
445
|
const initContentMinWidth = this._overlayCtrl.contentWrapperNode.style.minWidth;
|
|
444
446
|
const initContentWidth = this._overlayCtrl.contentWrapperNode.style.width;
|
|
@@ -7,6 +7,7 @@ import '@lion/ui/define/lion-select-rich.js';
|
|
|
7
7
|
import '@lion/ui/define/lion-listbox.js';
|
|
8
8
|
import '@lion/ui/define/lion-option.js';
|
|
9
9
|
import { LitElement } from 'lit';
|
|
10
|
+
import sinon from 'sinon';
|
|
10
11
|
import {
|
|
11
12
|
fixture as _fixture,
|
|
12
13
|
unsafeStatic,
|
|
@@ -711,6 +712,49 @@ describe('lion-select-rich', () => {
|
|
|
711
712
|
});
|
|
712
713
|
});
|
|
713
714
|
|
|
715
|
+
describe('Teardown', () => {
|
|
716
|
+
it('correctly removes event listeners when disconnected from dom', async () => {
|
|
717
|
+
const el = await fixture(html`
|
|
718
|
+
<lion-select-rich label="age">
|
|
719
|
+
<lion-option .choiceValue=${10}>Item 1</lion-option>
|
|
720
|
+
<lion-option .choiceValue=${20}>Item 2</lion-option>
|
|
721
|
+
</lion-select-rich>
|
|
722
|
+
`);
|
|
723
|
+
|
|
724
|
+
const { _overlayCtrl } = getSelectRichMembers(el);
|
|
725
|
+
const eventRemoveSpy = sinon.spy(_overlayCtrl, 'removeEventListener');
|
|
726
|
+
|
|
727
|
+
el.parentNode?.removeChild(el);
|
|
728
|
+
|
|
729
|
+
expect(eventRemoveSpy.callCount).to.equal(0);
|
|
730
|
+
|
|
731
|
+
await el.updateComplete;
|
|
732
|
+
expect(eventRemoveSpy.callCount).to.equal(0);
|
|
733
|
+
|
|
734
|
+
await el.updateComplete;
|
|
735
|
+
|
|
736
|
+
const hasOneInstanceFor = (
|
|
737
|
+
/** @type {string} */ eventName,
|
|
738
|
+
/** @type {any[][]} */ eventsToSearch,
|
|
739
|
+
/** @type {string} */ methodToRemove,
|
|
740
|
+
) =>
|
|
741
|
+
Boolean(
|
|
742
|
+
eventsToSearch.filter(
|
|
743
|
+
([eventToSearch, methodToSearch]) =>
|
|
744
|
+
eventToSearch === eventName && methodToSearch === methodToRemove,
|
|
745
|
+
).length,
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
// @ts-expect-error [allow-private] in tests
|
|
749
|
+
expect(hasOneInstanceFor('show', eventRemoveSpy.args, el.__overlayOnShow)).to.be.true;
|
|
750
|
+
// @ts-expect-error [allow-private] in tests
|
|
751
|
+
expect(hasOneInstanceFor('before-show', eventRemoveSpy.args, el.__overlayBeforeShow)).to.be
|
|
752
|
+
.true;
|
|
753
|
+
// @ts-expect-error [allow-private] in tests
|
|
754
|
+
expect(hasOneInstanceFor('hide', eventRemoveSpy.args, el.__overlayOnHide)).to.be.true;
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
|
|
714
758
|
describe('Subclassers', () => {
|
|
715
759
|
it('allows to override the type of overlay', async () => {
|
|
716
760
|
const mySelectTagString = defineCE(
|