@vollowx/seele 0.13.7 → 0.13.8

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