@vollowx/seele 0.13.7 → 0.13.9
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/README.md +33 -15
- package/package.json +2 -2
- package/src/all.d.ts +5 -0
- package/src/all.js +5 -0
- package/src/base/autocomplete.js +13 -13
- package/src/base/button.d.ts +4 -2
- package/src/base/button.js +15 -11
- package/src/base/checkbox.js +3 -8
- package/src/base/controllers/popover-controller.js +1 -1
- package/src/base/experimental/popup-styles.css.js +2 -0
- package/src/base/experimental/popup.d.ts +40 -0
- package/src/base/experimental/popup.js +278 -0
- package/src/base/mixins/attachable.d.ts +5 -2
- package/src/base/mixins/attachable.js +63 -23
- package/src/base/mixins/button-toggle-mixin.d.ts +7 -0
- package/src/base/mixins/button-toggle-mixin.js +101 -0
- package/src/base/mixins/internals-attached.d.ts +4 -0
- package/src/base/mixins/internals-attached.js +6 -0
- package/src/base/radio.d.ts +3 -0
- package/src/base/radio.js +28 -16
- package/src/base/tab-panel.d.ts +3 -3
- package/src/base/tab-panel.js +9 -11
- package/src/base/tab.d.ts +2 -8
- package/src/base/tab.js +16 -35
- package/src/base/tabs.d.ts +6 -6
- package/src/base/tabs.js +101 -99
- package/src/base/toggle-button.d.ts +6 -45
- package/src/base/toggle-button.js +6 -135
- package/src/base/tooltip.d.ts +2 -1
- package/src/base/tooltip.js +2 -2
- package/src/core/ensure-ready.d.ts +2 -1
- package/src/core/ensure-ready.js +12 -7
- package/src/m3/button/common-button-toggle.d.ts +4 -10
- package/src/m3/button/common-button-toggle.js +6 -40
- package/src/m3/button/icon-button-toggle.d.ts +4 -6
- package/src/m3/button/icon-button-toggle.js +6 -33
- package/src/m3/button/shared-button-styles.css.js +1 -1
- package/src/m3/button/shared-button-toggle-styles.css.js +1 -1
- package/src/m3/button-group/standard-button-group-styles.css.js +1 -1
- package/src/m3/button-group/standard-button-group.d.ts +2 -0
- package/src/m3/button-group/standard-button-group.js +44 -38
- package/src/m3/checkbox/checkbox-styles.css.js +1 -1
- package/src/m3/checkbox/checkbox.d.ts +1 -1
- package/src/m3/checkbox/checkbox.js +2 -5
- package/src/m3/dialog/dialog-styles.css.js +1 -1
- package/src/m3/dialog/dialog.d.ts +3 -1
- package/src/m3/dialog/dialog.js +6 -4
- package/src/m3/experimental/popup-styles.css.js +2 -0
- package/src/m3/experimental/popup.d.ts +9 -0
- package/src/m3/experimental/popup.js +11 -0
- package/src/m3/fab/fab-styles.css.js +1 -1
- package/src/m3/field/field-styles.css.js +1 -1
- package/src/m3/field/filled-field-styles.css.js +1 -1
- package/src/m3/focus-ring/focus-ring-styles.css.js +1 -1
- package/src/m3/focus-ring/focus-ring.d.ts +2 -1
- package/src/m3/focus-ring/focus-ring.js +4 -7
- package/src/m3/list/list-item-styles.css.js +1 -1
- package/src/m3/menu/menu-styles.css.js +1 -1
- package/src/m3/nagivation/navigation-rail-item-styles.css.js +2 -0
- package/src/m3/nagivation/navigation-rail-item.d.ts +21 -0
- package/src/m3/nagivation/navigation-rail-item.js +44 -0
- package/src/m3/nagivation/navigation-rail-styles.css.js +2 -0
- package/src/m3/nagivation/navigation-rail.d.ts +12 -0
- package/src/m3/nagivation/navigation-rail.js +17 -0
- package/src/m3/radio/radio-styles.css.js +1 -1
- package/src/m3/radio/radio.d.ts +3 -0
- package/src/m3/radio/radio.js +12 -4
- package/src/m3/ripple/ripple-styles.css.js +1 -1
- package/src/m3/ripple/ripple.d.ts +3 -13
- package/src/m3/ripple/ripple.js +28 -31
- package/src/m3/{system → styles}/defaults.css +1 -1
- package/src/m3/styles/motion-expressive.css +15 -0
- package/src/m3/styles/motion-standard.css +15 -0
- package/src/m3/styles/typography.css +24 -0
- package/src/m3/switch/switch-styles.css.js +1 -1
- package/src/m3/switch/switch.d.ts +4 -2
- package/src/m3/switch/switch.js +14 -12
- package/src/m3/tab/secondary-tab-styles.css.js +2 -0
- package/src/m3/tab/secondary-tab.d.ts +5 -0
- package/src/m3/tab/secondary-tab.js +15 -0
- package/src/m3/tab/tab-panel-styles.css.js +1 -1
- package/src/m3/tab/tab-styles.css.js +1 -1
- package/src/m3/tab/tab.d.ts +3 -0
- package/src/m3/tab/tab.js +68 -3
- package/src/m3/tab/tabs-styles.css.js +1 -1
- package/src/m3/tab/tabs.d.ts +1 -7
- package/src/m3/tab/tabs.js +6 -39
- package/src/m3/toolbar/toolbar-styles.css.js +1 -1
- package/src/win98/button/button-styles.css.js +1 -1
- package/src/m3/system/typography.css +0 -34
- /package/src/m3/{system → styles}/base.css +0 -0
- /package/src/m3/{system → styles}/elevation.css +0 -0
- /package/src/m3/{system → styles}/motion.d.ts +0 -0
- /package/src/m3/{system → styles}/motion.js +0 -0
package/README.md
CHANGED
|
@@ -33,12 +33,12 @@ What's the differences comparing to other projects?
|
|
|
33
33
|
|
|
34
34
|
## Getting started
|
|
35
35
|
|
|
36
|
-
Check out [
|
|
36
|
+
Check out [Installation](https://tideover.cc/seele/docs/#installation).
|
|
37
37
|
|
|
38
38
|
## Documentations
|
|
39
39
|
|
|
40
|
-
-
|
|
41
|
-
- [
|
|
40
|
+
- [Documentations](https://tideover.cc/seele/docs/), ([source](./docs/00-Overview.md))
|
|
41
|
+
- [Backup site](https://lucaz.srht.site/)
|
|
42
42
|
|
|
43
43
|
## Download
|
|
44
44
|
|
|
@@ -50,25 +50,34 @@ Check out [Overview](./docs/00-Overview.md).
|
|
|
50
50
|
|
|
51
51
|
seele relies on the following modern web standards:
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
| feature | baseline | Chromium | Firefox | Safari |
|
|
54
|
+
| --- | :---: | :---: | :---: | :---: |
|
|
55
|
+
| [`ElementInternals`][element-internals] | 2023 | - | - | - |
|
|
56
|
+
| [`:dir()`][:dir] | 2023 | 120 | - | - |
|
|
57
|
+
| [Constructable Stylesheets][constructable-stylesheets] | 2024 | - | 126 | - |
|
|
58
|
+
| [`:state()`][:state] | 2024 | 125 | 126 | - |
|
|
59
|
+
| [`aria*Element`][aria-active] and similar | 2025 | 135 | 136 | 16.4 |
|
|
60
|
+
| [`CSSNumericValue`][css-numeric-value] | not yet | - | Nightly | - |
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
_* `CSSNumericValue` is worked around currently so it does not raise the minimum
|
|
63
|
+
version of Firefox_
|
|
64
|
+
|
|
65
|
+
All of the above result in the following minimum requirements:
|
|
60
66
|
|
|
61
67
|
- Chromium: >= 135
|
|
62
68
|
- Firefox: >= 136
|
|
69
|
+
- Safari: >= 16.4
|
|
63
70
|
|
|
64
|
-
It is 2026 now, you don't really need to worry about this.
|
|
65
|
-
|
|
71
|
+
It is 2026 now, so you don't really need to worry about this. However, in the
|
|
72
|
+
future, the following web features might be used and require higher browser
|
|
73
|
+
versions:
|
|
66
74
|
|
|
67
|
-
- [
|
|
68
|
-
- [
|
|
69
|
-
- [`
|
|
75
|
+
- [Popover API][popover-api] — Baseline 2025, Safari 17, _will improve menu, popup, and combobox_
|
|
76
|
+
- [`@starting-style`][starting-style] — Baseline 2025, Safari 17.5
|
|
77
|
+
- [`anchor()`][anchor] — Baseline 2026, Firefox 147, Safari 26, _will remove the dependency `floating-dom`_
|
|
70
78
|
|
|
71
|
-
|
|
79
|
+
_* omitting a specific browser version means using that standard does not raise
|
|
80
|
+
the minimum required version of seele_
|
|
72
81
|
|
|
73
82
|
## Other Information
|
|
74
83
|
|
|
@@ -79,3 +88,12 @@ these following web features might be used and require higher browser versions:
|
|
|
79
88
|
[web-comps]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components
|
|
80
89
|
[apg-patterns]: https://www.w3.org/WAI/ARIA/apg/patterns/
|
|
81
90
|
[lit]: https://lit.dev/
|
|
91
|
+
[element-internals]: https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals
|
|
92
|
+
[:dir]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:dir
|
|
93
|
+
[constructable-stylesheets]: https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet
|
|
94
|
+
[:state]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:state
|
|
95
|
+
[aria-active]: https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/ariaActiveDescendantElement
|
|
96
|
+
[css-numeric-value]: https://developer.mozilla.org/en-US/docs/Web/API/CSSNumericValue
|
|
97
|
+
[popover-api]: https://developer.mozilla.org/en-US/docs/Web/API/Popover_API
|
|
98
|
+
[starting-style]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@starting-style
|
|
99
|
+
[anchor]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/anchor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vollowx/seele",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.9",
|
|
4
4
|
"description": "A extensible Web Components library, with Material Design 3 components pre-provided.",
|
|
5
5
|
"author": "vollowx",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"files": [
|
|
21
21
|
"src/**/*.js",
|
|
22
22
|
"src/**/*.d.ts",
|
|
23
|
-
"src/m3/
|
|
23
|
+
"src/m3/styles/*.css",
|
|
24
24
|
"custom-elements.json",
|
|
25
25
|
"LICENSE",
|
|
26
26
|
"README.md"
|
package/src/all.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ import './m3/list/list-item.js';
|
|
|
17
17
|
import './m3/loading-indicator/loading-indicator.js';
|
|
18
18
|
import './m3/menu/menu.js';
|
|
19
19
|
import './m3/menu/menu-item.js';
|
|
20
|
+
import './m3/nagivation/navigation-rail.js';
|
|
21
|
+
import './m3/nagivation/navigation-rail-item.js';
|
|
20
22
|
import './m3/radio/radio.js';
|
|
21
23
|
import './m3/ripple/ripple.js';
|
|
22
24
|
import './m3/select/filled-select.js';
|
|
@@ -26,9 +28,12 @@ import './m3/slider/slider.js';
|
|
|
26
28
|
import './m3/switch/switch.js';
|
|
27
29
|
import './m3/tab/tabs.js';
|
|
28
30
|
import './m3/tab/tab.js';
|
|
31
|
+
import './m3/tab/secondary-tab.js';
|
|
29
32
|
import './m3/tab/tab-panel.js';
|
|
30
33
|
import './m3/text-field/filled-text-field.js';
|
|
31
34
|
import './m3/text-field/outlined-text-field.js';
|
|
32
35
|
import './m3/toolbar/toolbar.js';
|
|
33
36
|
import './m3/tooltip/tooltip.js';
|
|
34
37
|
import './win98/button/button.js';
|
|
38
|
+
import './base/experimental/popup.js';
|
|
39
|
+
import './m3/experimental/popup.js';
|
package/src/all.js
CHANGED
|
@@ -17,6 +17,8 @@ import './m3/list/list-item.js';
|
|
|
17
17
|
import './m3/loading-indicator/loading-indicator.js';
|
|
18
18
|
import './m3/menu/menu.js';
|
|
19
19
|
import './m3/menu/menu-item.js';
|
|
20
|
+
import './m3/nagivation/navigation-rail.js';
|
|
21
|
+
import './m3/nagivation/navigation-rail-item.js';
|
|
20
22
|
import './m3/radio/radio.js';
|
|
21
23
|
import './m3/ripple/ripple.js';
|
|
22
24
|
import './m3/select/filled-select.js';
|
|
@@ -26,9 +28,12 @@ import './m3/slider/slider.js';
|
|
|
26
28
|
import './m3/switch/switch.js';
|
|
27
29
|
import './m3/tab/tabs.js';
|
|
28
30
|
import './m3/tab/tab.js';
|
|
31
|
+
import './m3/tab/secondary-tab.js';
|
|
29
32
|
import './m3/tab/tab-panel.js';
|
|
30
33
|
import './m3/text-field/filled-text-field.js';
|
|
31
34
|
import './m3/text-field/outlined-text-field.js';
|
|
32
35
|
import './m3/toolbar/toolbar.js';
|
|
33
36
|
import './m3/tooltip/tooltip.js';
|
|
34
37
|
import './win98/button/button.js';
|
|
38
|
+
import './base/experimental/popup.js';
|
|
39
|
+
import './m3/experimental/popup.js';
|
package/src/base/autocomplete.js
CHANGED
|
@@ -56,19 +56,19 @@ export class Autocomplete extends Base {
|
|
|
56
56
|
}
|
|
57
57
|
// TODO: handle multiple calls on this function, currently double-call
|
|
58
58
|
// disallowed
|
|
59
|
-
handleInputSlotChange() {
|
|
60
|
-
ensureReady(this.$input)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
async handleInputSlotChange() {
|
|
60
|
+
await ensureReady(this.$input);
|
|
61
|
+
const input = this.$input;
|
|
62
|
+
const $realInput = input.$inputOrTextarea;
|
|
63
|
+
$realInput.role = 'combobox';
|
|
64
|
+
$realInput.ariaExpanded = String(this.open);
|
|
65
|
+
$realInput.ariaHasPopup = 'listbox';
|
|
66
|
+
$realInput.ariaAutoComplete = this.mode;
|
|
67
|
+
$realInput.ariaControlsElements = [this.$menu];
|
|
68
|
+
input.addEventListener('input', this.handleInput.bind(this));
|
|
69
|
+
input.addEventListener('keydown', this.handleInputKeydown.bind(this));
|
|
70
|
+
input.addEventListener('click', () => (this.open = !this.open));
|
|
71
|
+
this.$menu.attach($realInput);
|
|
72
72
|
}
|
|
73
73
|
handleItemsSlotChange() {
|
|
74
74
|
// Initial filter based on current input value (if any)
|
package/src/base/button.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
+
import { updateInternals } from './mixins/internals-attached.js';
|
|
2
3
|
declare const Base: Constructor<import("./mixins/form-associated.js").FormAssociatedInterface> & Constructor<import("./mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
|
|
3
4
|
export declare class Button extends Base {
|
|
4
5
|
#private;
|
|
@@ -7,7 +8,8 @@ export declare class Button extends Base {
|
|
|
7
8
|
constructor();
|
|
8
9
|
connectedCallback(): void;
|
|
9
10
|
disconnectedCallback(): void;
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
updated(changed: Map<string, any>): void;
|
|
12
|
+
[updateInternals](): void;
|
|
13
|
+
_handleClick(e: Event): void;
|
|
12
14
|
}
|
|
13
15
|
export {};
|
package/src/base/button.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
3
|
import { property } from 'lit/decorators.js';
|
|
4
|
-
import { InternalsAttached, internals } from './mixins/internals-attached.js';
|
|
4
|
+
import { InternalsAttached, internals, updateInternals, } from './mixins/internals-attached.js';
|
|
5
5
|
import { FormAssociated } from './mixins/form-associated.js';
|
|
6
6
|
import { hiddenStyles } from './hidden-styles.css.js';
|
|
7
7
|
const Base = FormAssociated(InternalsAttached(LitElement));
|
|
@@ -17,20 +17,21 @@ export class Button extends Base {
|
|
|
17
17
|
e.stopPropagation();
|
|
18
18
|
if (e.key === 'Enter')
|
|
19
19
|
this.click();
|
|
20
|
+
else
|
|
21
|
+
this[internals].states.add('active');
|
|
22
|
+
// TODO: consider handle `pointerdown` and ditch native `:active` selector
|
|
23
|
+
// TODO: supporting `Enter` also requires listening on `focusout`
|
|
20
24
|
};
|
|
21
25
|
this.#handleKeyUp = (e) => {
|
|
22
26
|
if (e.key === ' ') {
|
|
27
|
+
this[internals].states.delete('active');
|
|
23
28
|
e.preventDefault();
|
|
24
29
|
e.stopPropagation();
|
|
25
30
|
this.click();
|
|
26
31
|
}
|
|
27
32
|
};
|
|
28
|
-
this.#handleClick = () => {
|
|
29
|
-
if (this.type !== 'button')
|
|
30
|
-
this[internals].form?.[this.type]();
|
|
31
|
-
};
|
|
32
33
|
this[internals].role = 'button';
|
|
33
|
-
this
|
|
34
|
+
this[updateInternals]();
|
|
34
35
|
// This is set to prevent a first-paint-time transition for styles like
|
|
35
36
|
// `border-radius` on `:host`. Also in `ToggleButton`.
|
|
36
37
|
// Double `requestAnimationFrame` is necessary since the callback inside is
|
|
@@ -41,26 +42,29 @@ export class Button extends Base {
|
|
|
41
42
|
super.connectedCallback();
|
|
42
43
|
this.addEventListener('keydown', this.#handleKeyDown);
|
|
43
44
|
this.addEventListener('keyup', this.#handleKeyUp);
|
|
44
|
-
this.addEventListener('click', this
|
|
45
|
+
this.addEventListener('click', this._handleClick.bind(this));
|
|
45
46
|
requestAnimationFrame(() => requestAnimationFrame(() => this.removeAttribute('notransition')));
|
|
46
47
|
}
|
|
47
48
|
disconnectedCallback() {
|
|
48
49
|
super.disconnectedCallback();
|
|
49
50
|
this.removeEventListener('keydown', this.#handleKeyDown);
|
|
50
51
|
this.removeEventListener('keyup', this.#handleKeyUp);
|
|
51
|
-
this.removeEventListener('click', this
|
|
52
|
+
this.removeEventListener('click', this._handleClick.bind(this));
|
|
52
53
|
}
|
|
53
54
|
updated(changed) {
|
|
54
55
|
if (changed.has('disabled'))
|
|
55
|
-
this
|
|
56
|
+
this[updateInternals]();
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
[updateInternals]() {
|
|
58
59
|
this.tabIndex = this.disabled ? -1 : 0;
|
|
59
60
|
this[internals].ariaDisabled = String(this.disabled);
|
|
60
61
|
}
|
|
61
62
|
#handleKeyDown;
|
|
62
63
|
#handleKeyUp;
|
|
63
|
-
|
|
64
|
+
_handleClick(e) {
|
|
65
|
+
if (this.type !== 'button')
|
|
66
|
+
this[internals].form?.[this.type]();
|
|
67
|
+
}
|
|
64
68
|
}
|
|
65
69
|
__decorate([
|
|
66
70
|
property({ reflect: true })
|
package/src/base/checkbox.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
3
|
import { property } from 'lit/decorators.js';
|
|
4
4
|
import { FormAssociated } from './mixins/form-associated.js';
|
|
5
|
-
import { InternalsAttached, internals } from './mixins/internals-attached.js';
|
|
5
|
+
import { InternalsAttached, internals, replaceStates, } from './mixins/internals-attached.js';
|
|
6
6
|
import { hiddenStyles } from './hidden-styles.css.js';
|
|
7
7
|
const PROPERTY_FROM_ARIA_CHECKED = {
|
|
8
8
|
true: 'checked',
|
|
@@ -63,9 +63,7 @@ export class Checkbox extends Base {
|
|
|
63
63
|
updateState() {
|
|
64
64
|
const prevAriaChecked = this[internals]
|
|
65
65
|
.ariaChecked;
|
|
66
|
-
this[
|
|
67
|
-
this[internals].states.delete('was-checked');
|
|
68
|
-
this[internals].states.delete('was-indeterminate');
|
|
66
|
+
this[replaceStates](['was-unchecked', 'was-checked', 'was-indeterminate'], []);
|
|
69
67
|
if (prevAriaChecked && PROPERTY_FROM_ARIA_CHECKED[prevAriaChecked]) {
|
|
70
68
|
this[internals].states.add(`was-${PROPERTY_FROM_ARIA_CHECKED[prevAriaChecked]}`);
|
|
71
69
|
}
|
|
@@ -76,10 +74,7 @@ export class Checkbox extends Base {
|
|
|
76
74
|
: 'false';
|
|
77
75
|
const currentAriaChecked = this[internals]
|
|
78
76
|
.ariaChecked;
|
|
79
|
-
this[
|
|
80
|
-
this[internals].states.delete('checked');
|
|
81
|
-
this[internals].states.delete('indeterminate');
|
|
82
|
-
this[internals].states.add(`${PROPERTY_FROM_ARIA_CHECKED[currentAriaChecked]}`);
|
|
77
|
+
this[replaceStates](['unchecked', 'checked', 'indeterminate'], [PROPERTY_FROM_ARIA_CHECKED[currentAriaChecked]]);
|
|
83
78
|
this.tabIndex = this.disabled ? -1 : 0;
|
|
84
79
|
this[internals].ariaDisabled = String(this.disabled);
|
|
85
80
|
this[internals].setFormValue(this.checked ? 'on' : null);
|
|
@@ -134,7 +134,7 @@ export class PopoverController {
|
|
|
134
134
|
this.cleanupAutoUpdate?.();
|
|
135
135
|
this.cleanupAutoUpdate = undefined;
|
|
136
136
|
}
|
|
137
|
-
reposition() {
|
|
137
|
+
async reposition() {
|
|
138
138
|
const trigger = this.config.trigger();
|
|
139
139
|
const popover = this.config.popover();
|
|
140
140
|
if (!trigger || !popover)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NOTE: demos at dev/pending.html
|
|
3
|
+
* NOTE: should apply writing style on event listeners to other files
|
|
4
|
+
* TODO: consider removing popoverController, but in case of tooltip, reserve
|
|
5
|
+
* for now
|
|
6
|
+
* TODO: hijack all `hidePopover()`s to allow more complex JS animations
|
|
7
|
+
* TODO: simpler Menu that just inherits List - do we really need a separated
|
|
8
|
+
* Menu? Why not just `<md-list role="presentation">`?
|
|
9
|
+
*
|
|
10
|
+
* Design: TO BE ADDED
|
|
11
|
+
*/
|
|
12
|
+
import { LitElement, PropertyValues } from 'lit';
|
|
13
|
+
import { Placement, Strategy } from '@floating-ui/dom';
|
|
14
|
+
import { autoAttachToParent, handleControlChange } from '../mixins/attachable.js';
|
|
15
|
+
export declare function lookForTabbable(root: Node): HTMLElement | null;
|
|
16
|
+
declare const Popup_base: Constructor<import("../mixins/attachable.js").AttachableInterface> & Constructor<import("../mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
|
|
17
|
+
export declare class Popup extends Popup_base {
|
|
18
|
+
#private;
|
|
19
|
+
open: boolean;
|
|
20
|
+
noFocusControl: boolean;
|
|
21
|
+
placement: Placement;
|
|
22
|
+
strategy: Strategy;
|
|
23
|
+
offset: number;
|
|
24
|
+
windowPadding: number;
|
|
25
|
+
static styles: any[];
|
|
26
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
27
|
+
constructor();
|
|
28
|
+
connectedCallback(): void;
|
|
29
|
+
disconnectedCallback(): void;
|
|
30
|
+
protected updated(changedProperties: PropertyValues<this>): void;
|
|
31
|
+
[autoAttachToParent]: boolean;
|
|
32
|
+
[handleControlChange](prev: HTMLElement | null, next: HTMLElement | null): void;
|
|
33
|
+
toggle(): Promise<void>;
|
|
34
|
+
show(): Promise<void>;
|
|
35
|
+
hide(): Promise<void>;
|
|
36
|
+
_show(): Promise<void>;
|
|
37
|
+
_hide(): Promise<void>;
|
|
38
|
+
_cleanup(): void;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NOTE: demos at dev/pending.html
|
|
3
|
+
* NOTE: should apply writing style on event listeners to other files
|
|
4
|
+
* TODO: consider removing popoverController, but in case of tooltip, reserve
|
|
5
|
+
* for now
|
|
6
|
+
* TODO: hijack all `hidePopover()`s to allow more complex JS animations
|
|
7
|
+
* TODO: simpler Menu that just inherits List - do we really need a separated
|
|
8
|
+
* Menu? Why not just `<md-list role="presentation">`?
|
|
9
|
+
*
|
|
10
|
+
* Design: TO BE ADDED
|
|
11
|
+
*/
|
|
12
|
+
var _a;
|
|
13
|
+
import { __decorate } from "tslib";
|
|
14
|
+
import { LitElement, html, isServer } from 'lit';
|
|
15
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
16
|
+
import { autoUpdate, computePosition, arrow, flip, offset, shift, } from '@floating-ui/dom';
|
|
17
|
+
import { InternalsAttached } from '../mixins/internals-attached.js';
|
|
18
|
+
import { Attachable, autoAttachToParent, handleControlChange, } from '../mixins/attachable.js';
|
|
19
|
+
import { transformOriginFromArrow } from '../controllers/popover-controller.js';
|
|
20
|
+
import { popupStyles } from './popup-styles.css.js';
|
|
21
|
+
// Returns the first tabbable element within an eleent
|
|
22
|
+
//
|
|
23
|
+
// TODO: reverse search?
|
|
24
|
+
export function lookForTabbable(root) {
|
|
25
|
+
const FOCUSABLE_SELECTOR = [
|
|
26
|
+
'a[href]',
|
|
27
|
+
'button',
|
|
28
|
+
'input',
|
|
29
|
+
'textarea',
|
|
30
|
+
'select',
|
|
31
|
+
'summary',
|
|
32
|
+
'[tabindex]',
|
|
33
|
+
'[contenteditable=true]',
|
|
34
|
+
].join(',');
|
|
35
|
+
const isTabbable = (el) => el.matches(FOCUSABLE_SELECTOR) &&
|
|
36
|
+
!el.hasAttribute('disabled') &&
|
|
37
|
+
el.getAttribute('tabindex') !== '-1';
|
|
38
|
+
if ('shadowRoot' in root && root.shadowRoot) {
|
|
39
|
+
const shadowResult = lookForTabbable(root.shadowRoot);
|
|
40
|
+
if (shadowResult)
|
|
41
|
+
return shadowResult;
|
|
42
|
+
}
|
|
43
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
|
44
|
+
acceptNode: (node) => {
|
|
45
|
+
const el = node;
|
|
46
|
+
return isTabbable(el) || el.shadowRoot
|
|
47
|
+
? NodeFilter.FILTER_ACCEPT
|
|
48
|
+
: NodeFilter.FILTER_SKIP;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
let currentNode = walker.nextNode();
|
|
52
|
+
while (currentNode) {
|
|
53
|
+
if (isTabbable(currentNode)) {
|
|
54
|
+
return currentNode;
|
|
55
|
+
}
|
|
56
|
+
if (currentNode.shadowRoot) {
|
|
57
|
+
const shadowFocusable = lookForTabbable(currentNode.shadowRoot);
|
|
58
|
+
if (shadowFocusable) {
|
|
59
|
+
return shadowFocusable;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
currentNode = walker.nextNode();
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
let Popup = class Popup extends Attachable(InternalsAttached(LitElement)) {
|
|
67
|
+
static { this.styles = [popupStyles]; }
|
|
68
|
+
render() {
|
|
69
|
+
return html `<slot></slot>`;
|
|
70
|
+
}
|
|
71
|
+
constructor() {
|
|
72
|
+
super();
|
|
73
|
+
this.open = false;
|
|
74
|
+
this.noFocusControl = false;
|
|
75
|
+
this.placement = 'bottom-start';
|
|
76
|
+
this.strategy = 'absolute';
|
|
77
|
+
this.offset = 0;
|
|
78
|
+
this.windowPadding = 8;
|
|
79
|
+
this[_a] = false;
|
|
80
|
+
this.#onRequestHide = () => {
|
|
81
|
+
this.hide();
|
|
82
|
+
};
|
|
83
|
+
// #allowHidePopover = false;
|
|
84
|
+
// #onBeforeToggle = (e: ToggleEvent) => {
|
|
85
|
+
// // Intercept browser light-dismiss (ESC, click outside) or external hidePopover() calls
|
|
86
|
+
// if (e.newState === 'closed' && !this.#allowHidePopover) {
|
|
87
|
+
// e.preventDefault();
|
|
88
|
+
// if (this.open) {
|
|
89
|
+
// this.hide();
|
|
90
|
+
// }
|
|
91
|
+
// }
|
|
92
|
+
// };
|
|
93
|
+
this.#onToggle = (e) => {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
if (e.newState === 'closed' && this.open)
|
|
96
|
+
this.hide();
|
|
97
|
+
};
|
|
98
|
+
this.#onFocusOut = (e) => {
|
|
99
|
+
// for `this` and `this.$control`
|
|
100
|
+
if (this.noFocusControl || !this.open)
|
|
101
|
+
return;
|
|
102
|
+
const target = e.relatedTarget;
|
|
103
|
+
if (target && (this.contains(target) || this.$control?.contains(target))) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (target) {
|
|
107
|
+
this.hide();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
requestAnimationFrame(() => {
|
|
111
|
+
if (!document.hasFocus())
|
|
112
|
+
this.hide();
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
this.#onTriggerClick = (e) => {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
this.toggle();
|
|
118
|
+
};
|
|
119
|
+
this.#dummyArrow = isServer ? null : document.createElement('div');
|
|
120
|
+
if (!this.hasAttribute('popover'))
|
|
121
|
+
this.setAttribute('popover', 'auto');
|
|
122
|
+
if (!isServer) {
|
|
123
|
+
this.addEventListener('request-popup-hide', this.#onRequestHide);
|
|
124
|
+
// this.addEventListener('beforetoggle', this.#onBeforeToggle);
|
|
125
|
+
this.addEventListener('toggle', this.#onToggle);
|
|
126
|
+
this.addEventListener('focusout', this.#onFocusOut);
|
|
127
|
+
}
|
|
128
|
+
this.setAttribute('notransition', '');
|
|
129
|
+
}
|
|
130
|
+
connectedCallback() {
|
|
131
|
+
super.connectedCallback();
|
|
132
|
+
requestAnimationFrame(() => requestAnimationFrame(() => this.removeAttribute('notransition')));
|
|
133
|
+
}
|
|
134
|
+
disconnectedCallback() {
|
|
135
|
+
this._cleanup();
|
|
136
|
+
super.disconnectedCallback();
|
|
137
|
+
}
|
|
138
|
+
updated(changedProperties) {
|
|
139
|
+
if (changedProperties.has('open')) {
|
|
140
|
+
if (this.open)
|
|
141
|
+
this._show();
|
|
142
|
+
else
|
|
143
|
+
this._hide();
|
|
144
|
+
this.#syncTriggerAria();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
[(_a = autoAttachToParent, handleControlChange)](prev, next) {
|
|
148
|
+
if (prev) {
|
|
149
|
+
prev.removeEventListener('click', this.#onTriggerClick);
|
|
150
|
+
prev.removeEventListener('focusout', this.#onFocusOut);
|
|
151
|
+
}
|
|
152
|
+
if (next) {
|
|
153
|
+
next.addEventListener('click', this.#onTriggerClick);
|
|
154
|
+
next.addEventListener('focusout', this.#onFocusOut);
|
|
155
|
+
next.ariaHasPopup = 'true';
|
|
156
|
+
next.ariaExpanded = String(this.open);
|
|
157
|
+
next.ariaControlsElements = [this];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
#onRequestHide;
|
|
161
|
+
// #allowHidePopover = false;
|
|
162
|
+
// #onBeforeToggle = (e: ToggleEvent) => {
|
|
163
|
+
// // Intercept browser light-dismiss (ESC, click outside) or external hidePopover() calls
|
|
164
|
+
// if (e.newState === 'closed' && !this.#allowHidePopover) {
|
|
165
|
+
// e.preventDefault();
|
|
166
|
+
// if (this.open) {
|
|
167
|
+
// this.hide();
|
|
168
|
+
// }
|
|
169
|
+
// }
|
|
170
|
+
// };
|
|
171
|
+
#onToggle;
|
|
172
|
+
#onFocusOut;
|
|
173
|
+
#onTriggerClick;
|
|
174
|
+
#syncTriggerAria() {
|
|
175
|
+
this.$control?.setAttribute('aria-expanded', String(this.open));
|
|
176
|
+
}
|
|
177
|
+
#focusFirstInteractiveElement() {
|
|
178
|
+
if (this.noFocusControl)
|
|
179
|
+
return;
|
|
180
|
+
const autoFocusEl = this.querySelector('[autofocus]');
|
|
181
|
+
if (autoFocusEl) {
|
|
182
|
+
autoFocusEl.focus();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const firstFocusable = lookForTabbable(this);
|
|
186
|
+
if (firstFocusable) {
|
|
187
|
+
firstFocusable.focus();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async toggle() {
|
|
191
|
+
this.open = !this.open;
|
|
192
|
+
}
|
|
193
|
+
async show() {
|
|
194
|
+
this.open = true;
|
|
195
|
+
}
|
|
196
|
+
async hide() {
|
|
197
|
+
this.open = false;
|
|
198
|
+
}
|
|
199
|
+
async _show() {
|
|
200
|
+
if (this.isConnected && !this.matches(':popover-open'))
|
|
201
|
+
this.showPopover();
|
|
202
|
+
const trigger = this.$control;
|
|
203
|
+
if (trigger) {
|
|
204
|
+
this._cleanup();
|
|
205
|
+
this.#cleanupAutoUpdate = autoUpdate(trigger, this, () => this.#reposition());
|
|
206
|
+
await this.#reposition();
|
|
207
|
+
}
|
|
208
|
+
// await this._showing();
|
|
209
|
+
requestAnimationFrame(() => {
|
|
210
|
+
if (this.open)
|
|
211
|
+
this.#focusFirstInteractiveElement();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async _hide() {
|
|
215
|
+
// await this._hiding();
|
|
216
|
+
this._cleanup();
|
|
217
|
+
if (this.matches(':popover-open'))
|
|
218
|
+
this.hidePopover();
|
|
219
|
+
// if (this.matches(':popover-open')) {
|
|
220
|
+
// this.#allowHidePopover = true;
|
|
221
|
+
// try {
|
|
222
|
+
// this.hidePopover();
|
|
223
|
+
// } finally {
|
|
224
|
+
// this.#allowHidePopover = false;
|
|
225
|
+
// }
|
|
226
|
+
// }
|
|
227
|
+
}
|
|
228
|
+
// async _showing(): Promise<void> {}
|
|
229
|
+
// async _hiding(): Promise<void> {}
|
|
230
|
+
#cleanupAutoUpdate;
|
|
231
|
+
#dummyArrow;
|
|
232
|
+
async #reposition() {
|
|
233
|
+
const trigger = this.$control;
|
|
234
|
+
if (!trigger)
|
|
235
|
+
return;
|
|
236
|
+
const { x, y, placement, middlewareData } = await computePosition(trigger, this, {
|
|
237
|
+
placement: this.placement,
|
|
238
|
+
strategy: this.strategy,
|
|
239
|
+
middleware: [
|
|
240
|
+
offset(this.offset),
|
|
241
|
+
flip({ padding: this.windowPadding }),
|
|
242
|
+
shift({ padding: this.windowPadding, crossAxis: true }),
|
|
243
|
+
arrow({ element: this.#dummyArrow }),
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
Object.assign(this.style, {
|
|
247
|
+
left: `${x}px`,
|
|
248
|
+
top: `${y}px`,
|
|
249
|
+
transformOrigin: transformOriginFromArrow(placement, middlewareData.arrow),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
_cleanup() {
|
|
253
|
+
this.#cleanupAutoUpdate?.();
|
|
254
|
+
this.#cleanupAutoUpdate = undefined;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
__decorate([
|
|
258
|
+
property({ type: Boolean, reflect: true })
|
|
259
|
+
], Popup.prototype, "open", void 0);
|
|
260
|
+
__decorate([
|
|
261
|
+
property({ type: Boolean, attribute: 'no-focus-control' })
|
|
262
|
+
], Popup.prototype, "noFocusControl", void 0);
|
|
263
|
+
__decorate([
|
|
264
|
+
property({ type: String })
|
|
265
|
+
], Popup.prototype, "placement", void 0);
|
|
266
|
+
__decorate([
|
|
267
|
+
property({ type: String })
|
|
268
|
+
], Popup.prototype, "strategy", void 0);
|
|
269
|
+
__decorate([
|
|
270
|
+
property({ type: Number })
|
|
271
|
+
], Popup.prototype, "offset", void 0);
|
|
272
|
+
__decorate([
|
|
273
|
+
property({ type: Number })
|
|
274
|
+
], Popup.prototype, "windowPadding", void 0);
|
|
275
|
+
Popup = __decorate([
|
|
276
|
+
customElement('complementary-popup')
|
|
277
|
+
], Popup);
|
|
278
|
+
export { Popup };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
+
export declare const autoAttachToParent: unique symbol;
|
|
3
|
+
export declare const handleControlChange: unique symbol;
|
|
2
4
|
export declare class AttachableInterface {
|
|
3
|
-
$control: HTMLElement;
|
|
5
|
+
$control: HTMLElement | null;
|
|
4
6
|
htmlFor: string | null;
|
|
5
7
|
attach(control: HTMLElement, force?: boolean): void;
|
|
6
8
|
detach(): void;
|
|
7
|
-
|
|
9
|
+
[autoAttachToParent]: boolean;
|
|
10
|
+
[handleControlChange](prev: HTMLElement | null, next: HTMLElement | null): void;
|
|
8
11
|
}
|
|
9
12
|
export declare const Attachable: <T extends Constructor<LitElement>>(superClass: T) => Constructor<AttachableInterface> & T;
|