@lion/ui 0.8.4 → 0.8.6
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/core/src/ScopedElementsMixin.js +2 -2
- package/components/core/test/ScopedElementsMixin.test.js +34 -1
- package/components/core/test/SlotMixin.test.js +13 -28
- package/components/form-core/src/FormControlMixin.js +1 -1
- package/components/form-core/src/validate/ValidateMixin.js +9 -4
- package/components/form-core/src/validate/Validator.js +17 -3
- package/components/form-core/test/validate/Validator.test.js +8 -0
- package/components/form-core/test-suites/ValidateMixin.suite.js +7 -7
- package/components/overlays/src/OverlayController.js +25 -1
- package/components/overlays/src/OverlayMixin.js +17 -4
- package/components/overlays/src/utils/adopt-styles.js +3 -3
- package/components/overlays/test/OverlayController.test.js +33 -2
- package/custom-elements.json +582 -0
- package/dist-types/components/core/test/SlotMixin.test.d.ts.map +1 -1
- package/dist-types/components/form-core/src/validate/ValidateMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/src/validate/Validator.d.ts +16 -6
- package/dist-types/components/form-core/src/validate/Validator.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayController.d.ts +2 -0
- 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/tsconfig.tsbuildinfo +1 -1
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +1 -1
- 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/icon/use-cases.md +1 -1
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/overview.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/overview.md +1 -1
- package/docs/components/listbox/use-cases.md +1 -1
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select/use-cases.md +1 -1
- package/docs/components/select-rich/use-cases.md +1 -1
- package/docs/components/tabs/examples.md +1 -1
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/providence-analytics/overview.md +42 -139
- 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/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
- package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +0 -68
- package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +0 -111
- package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +0 -69
- package/docs/fundamentals/node-tools/providence-analytics/assets/_mermaid.svg.js +0 -1226
- package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +0 -22
|
@@ -89,8 +89,8 @@ const ScopedElementsMixinImplementation = superclass =>
|
|
|
89
89
|
*/
|
|
90
90
|
defineScopedElement(tagName, classToBeRegistered) {
|
|
91
91
|
const registeredClass = this.registry.get(tagName);
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
92
|
+
const isNewClassWithSameName = registeredClass && registeredClass !== classToBeRegistered;
|
|
93
|
+
if (!supportsScopedRegistry() && isNewClassWithSameName) {
|
|
94
94
|
// eslint-disable-next-line no-console
|
|
95
95
|
console.error(
|
|
96
96
|
[
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
} from '@lit-labs/testing/fixtures.js';
|
|
7
7
|
import { LitElement, html } from 'lit';
|
|
8
8
|
import sinon from 'sinon';
|
|
9
|
-
import { browserDetection } from '../src/browserDetection.js';
|
|
10
9
|
|
|
11
10
|
import { ScopedElementsMixin, supportsScopedRegistry } from '../src/ScopedElementsMixin.js';
|
|
11
|
+
import { browserDetection } from '../src/browserDetection.js';
|
|
12
12
|
|
|
13
13
|
const hasRealScopedRegistrySupport = supportsScopedRegistry();
|
|
14
14
|
const originalShadowRootProps = {
|
|
@@ -78,6 +78,8 @@ describe('ScopedElementsMixin', () => {
|
|
|
78
78
|
|
|
79
79
|
describe('When scoped registries are supported', () => {
|
|
80
80
|
it('registers elements on local registry', async () => {
|
|
81
|
+
if (!hasRealScopedRegistrySupport) return;
|
|
82
|
+
|
|
81
83
|
const ceDefineSpy = sinon.spy(customElements, 'define');
|
|
82
84
|
|
|
83
85
|
const el = /** @type {ScopedElementsHost} */ (
|
|
@@ -127,5 +129,36 @@ describe('ScopedElementsMixin', () => {
|
|
|
127
129
|
expect(ceDefineSpy.calledWith('scoped-elements-child-no-reg')).to.be.true;
|
|
128
130
|
ceDefineSpy.restore();
|
|
129
131
|
});
|
|
132
|
+
|
|
133
|
+
it('fails when different classes are registered under different name', async () => {
|
|
134
|
+
class ScopedElementsHostNoReg2 extends ScopedElementsMixin(LitElement) {
|
|
135
|
+
static scopedElements = { 'scoped-elements-child-no-reg': class extends HTMLElement {} };
|
|
136
|
+
|
|
137
|
+
render() {
|
|
138
|
+
return html`<scoped-elements-child-no-reg></scoped-elements-child-no-reg>`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
customElements.define('scoped-elements-host-no-reg-2', ScopedElementsHostNoReg2);
|
|
142
|
+
|
|
143
|
+
const errorSpy = sinon.spy(console, 'error');
|
|
144
|
+
/** @type {ScopedElementsHostNoReg2} */ (
|
|
145
|
+
await fixture(html`<scoped-elements-host-no-reg></scoped-elements-host-no-reg>`)
|
|
146
|
+
);
|
|
147
|
+
/** @type {ScopedElementsHostNoReg2} */ (
|
|
148
|
+
await fixture(html`<scoped-elements-host-no-reg-2></scoped-elements-host-no-reg-2>`)
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(errorSpy.args[0][0]).to.equal(
|
|
152
|
+
[
|
|
153
|
+
'You are trying to re-register the "scoped-elements-child-no-reg" custom element with a different class via ScopedElementsMixin.',
|
|
154
|
+
'This is only possible with a CustomElementRegistry.',
|
|
155
|
+
'Your browser does not support this feature so you will need to load a polyfill for it.',
|
|
156
|
+
'Load "@webcomponents/scoped-custom-element-registry" before you register ANY web component to the global customElements registry.',
|
|
157
|
+
'e.g. add "<script src="/node_modules/@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min.js"></script>" as your first script tag.',
|
|
158
|
+
'For more details you can visit https://open-wc.org/docs/development/scoped-elements/',
|
|
159
|
+
].join('\n'),
|
|
160
|
+
);
|
|
161
|
+
errorSpy.restore();
|
|
162
|
+
});
|
|
130
163
|
});
|
|
131
164
|
});
|
|
@@ -1,31 +1,14 @@
|
|
|
1
|
-
import sinon from 'sinon';
|
|
2
1
|
import { defineCE, expect, fixture, fixtureSync, unsafeStatic, html } from '@open-wc/testing';
|
|
3
|
-
import { ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
|
|
4
2
|
import { SlotMixin } from '@lion/ui/core.js';
|
|
5
3
|
import { LitElement } from 'lit';
|
|
4
|
+
import sinon from 'sinon';
|
|
5
|
+
|
|
6
|
+
import { ScopedElementsMixin, supportsScopedRegistry } from '../src/ScopedElementsMixin.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @typedef {import('../types/SlotMixinTypes.js').SlotHost} SlotHost
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
// @ts-ignore
|
|
12
|
-
const createElementNative = ShadowRoot.prototype.createElement;
|
|
13
|
-
function mockScopedRegistry() {
|
|
14
|
-
const outputObj = { createElementCallCount: 0 };
|
|
15
|
-
// @ts-expect-error wait for browser support
|
|
16
|
-
ShadowRoot.prototype.createElement = (tagName, options) => {
|
|
17
|
-
outputObj.createElementCallCount += 1;
|
|
18
|
-
// Return an element that lit can use as render target
|
|
19
|
-
return createElementNative(tagName, options);
|
|
20
|
-
};
|
|
21
|
-
return outputObj;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function unMockScopedRegistry() {
|
|
25
|
-
// @ts-expect-error wait for browser support
|
|
26
|
-
ShadowRoot.prototype.createElement = createElementNative;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
12
|
describe('SlotMixin', () => {
|
|
30
13
|
it('inserts provided element into light dom and sets slot', async () => {
|
|
31
14
|
const tag = defineCE(
|
|
@@ -568,8 +551,11 @@ describe('SlotMixin', () => {
|
|
|
568
551
|
});
|
|
569
552
|
|
|
570
553
|
describe('Scoped Registries', () => {
|
|
571
|
-
it('supports scoped elements when polyfill loaded', async () => {
|
|
572
|
-
|
|
554
|
+
it('supports scoped elements when scoped registries supported (or polyfill loaded)', async () => {
|
|
555
|
+
if (!supportsScopedRegistry()) return;
|
|
556
|
+
|
|
557
|
+
// @ts-expect-error
|
|
558
|
+
const createElSpy = sinon.spy(ShadowRoot.prototype, 'createElement');
|
|
573
559
|
|
|
574
560
|
class ScopedEl extends LitElement {}
|
|
575
561
|
|
|
@@ -600,14 +586,14 @@ describe('SlotMixin', () => {
|
|
|
600
586
|
const tag = unsafeStatic(tagName);
|
|
601
587
|
await fixture(html`<${tag}></${tag}>`);
|
|
602
588
|
|
|
603
|
-
expect(
|
|
589
|
+
expect(createElSpy.callCount).to.equal(1);
|
|
604
590
|
|
|
605
|
-
|
|
591
|
+
createElSpy.restore();
|
|
606
592
|
});
|
|
607
593
|
|
|
608
|
-
it('does not scope elements when polyfill not loaded', async () => {
|
|
609
|
-
|
|
610
|
-
|
|
594
|
+
it('does not scope elements when scoped registries not supported (or polyfill not loaded)', async () => {
|
|
595
|
+
if (supportsScopedRegistry()) return;
|
|
596
|
+
|
|
611
597
|
class ScopedEl extends LitElement {}
|
|
612
598
|
|
|
613
599
|
const tagName = defineCE(
|
|
@@ -645,7 +631,6 @@ describe('SlotMixin', () => {
|
|
|
645
631
|
|
|
646
632
|
document.body.removeChild(renderTarget);
|
|
647
633
|
docSpy.restore();
|
|
648
|
-
unMockScopedRegistry();
|
|
649
634
|
});
|
|
650
635
|
});
|
|
651
636
|
});
|
|
@@ -274,7 +274,7 @@ const FormControlMixinImplementation = superclass =>
|
|
|
274
274
|
super.updated(changedProperties);
|
|
275
275
|
|
|
276
276
|
if (changedProperties.has('disabled')) {
|
|
277
|
-
this._inputNode?.setAttribute('aria-disabled', this.disabled
|
|
277
|
+
this._inputNode?.setAttribute('aria-disabled', `${Boolean(this.disabled)}`);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
if (changedProperties.has('_ariaLabelledNodes')) {
|
|
@@ -10,10 +10,10 @@ import { SyncUpdatableMixin } from '../utils/SyncUpdatableMixin.js';
|
|
|
10
10
|
import { LionValidationFeedback } from './LionValidationFeedback.js';
|
|
11
11
|
import { ResultValidator as MetaValidator } from './ResultValidator.js';
|
|
12
12
|
import { Unparseable } from './Unparseable.js';
|
|
13
|
-
import { Validator } from './Validator.js';
|
|
14
13
|
import { Required } from './validators/Required.js';
|
|
15
14
|
import { FormControlMixin } from '../FormControlMixin.js';
|
|
16
|
-
|
|
15
|
+
// eslint-disable-next-line no-unused-vars
|
|
16
|
+
import { Validator } from './Validator.js';
|
|
17
17
|
// TODO: [v1] make all @readOnly => @readonly and actually make sure those values cannot be set
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -460,7 +460,9 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
460
460
|
|
|
461
461
|
if (isEmpty) {
|
|
462
462
|
const hasSingleValue = !(/** @type {* & ValidateHost} */ (this)._isFormOrFieldset);
|
|
463
|
-
const requiredValidator = this._allValidators.find(
|
|
463
|
+
const requiredValidator = this._allValidators.find(
|
|
464
|
+
v => /** @type {typeof Validator} */ (v.constructor)?.validatorName === 'Required',
|
|
465
|
+
);
|
|
464
466
|
if (requiredValidator) {
|
|
465
467
|
this.__syncValidationResult = [{ validator: requiredValidator, outcome: true }];
|
|
466
468
|
}
|
|
@@ -685,7 +687,10 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
685
687
|
}
|
|
686
688
|
|
|
687
689
|
for (const validatorToSetup of this._allValidators) {
|
|
688
|
-
|
|
690
|
+
// disable dot notation to avoid the renaming for the prop during build/minification
|
|
691
|
+
const validatorCtor = /** @type {typeof Validator} */ (validatorToSetup.constructor);
|
|
692
|
+
// eslint-disable-next-line dot-notation
|
|
693
|
+
if (validatorCtor['_$isValidator$'] === undefined) {
|
|
689
694
|
// throws in constructor are not visible to end user so we do both
|
|
690
695
|
const errorType = Array.isArray(validatorToSetup) ? 'array' : typeof validatorToSetup;
|
|
691
696
|
const errorMessage = `Validators array only accepts class instances of Validator. Type "${errorType}" found. This may be caused by having multiple installations of "@lion/ui/form-core.js".`;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {import('../../types/validate/index.js').FeedbackMessageData} FeedbackMessageData
|
|
3
|
-
* @typedef {import('../../types/validate/index.js').ValidatorParam} ValidatorParam
|
|
4
|
-
* @typedef {import('../../types/validate/index.js').ValidatorConfig} ValidatorConfig
|
|
5
3
|
* @typedef {import('../../types/validate/index.js').ValidatorOutcome} ValidatorOutcome
|
|
6
|
-
* @typedef {import('../../types/validate/index.js').
|
|
4
|
+
* @typedef {import('../../types/validate/index.js').ValidatorConfig} ValidatorConfig
|
|
5
|
+
* @typedef {import('../../types/validate/index.js').ValidatorParam} ValidatorParam
|
|
7
6
|
* @typedef {import('../../types/validate/index.js').ValidationType} ValidationType
|
|
7
|
+
* @typedef {import('../../types/validate/index.js').ValidatorName} ValidatorName
|
|
8
8
|
* @typedef {import('../FormControlMixin.js').FormControlHost} FormControlHost
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -32,8 +32,22 @@ export class Validator extends EventTarget {
|
|
|
32
32
|
* @type {ValidationType}
|
|
33
33
|
*/
|
|
34
34
|
this.type = config?.type || 'error';
|
|
35
|
+
/**
|
|
36
|
+
* Disable dot notation to avoid the renaming for the prop during build/minification
|
|
37
|
+
*/
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
/**
|
|
41
|
+
* This unique marker allows us to identify Validator instances across different lion versions.
|
|
42
|
+
* It's meant to be used as a replacement for an instanceof check.
|
|
43
|
+
*
|
|
44
|
+
* N.B. it's important to keep the bracket notation, as `static _$isValidator$ = true;` will
|
|
45
|
+
* be renamed during minification and that leads to different names across different
|
|
46
|
+
* lion versions.
|
|
47
|
+
* @type {boolean}
|
|
48
|
+
*/
|
|
49
|
+
static ['_$isValidator$'] = true;
|
|
50
|
+
|
|
37
51
|
/**
|
|
38
52
|
* The name under which validation results get registered. For convenience and predictability, this
|
|
39
53
|
* should always be the same as the constructor name (since it will be obfuscated in js builds,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable dot-notation */
|
|
1
2
|
import { LitElement } from 'lit';
|
|
2
3
|
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
|
3
4
|
import sinon from 'sinon';
|
|
@@ -187,6 +188,13 @@ describe('Validator', () => {
|
|
|
187
188
|
expect(disconnectSpy.calledWith(el)).to.equal(true);
|
|
188
189
|
});
|
|
189
190
|
|
|
191
|
+
it('adds static ["_$isValidator$"] property as a marker to identify the Validator class across different lion versions (as instanceof cannot be used)', async () => {
|
|
192
|
+
const myValidator = new Validator();
|
|
193
|
+
|
|
194
|
+
expect(myValidator.constructor['_$isValidator$']).to.exist;
|
|
195
|
+
expect(myValidator.constructor['_$isValidator$']).to.be.true;
|
|
196
|
+
});
|
|
197
|
+
|
|
190
198
|
describe('Types', () => {
|
|
191
199
|
it('has type "error" by default', async () => {
|
|
192
200
|
expect(new Validator().type).to.equal('error');
|
|
@@ -2,25 +2,25 @@ import { LitElement } from 'lit';
|
|
|
2
2
|
import { aTimeout, defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
|
3
3
|
import {
|
|
4
4
|
getFormControlMembers,
|
|
5
|
-
AlwaysInvalid,
|
|
6
|
-
AlwaysValid,
|
|
7
5
|
AsyncAlwaysInvalid,
|
|
8
6
|
AsyncAlwaysValid,
|
|
7
|
+
AlwaysInvalid,
|
|
8
|
+
AlwaysValid,
|
|
9
9
|
} from '@lion/ui/form-core-test-helpers.js';
|
|
10
10
|
import sinon from 'sinon';
|
|
11
11
|
import {
|
|
12
|
+
ResultValidator,
|
|
13
|
+
ValidateMixin,
|
|
12
14
|
EqualsLength,
|
|
15
|
+
Unparseable,
|
|
13
16
|
MaxLength,
|
|
14
17
|
MinLength,
|
|
15
|
-
Required,
|
|
16
|
-
ResultValidator,
|
|
17
|
-
Unparseable,
|
|
18
|
-
ValidateMixin,
|
|
19
18
|
Validator,
|
|
19
|
+
Required,
|
|
20
20
|
} from '@lion/ui/form-core.js';
|
|
21
21
|
|
|
22
|
-
import '@lion/ui/define/lion-field.js';
|
|
23
22
|
import '@lion/ui/define/lion-validation-feedback.js';
|
|
23
|
+
import '@lion/ui/define/lion-field.js';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* @typedef {import('@lion/ui/form-core.js').LionField} LionField
|
|
@@ -192,7 +192,6 @@ export class OverlayController extends EventTarget {
|
|
|
192
192
|
zIndex: 9999,
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
-
this.manager.add(this);
|
|
196
195
|
/** @protected */
|
|
197
196
|
this._contentId = `overlay-content--${Math.random().toString(36).slice(2, 10)}`;
|
|
198
197
|
/** @private */
|
|
@@ -475,6 +474,14 @@ export class OverlayController extends EventTarget {
|
|
|
475
474
|
this._init();
|
|
476
475
|
/** @private */
|
|
477
476
|
this.__elementToFocusAfterHide = undefined;
|
|
477
|
+
|
|
478
|
+
if (!this.#isRegisteredOnManager()) {
|
|
479
|
+
this.manager.add(this);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
#isRegisteredOnManager() {
|
|
484
|
+
return Boolean(this.manager.list.find(ctrl => this === ctrl));
|
|
478
485
|
}
|
|
479
486
|
|
|
480
487
|
/**
|
|
@@ -1285,6 +1292,14 @@ export class OverlayController extends EventTarget {
|
|
|
1285
1292
|
wasMouseUpInside = false;
|
|
1286
1293
|
});
|
|
1287
1294
|
};
|
|
1295
|
+
|
|
1296
|
+
/** @type {EventListenerOrEventListenerObject} */
|
|
1297
|
+
this.__onWindowBlur = () => {
|
|
1298
|
+
// When the current window loses the focus (clicking outside iframe) the overlay gets hidden
|
|
1299
|
+
setTimeout(() => {
|
|
1300
|
+
this.hide();
|
|
1301
|
+
});
|
|
1302
|
+
};
|
|
1288
1303
|
}
|
|
1289
1304
|
|
|
1290
1305
|
this.contentWrapperNode[addOrRemoveListener](
|
|
@@ -1321,6 +1336,11 @@ export class OverlayController extends EventTarget {
|
|
|
1321
1336
|
(this.__onDocumentMouseUp),
|
|
1322
1337
|
true,
|
|
1323
1338
|
);
|
|
1339
|
+
window[addOrRemoveListener](
|
|
1340
|
+
'blur',
|
|
1341
|
+
/** @type {EventListenerOrEventListenerObject} */
|
|
1342
|
+
(this.__onWindowBlur),
|
|
1343
|
+
);
|
|
1324
1344
|
}
|
|
1325
1345
|
|
|
1326
1346
|
/**
|
|
@@ -1341,6 +1361,10 @@ export class OverlayController extends EventTarget {
|
|
|
1341
1361
|
this.__handleOverlayStyles({ phase: 'teardown' });
|
|
1342
1362
|
this._handleFeatures({ phase: 'teardown' });
|
|
1343
1363
|
this.__wrappingDialogNode?.removeEventListener('cancel', this.__cancelHandler);
|
|
1364
|
+
|
|
1365
|
+
if (this.#isRegisteredOnManager()) {
|
|
1366
|
+
this.manager.remove(this);
|
|
1367
|
+
}
|
|
1344
1368
|
}
|
|
1345
1369
|
|
|
1346
1370
|
/** @private */
|
|
@@ -182,11 +182,13 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
182
182
|
this._setupOverlayCtrl();
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
disconnectedCallback() {
|
|
185
|
+
async disconnectedCallback() {
|
|
186
186
|
super.disconnectedCallback();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
|
|
188
|
+
if (!this._overlayCtrl) return;
|
|
189
|
+
if (await this.#isMovingInDom()) return;
|
|
190
|
+
|
|
191
|
+
this._teardownOverlayCtrl();
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
/**
|
|
@@ -253,6 +255,7 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
253
255
|
_teardownOverlayCtrl() {
|
|
254
256
|
this._teardownOpenCloseListeners();
|
|
255
257
|
this.__teardownSyncFromOverlayController();
|
|
258
|
+
|
|
256
259
|
/** @type {OverlayController} */
|
|
257
260
|
(this._overlayCtrl).teardown();
|
|
258
261
|
}
|
|
@@ -387,5 +390,15 @@ export const OverlayMixinImplementation = superclass =>
|
|
|
387
390
|
ctrl._popper.update();
|
|
388
391
|
}
|
|
389
392
|
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* When we're moving around in dom, disconnectedCallback gets called.
|
|
396
|
+
* Before we decide to teardown, let's wait to see if we were not just moving nodes around.
|
|
397
|
+
* @return {Promise<boolean>}
|
|
398
|
+
*/
|
|
399
|
+
async #isMovingInDom() {
|
|
400
|
+
await this.updateComplete;
|
|
401
|
+
return this.isConnected;
|
|
402
|
+
}
|
|
390
403
|
};
|
|
391
404
|
export const OverlayMixin = dedupeMixin(OverlayMixinImplementation);
|
|
@@ -25,7 +25,7 @@ export const _adoptStyleUtils = {
|
|
|
25
25
|
adoptStyles: undefined,
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
const styleCache = new
|
|
28
|
+
const styleCache = new WeakMap();
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* @param {CSSStyleSheet} cssStyleSheet
|
|
@@ -81,10 +81,10 @@ function adoptStyleWhenAdoptedStylesheetsNotSupported(
|
|
|
81
81
|
*/
|
|
82
82
|
function handleCache(renderRoot, style, { teardown = false } = {}) {
|
|
83
83
|
let haltFurtherExecution = false;
|
|
84
|
-
if (!styleCache.has(renderRoot)) {
|
|
84
|
+
if (renderRoot && !styleCache.has(renderRoot)) {
|
|
85
85
|
styleCache.set(renderRoot, []);
|
|
86
86
|
}
|
|
87
|
-
const addedStylesForRoot = styleCache.get(renderRoot);
|
|
87
|
+
const addedStylesForRoot = styleCache.get(renderRoot) ?? [];
|
|
88
88
|
const foundStyle = addedStylesForRoot.find(
|
|
89
89
|
(/** @type {import("lit").CSSResultOrNative} */ addedStyle) => style === addedStyle,
|
|
90
90
|
);
|
|
@@ -36,6 +36,14 @@ async function mimicEscapePress(element) {
|
|
|
36
36
|
await aTimeout(0);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @param {OverlayController} ctrlToFind
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function isRegisteredOnManager(ctrlToFind) {
|
|
44
|
+
return Boolean(ctrlToFind.manager?.list?.find(ctrl => ctrlToFind === ctrl));
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
/**
|
|
40
48
|
* Make sure that all browsers serialize html in a similar way
|
|
41
49
|
* (Firefox tends to output empty style attrs)
|
|
@@ -275,8 +283,18 @@ describe('OverlayController', () => {
|
|
|
275
283
|
});
|
|
276
284
|
});
|
|
277
285
|
|
|
278
|
-
// TODO: Add teardown feature tests
|
|
279
|
-
describe('Teardown', () => {
|
|
286
|
+
// TODO: Add more teardown feature tests
|
|
287
|
+
describe('Teardown', () => {
|
|
288
|
+
it('unregisters itself from overlayManager', async () => {
|
|
289
|
+
const ctrl = new OverlayController(withGlobalTestConfig());
|
|
290
|
+
|
|
291
|
+
expect(isRegisteredOnManager(ctrl)).to.be.true;
|
|
292
|
+
|
|
293
|
+
ctrl.teardown();
|
|
294
|
+
|
|
295
|
+
expect(isRegisteredOnManager(ctrl)).to.be.false;
|
|
296
|
+
});
|
|
297
|
+
});
|
|
280
298
|
|
|
281
299
|
describe('Node Configuration', () => {
|
|
282
300
|
describe('Content', async () => {
|
|
@@ -1092,6 +1110,19 @@ describe('OverlayController', () => {
|
|
|
1092
1110
|
|
|
1093
1111
|
expect(ctrl.isShown).to.be.true;
|
|
1094
1112
|
});
|
|
1113
|
+
|
|
1114
|
+
it('hides when window is blurred (useful for iframes)', async () => {
|
|
1115
|
+
const ctrl = new OverlayController({
|
|
1116
|
+
...withGlobalTestConfig(),
|
|
1117
|
+
hidesOnOutsideClick: true,
|
|
1118
|
+
});
|
|
1119
|
+
await ctrl.show();
|
|
1120
|
+
|
|
1121
|
+
window.dispatchEvent(new Event('blur'));
|
|
1122
|
+
await aTimeout(0);
|
|
1123
|
+
|
|
1124
|
+
expect(ctrl.isShown).to.be.false;
|
|
1125
|
+
});
|
|
1095
1126
|
});
|
|
1096
1127
|
|
|
1097
1128
|
describe('elementToFocusAfterHide', () => {
|