@vollowx/seele 0.13.8 → 0.13.10

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 (68) hide show
  1. package/README.md +3 -4
  2. package/package.json +2 -2
  3. package/src/all.d.ts +3 -0
  4. package/src/all.js +3 -0
  5. package/src/base/autocomplete.js +13 -13
  6. package/src/base/button.d.ts +1 -1
  7. package/src/base/button.js +1 -2
  8. package/src/base/experimental/popup.d.ts +3 -1
  9. package/src/base/experimental/popup.js +4 -2
  10. package/src/base/mixins/attachable.d.ts +5 -2
  11. package/src/base/mixins/attachable.js +63 -23
  12. package/src/base/mixins/button-toggle-mixin.js +1 -2
  13. package/src/base/mixins/internals-attached.d.ts +2 -0
  14. package/src/base/mixins/internals-attached.js +1 -0
  15. package/src/base/radio.d.ts +3 -0
  16. package/src/base/radio.js +28 -16
  17. package/src/base/tab-panel.d.ts +3 -3
  18. package/src/base/tab-panel.js +9 -11
  19. package/src/base/tab.d.ts +2 -8
  20. package/src/base/tab.js +16 -35
  21. package/src/base/tabs.d.ts +6 -6
  22. package/src/base/tabs.js +101 -99
  23. package/src/base/tooltip.d.ts +2 -1
  24. package/src/base/tooltip.js +2 -2
  25. package/src/core/ensure-ready.d.ts +2 -1
  26. package/src/core/ensure-ready.js +12 -7
  27. package/src/m3/button/shared-button-styles.css.js +1 -1
  28. package/src/m3/button-group/standard-button-group-styles.css.js +1 -1
  29. package/src/m3/button-group/standard-button-group.d.ts +2 -0
  30. package/src/m3/button-group/standard-button-group.js +44 -38
  31. package/src/m3/checkbox/checkbox.d.ts +1 -1
  32. package/src/m3/checkbox/checkbox.js +2 -5
  33. package/src/m3/dialog/dialog.d.ts +3 -1
  34. package/src/m3/dialog/dialog.js +6 -4
  35. package/src/m3/focus-ring/focus-ring.d.ts +2 -1
  36. package/src/m3/focus-ring/focus-ring.js +4 -7
  37. package/src/m3/navigation/navigation-rail-item-styles.css.js +2 -0
  38. package/src/m3/navigation/navigation-rail-item.d.ts +21 -0
  39. package/src/m3/navigation/navigation-rail-item.js +44 -0
  40. package/src/m3/navigation/navigation-rail-styles.css.js +2 -0
  41. package/src/m3/navigation/navigation-rail.d.ts +12 -0
  42. package/src/m3/navigation/navigation-rail.js +17 -0
  43. package/src/m3/radio/radio-styles.css.js +1 -1
  44. package/src/m3/radio/radio.d.ts +3 -0
  45. package/src/m3/radio/radio.js +12 -4
  46. package/src/m3/ripple/ripple.d.ts +2 -1
  47. package/src/m3/ripple/ripple.js +20 -22
  48. package/src/m3/{system → styles}/defaults.css +1 -1
  49. package/src/m3/styles/motion-expressive.css +15 -0
  50. package/src/m3/styles/motion-standard.css +15 -0
  51. package/src/m3/styles/typography.css +24 -0
  52. package/src/m3/switch/switch.d.ts +1 -0
  53. package/src/m3/switch/switch.js +4 -3
  54. package/src/m3/tab/secondary-tab-styles.css.js +2 -0
  55. package/src/m3/tab/secondary-tab.d.ts +5 -0
  56. package/src/m3/tab/secondary-tab.js +15 -0
  57. package/src/m3/tab/tab-panel-styles.css.js +1 -1
  58. package/src/m3/tab/tab-styles.css.js +1 -1
  59. package/src/m3/tab/tab.d.ts +3 -0
  60. package/src/m3/tab/tab.js +68 -3
  61. package/src/m3/tab/tabs-styles.css.js +1 -1
  62. package/src/m3/tab/tabs.d.ts +1 -7
  63. package/src/m3/tab/tabs.js +6 -39
  64. package/src/m3/system/typography.css +0 -34
  65. /package/src/m3/{system → styles}/base.css +0 -0
  66. /package/src/m3/{system → styles}/elevation.css +0 -0
  67. /package/src/m3/{system → styles}/motion.d.ts +0 -0
  68. /package/src/m3/{system → styles}/motion.js +0 -0
package/src/base/tabs.js CHANGED
@@ -1,133 +1,138 @@
1
1
  import { __decorate } from "tslib";
2
- import { LitElement, html } from 'lit';
3
- import { property, queryAssignedElements } from 'lit/decorators.js';
2
+ import { LitElement, html, isServer } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
4
  import { internals } from './mixins/internals-attached.js';
5
5
  /**
6
- * @fires {CustomEvent} tab-select
6
+ * @fires {CustomEvent} select
7
7
  */
8
8
  export class Tabs extends LitElement {
9
- constructor() {
10
- super(...arguments);
11
- this._role = 'tablist';
12
- this.switch = 'manual';
13
- this.selected = '';
14
- this.#handleSlotChange = (_) => {
15
- const parent = this.$parent;
16
- this.$tabs.forEach((tab) => {
17
- const panel = parent.querySelector(`[seele-base="tab"][value="${tab.value}"]`);
18
- if (!panel)
19
- console.error('[seele] cannot find a matching panel for tab ', tab);
20
- // TODO: ariaLabelledByElements
21
- tab.setAttribute('aria-controls', panel.id);
22
- panel.setAttribute('aria-labelledby', tab.id);
23
- });
24
- };
25
- this.#handleKeyDown = (event) => {
26
- const tabs = this.$tabs;
27
- if (!tabs.length)
28
- return;
29
- const activeElement = this.getRootNode()
30
- .activeElement;
31
- const currentIndex = tabs.findIndex((t) => t === activeElement || t.focused);
32
- let nextIndex = currentIndex !== -1 ? currentIndex : 0;
33
- let handled = false;
34
- switch (event.key) {
35
- case 'ArrowLeft':
36
- nextIndex = currentIndex <= 0 ? tabs.length - 1 : currentIndex - 1;
37
- handled = true;
38
- break;
39
- case 'ArrowRight':
40
- nextIndex = currentIndex >= tabs.length - 1 ? 0 : currentIndex + 1;
41
- handled = true;
42
- break;
43
- case 'Home':
44
- nextIndex = 0;
45
- handled = true;
46
- break;
47
- case 'End':
48
- nextIndex = tabs.length - 1;
49
- handled = true;
50
- break;
51
- case 'Enter':
52
- case ' ':
53
- if (currentIndex !== -1) {
54
- this.selectTab(tabs[currentIndex]);
55
- }
56
- handled = true;
57
- break;
58
- }
59
- if (handled) {
60
- event.preventDefault();
61
- event.stopPropagation();
62
- if (event.key !== 'Enter' && event.key !== ' ') {
63
- this.focusTab(tabs[nextIndex]);
64
- if (this.switch === 'auto')
65
- this.selectTab(tabs[nextIndex]);
66
- }
67
- }
68
- };
69
- this.#handleClick = (event) => {
70
- const target = event.target;
71
- const tab = target.closest('md-tab');
72
- if (tab && this.$tabs.includes(tab)) {
73
- this.focusTab(tab);
74
- this.selectTab(tab);
75
- }
76
- };
77
- }
78
9
  get $tabs() {
79
- return this.$slotItems.filter((item) => item[internals].role === 'tab');
10
+ return [...this.querySelectorAll('[seele-base=tab]')];
80
11
  }
81
- get $parent() {
82
- return this.parentNode instanceof ShadowRoot
83
- ? this.parentNode.host
84
- : this.parentElement;
12
+ get $panels() {
13
+ return [
14
+ ...this.querySelectorAll('[seele-base=tabpanel]'),
15
+ ];
85
16
  }
86
17
  render() {
87
- return html `<div part="tablist" role="tablist"><slot @slotchange="${this.#handleSlotChange}"></slot></div>`;
18
+ return html `<div part="tablist" role="tablist"><slot @slotchange="${this.#handleSlotChange}"></slot></div><slot name="panels"></slot>`;
19
+ }
20
+ constructor() {
21
+ super();
22
+ this.switch = 'manual';
23
+ this.selected = '';
24
+ if (!isServer) {
25
+ this.addEventListener('keydown', this.#handleKeyDown.bind(this));
26
+ this.addEventListener('click', this.#handleClick.bind(this));
27
+ }
88
28
  }
89
29
  connectedCallback() {
90
30
  super.connectedCallback();
91
- this.addEventListener('keydown', this.#handleKeyDown.bind(this));
92
- this.addEventListener('click', this.#handleClick.bind(this));
31
+ queueMicrotask(this.#handleSlotChange.bind(this));
93
32
  }
94
33
  updated(changed) {
95
34
  if (changed.has('selected') && this.selected) {
96
35
  const targetTab = this.$tabs.find((t) => t.value === this.selected);
97
- if (targetTab && !targetTab.selected) {
98
- this.selectTab(targetTab);
36
+ if (targetTab)
37
+ this._selectTab(targetTab);
38
+ }
39
+ }
40
+ #handleSlotChange() {
41
+ this.$tabs.forEach((tab) => {
42
+ const panel = this.$panels.find((panel) => panel.value === tab.value);
43
+ if (!panel) {
44
+ console.error('[seele] Cannot find a matching panel for tab ', tab);
45
+ }
46
+ else {
47
+ tab[internals].ariaControlsElements = [panel];
48
+ panel[internals].ariaLabelledByElements = [tab];
99
49
  }
50
+ });
51
+ }
52
+ #handleKeyDown(event) {
53
+ const tabs = this.$tabs.filter((t) => !t.disabled);
54
+ if (!tabs.length)
55
+ return;
56
+ const activeElement = this.getRootNode()
57
+ .activeElement;
58
+ const currentIndex = tabs.findIndex((t) => t === activeElement);
59
+ let nextIndex = currentIndex !== -1 ? currentIndex : 0;
60
+ let handled = false;
61
+ switch (event.key) {
62
+ case 'ArrowLeft':
63
+ nextIndex = currentIndex <= 0 ? tabs.length - 1 : currentIndex - 1;
64
+ handled = true;
65
+ break;
66
+ case 'ArrowRight':
67
+ nextIndex = currentIndex >= tabs.length - 1 ? 0 : currentIndex + 1;
68
+ handled = true;
69
+ break;
70
+ case 'Home':
71
+ nextIndex = 0;
72
+ handled = true;
73
+ break;
74
+ case 'End':
75
+ nextIndex = tabs.length - 1;
76
+ handled = true;
77
+ break;
78
+ case 'Enter':
79
+ case ' ':
80
+ if (currentIndex !== -1) {
81
+ this._selectTab(tabs[currentIndex]);
82
+ }
83
+ handled = true;
84
+ break;
85
+ }
86
+ if (handled) {
87
+ event.preventDefault();
88
+ event.stopPropagation();
89
+ if (event.key !== 'Enter' && event.key !== ' ') {
90
+ this.focusTab(tabs[nextIndex]);
91
+ if (this.switch === 'auto')
92
+ this._selectTab(tabs[nextIndex], true);
93
+ }
94
+ }
95
+ }
96
+ #handleClick(event) {
97
+ const target = event.target;
98
+ const tab = target.closest('[seele-base=tab]');
99
+ if (tab && !tab.disabled && this.$tabs.includes(tab)) {
100
+ this.focusTab(tab);
101
+ this._selectTab(tab, true);
100
102
  }
101
103
  }
102
- #handleSlotChange;
103
- #handleKeyDown;
104
- #handleClick;
105
104
  focusTab(tab) {
106
105
  this.$tabs.forEach((t) => {
107
106
  t.blur();
108
107
  t.tabIndex = -1;
109
108
  });
110
109
  tab.tabIndex = 0;
111
- tab.focus();
110
+ // TODO: prevent scrolling in other containers
111
+ tab.scrollIntoView({
112
+ block: 'nearest',
113
+ inline: 'nearest',
114
+ behavior: 'smooth',
115
+ });
116
+ tab.focus({ preventScroll: true });
112
117
  }
113
- selectTab(selectedTab) {
118
+ _selectTab(selectedTab, dispatchEvent = false) {
114
119
  this.selected = selectedTab.value;
115
- const parent = this.$parent;
116
- const panels = Array.from(parent.querySelectorAll('[seele-base="tabpanel"]'));
120
+ const panels = this.$panels;
117
121
  this.$tabs.forEach((tab) => {
118
122
  tab.selected = tab === selectedTab;
119
- tab.tabIndex = tab === selectedTab ? 0 : -1;
123
+ tab.tabIndex = tab === selectedTab && !tab.disabled ? 0 : -1;
120
124
  });
121
125
  panels.forEach((panel) => {
122
126
  if (panel.value && selectedTab.value) {
123
- panel.hidden = panel.value !== selectedTab.value;
127
+ panel.selected = panel.value === selectedTab.value;
124
128
  }
125
129
  });
126
- this.dispatchEvent(new CustomEvent('tab-select', {
127
- detail: { tab: selectedTab, value: this.selected },
128
- bubbles: true,
129
- composed: true,
130
- }));
130
+ if (dispatchEvent)
131
+ this.dispatchEvent(new CustomEvent('select', {
132
+ detail: { tab: selectedTab, value: this.selected },
133
+ bubbles: true,
134
+ composed: true,
135
+ }));
131
136
  }
132
137
  }
133
138
  __decorate([
@@ -136,6 +141,3 @@ __decorate([
136
141
  __decorate([
137
142
  property({ type: String, reflect: true })
138
143
  ], Tabs.prototype, "selected", void 0);
139
- __decorate([
140
- queryAssignedElements({ flatten: true })
141
- ], Tabs.prototype, "$slotItems", void 0);
@@ -1,4 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
+ import { handleControlChange } from './mixins/attachable.js';
2
3
  declare const Base: Constructor<import("./mixins/attachable.js").AttachableInterface> & Constructor<import("./mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
3
4
  /**
4
5
  * TODO: Check if $control controls a menu/popover and keep hidden when it is open.
@@ -31,7 +32,7 @@ export declare class Tooltip extends Base {
31
32
  render(): import("lit-html").TemplateResult<1>;
32
33
  private readonly popoverController;
33
34
  constructor();
34
- handleControlChange(prev?: HTMLElement | null, next?: HTMLElement | null): void;
35
+ [handleControlChange](prev?: HTMLElement | null, next?: HTMLElement | null): void;
35
36
  set visible(value: boolean);
36
37
  }
37
38
  export {};
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
2
2
  import { LitElement, html } from 'lit';
3
3
  import { property, query } from 'lit/decorators.js';
4
4
  import { focusVisible } from '../core/focus-visible.js';
5
- import { Attachable } from './mixins/attachable.js';
5
+ import { Attachable, handleControlChange } from './mixins/attachable.js';
6
6
  import { InternalsAttached, internals } from './mixins/internals-attached.js';
7
7
  import { PopoverController } from './controllers/popover-controller.js';
8
8
  let lastHidingTime = 0;
@@ -83,7 +83,7 @@ export class Tooltip extends Base {
83
83
  };
84
84
  this[internals].role = 'tooltip';
85
85
  }
86
- handleControlChange(prev = null, next = null) {
86
+ [handleControlChange](prev = null, next = null) {
87
87
  const eventHandlers = {
88
88
  focusin: this.#handleFocusIn,
89
89
  focusout: this.#handleFocusOut,
@@ -1 +1,2 @@
1
- export declare function ensureReady(element: HTMLElement): Promise<HTMLElement>;
1
+ export declare function ensureReady(element: HTMLElement | null, useAnimationFrame?: boolean): Promise<void>;
2
+ export declare function ensureSlottedReady(host: HTMLElement, getSlotted?: () => (HTMLElement | null)[], useAnimationFrame?: boolean): Promise<void>;
@@ -1,15 +1,20 @@
1
- export async function ensureReady(element) {
1
+ export async function ensureReady(element, useAnimationFrame = false) {
2
2
  if (!element)
3
3
  return null;
4
4
  if (element.matches(':not(:defined)'))
5
5
  await customElements.whenDefined(element.tagName.toLowerCase());
6
- // Handles Lit update
7
- if ('updateComplete' in element) {
6
+ if ('updateComplete' in element)
8
7
  await element.updateComplete;
9
- }
10
- else {
11
- // Yield to the next frame to allow initial render to finish
8
+ if (useAnimationFrame)
12
9
  await new Promise((resolve) => requestAnimationFrame(resolve));
10
+ }
11
+ export async function ensureSlottedReady(host, getSlotted, useAnimationFrame = false) {
12
+ await ensureReady(host);
13
+ if (getSlotted) {
14
+ const elements = getSlotted();
15
+ await Promise.all(elements.map((el) => ensureReady(el, false)));
13
16
  }
14
- return element;
17
+ // Might be useful when you need to use sth like `getBoundingClientRect()`
18
+ if (useAnimationFrame)
19
+ await new Promise((resolve) => requestAnimationFrame(resolve));
15
20
  }
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const sharedButtonStyles = css`@layer common-button,icon-button;@layer shared{:host{--_on-color:var(--md-sys-color-on-primary);--_color:var(--md-sys-color-primary);--_h:40px;--_icon-size:20px;--_rs:12px;--_rp:8px;--_text-color:var(--_on-color);--_background-color:var(--_color);box-sizing:border-box;cursor:pointer;height:var(--_h);-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;vertical-align:top;border-start-start-radius:var(--_inner-left-radius,calc(var(--_h) / 2));border-start-end-radius:var(--_inner-right-radius,calc(var(--_h) / 2));border-end-end-radius:var(--_inner-right-radius,calc(var(--_h) / 2));border-end-start-radius:var(--_inner-left-radius,calc(var(--_h) / 2));outline:0;flex-shrink:0;justify-content:center;align-items:center;display:inline-flex;position:relative}:host([notransition]){transition:none!important}md-ripple,md-focus-ring{position:absolute}:host([size=xs]){--_h:32px}:host([size=m]){--_h:56px;--_icon-size:24px;--_rs:16px;--_rp:12px}:host([size=l]){--_h:96px;--_icon-size:32px;--_rs:28px;--_rp:16px}:host([size=xl]){--_h:136px;--_icon-size:40px;--_rs:28px;--_rp:16px}:host([color=secondary]){--_on-color:var(--md-sys-color-on-secondary);--_color:var(--md-sys-color-secondary)}:host([color=tertiary]){--_on-color:var(--md-sys-color-on-tertiary);--_color:var(--md-sys-color-tertiary)}:host([variant=tonal]){--_background-color:var(--md-sys-color-secondary-container);--_text-color:var(--md-sys-color-on-secondary-container)}:host([variant=outlined]){--_background-color:transparent;--_text-color:var(--md-sys-color-on-surface-variant);--md-focus-ring-offset:3px;border-color:color-mix(in srgb, var(--md-sys-color-outline-variant) 100%, transparent);border-style:solid;border-width:1px}:host([variant=text]){--_text-color:var(--_color);--_background-color:transparent!important}:host([square]){--md-focus-ring-shape:var(--_rs);border-radius:var(--_rs)}@media (forced-colors:active){:host{forced-color-adjust:none;color:buttontext;background:buttonface;border:1px solid buttonborder}}}@layer shared-toggle,override;@layer active{:host(:active),:host(:state(active)){--md-focus-ring-shape:var(--_rp);border-radius:var(--_rp)}}@layer disabled{:host([variant=outlined]:disabled){--_background-color:var(--md-sys-color-outline)}:host(:disabled),:host([variant=outlined]:state(checked):disabled){--_background-color:var(--md-sys-color-on-surface);--_background-opacity:10%;--_text-color:var(--md-sys-color-on-surface);--_text-opacity:38%;border-color:color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);box-shadow:none;pointer-events:none}@media (forced-colors:active){:host(:disabled){color:graytext;border-color:graytext}}}`;
2
+ export const sharedButtonStyles = css`@layer common-button,icon-button;@layer shared{:host{--_on-color:var(--md-sys-color-on-primary);--_color:var(--md-sys-color-primary);--_h:40px;--_icon-size:20px;--_rs:12px;--_rp:8px;--_text-color:var(--_on-color);--_background-color:var(--_color);box-sizing:border-box;cursor:pointer;height:var(--_h);-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;vertical-align:top;border-start-start-radius:var(--_inner-left-radius,calc(var(--_h) / 2));border-start-end-radius:var(--_inner-right-radius,calc(var(--_h) / 2));border-end-end-radius:var(--_inner-right-radius,calc(var(--_h) / 2));border-end-start-radius:var(--_inner-left-radius,calc(var(--_h) / 2));outline:0;flex-shrink:0;justify-content:center;align-items:center;display:inline-flex;position:relative}:host([notransition]){transition:none!important}md-ripple,md-focus-ring{position:absolute}:host([size=xs]){--_h:32px}:host([size=m]){--_h:56px;--_icon-size:24px;--_rs:16px;--_rp:12px}:host([size=l]){--_h:96px;--_icon-size:32px;--_rs:28px;--_rp:16px}:host([size=xl]){--_h:136px;--_icon-size:40px;--_rs:28px;--_rp:16px}:host([color=secondary]){--_on-color:var(--md-sys-color-on-secondary);--_color:var(--md-sys-color-secondary)}:host([color=tertiary]){--_on-color:var(--md-sys-color-on-tertiary);--_color:var(--md-sys-color-tertiary)}:host([variant=tonal]){--_background-color:var(--md-sys-color-secondary-container);--_text-color:var(--md-sys-color-on-secondary-container)}:host([variant=outlined]){--_background-color:transparent;--_text-color:var(--md-sys-color-on-surface-variant);--md-focus-ring-offset:3px;border-color:color-mix(in srgb, var(--md-sys-color-outline-variant) 100%, transparent);border-style:solid;border-width:1px;& md-ripple{inset:-1px}}:host([variant=text]){--_text-color:var(--_color);--_background-color:transparent!important}:host([square]){--md-focus-ring-shape:var(--_rs);border-radius:var(--_rs)}@media (forced-colors:active){:host{forced-color-adjust:none;color:buttontext;background:buttonface;border:1px solid buttonborder}}}@layer shared-toggle,override;@layer active{:host(:active),:host(:state(active)){--md-focus-ring-shape:var(--_rp);border-radius:var(--_rp)}}@layer disabled{:host([variant=outlined]:disabled){--_background-color:var(--md-sys-color-outline)}:host(:disabled),:host([variant=outlined]:state(checked):disabled){--_background-color:var(--md-sys-color-on-surface);--_background-opacity:10%;--_text-color:var(--md-sys-color-on-surface);--_text-opacity:38%;border-color:color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);box-shadow:none;pointer-events:none}@media (forced-colors:active){:host(:disabled){color:graytext;border-color:graytext}}}`;
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const standardButtonGroupStyles = css`:host{gap:8px;display:inline-flex}:host([size=s]){gap:12px}:host([size=ss]){gap:18px}`;
2
+ export const standardButtonGroupStyles = css`:host{gap:8px;display:inline-flex}:host([size=s]){gap:12px}:host([size=xs]){gap:18px}`;
@@ -10,7 +10,9 @@ export declare class M3StandardButtonGroup extends LitElement {
10
10
  $buttons: Array<HTMLElement>;
11
11
  static styles: any[];
12
12
  render(): import("lit-html").TemplateResult<1>;
13
+ constructor();
13
14
  connectedCallback(): void;
15
+ firstUpdated(): Promise<void>;
14
16
  disconnectedCallback(): void;
15
17
  /**
16
18
  * Passing no element resets all shape changes
@@ -1,8 +1,9 @@
1
1
  import { __decorate } from "tslib";
2
- import { LitElement, html } from 'lit';
2
+ import { LitElement, html, isServer } from 'lit';
3
3
  import { property, queryAssignedElements } from 'lit/decorators.js';
4
4
  import { customElement } from '../../core/decorators.js';
5
- import { getSpring } from '../system/motion.js';
5
+ import { ensureSlottedReady } from '../../core/ensure-ready.js';
6
+ import { getSpring } from '../styles/motion.js';
6
7
  import { standardButtonGroupStyles } from './standard-button-group-styles.css.js';
7
8
  const EXPAND_FACTOR = 1.15;
8
9
  const shapeAnimation = Symbol('shapeAnimation');
@@ -12,8 +13,12 @@ const shapeAnimation = Symbol('shapeAnimation');
12
13
  * @slot - buttons
13
14
  */
14
15
  let M3StandardButtonGroup = class M3StandardButtonGroup extends LitElement {
16
+ static { this.styles = [standardButtonGroupStyles]; }
17
+ render() {
18
+ return html `<slot @slotchange="${this.#handleSlotChange}"></slot>`;
19
+ }
15
20
  constructor() {
16
- super(...arguments);
21
+ super();
17
22
  this.size = '';
18
23
  this.#activeIndex = -1;
19
24
  this.#baseWidths = [];
@@ -42,20 +47,21 @@ let M3StandardButtonGroup = class M3StandardButtonGroup extends LitElement {
42
47
  this.updateLayout();
43
48
  }
44
49
  };
45
- }
46
- static { this.styles = [standardButtonGroupStyles]; }
47
- render() {
48
- return html `<slot @slotchange="${this.#handleSlotChange}"></slot>`;
50
+ if (!isServer) {
51
+ this.addEventListener('pointerdown', this.#handlePointerDown);
52
+ this.addEventListener('keydown', this.#handleKeyDown, { capture: true });
53
+ }
49
54
  }
50
55
  connectedCallback() {
51
56
  super.connectedCallback();
52
- this.addEventListener('pointerdown', this.#handlePointerDown);
53
- this.addEventListener('keydown', this.#handleKeyDown, { capture: true });
54
- window.addEventListener('pointerup', this.#reset);
55
- window.addEventListener('pointercancel', this.#reset);
56
- window.addEventListener('keyup', this.#reset, { capture: true });
57
+ const signal = (this.#abortController = new AbortController()).signal;
58
+ window.addEventListener('pointerup', this.#reset, { signal });
59
+ window.addEventListener('pointercancel', this.#reset, { signal });
60
+ window.addEventListener('keyup', this.#reset, { capture: true, signal });
57
61
  this.#resizeObserver = new ResizeObserver((entries) => {
58
- if (this.#activeIndex !== -1)
62
+ const isAnimating = this.#activeIndex !== -1 ||
63
+ this.$buttons.some((btn) => btn[shapeAnimation]);
64
+ if (isAnimating)
59
65
  return; // Skip if animating
60
66
  entries.forEach((entry) => {
61
67
  const target = entry.target;
@@ -63,22 +69,23 @@ let M3StandardButtonGroup = class M3StandardButtonGroup extends LitElement {
63
69
  if (index !== -1) {
64
70
  const currentWidth = target.style.width;
65
71
  target.style.width = '';
66
- this.#baseWidths[index] = entry.target.getBoundingClientRect().width;
72
+ this.#baseWidths[index] = target.getBoundingClientRect().width;
67
73
  if (currentWidth)
68
74
  target.style.width = currentWidth;
69
75
  }
70
76
  });
71
77
  });
72
- queueMicrotask(this.#handleSlotChange);
73
- // Which is not called on a SSR'd element
78
+ }
79
+ async firstUpdated() {
80
+ await ensureSlottedReady(this, () => this.$buttons, true);
81
+ this.#handleSlotChange();
74
82
  }
75
83
  disconnectedCallback() {
76
- window.removeEventListener('pointerup', this.#reset);
77
- window.removeEventListener('pointercancel', this.#reset);
78
- window.removeEventListener('keyup', this.#reset, { capture: true });
84
+ this.#abortController.abort();
79
85
  this.#resizeObserver.disconnect();
80
86
  super.disconnectedCallback();
81
87
  }
88
+ #abortController;
82
89
  #activeIndex;
83
90
  #resizeObserver;
84
91
  #baseWidths;
@@ -93,35 +100,34 @@ let M3StandardButtonGroup = class M3StandardButtonGroup extends LitElement {
93
100
  const pressedIndex = target ? this.$buttons.indexOf(target) : -1;
94
101
  this.#activeIndex = pressedIndex;
95
102
  const pressedBaseWidth = pressedIndex !== -1 ? this.#baseWidths[pressedIndex] : 0;
96
- const expansionDelta = pressedBaseWidth * (EXPAND_FACTOR - 1);
97
103
  const isPressedAtEdge = pressedIndex === 0 || pressedIndex === this.$buttons.length - 1;
104
+ const delta = (pressedBaseWidth * (EXPAND_FACTOR - 1)) / (isPressedAtEdge ? 1 : 2);
105
+ const spring = getSpring(this, 'spatial', 'fast');
98
106
  this.$buttons.forEach((btn, i) => {
99
107
  const currentWidth = btn.getBoundingClientRect().width;
100
108
  let targetWidth = this.#baseWidths[i];
101
109
  if (pressedIndex !== -1) {
102
- if (i === pressedIndex) {
110
+ if (i === pressedIndex)
103
111
  targetWidth = pressedBaseWidth * EXPAND_FACTOR;
104
- }
105
- else if (Math.abs(i - pressedIndex) === 1) {
106
- if (isPressedAtEdge)
107
- targetWidth = this.#baseWidths[i] - expansionDelta;
108
- else
109
- targetWidth = this.#baseWidths[i] - expansionDelta / 2;
110
- }
112
+ else if (Math.abs(i - pressedIndex) === 1)
113
+ targetWidth -= delta;
111
114
  }
112
- const spring = getSpring(this, 'spatial', 'fast');
113
- if (btn[shapeAnimation])
114
- btn[shapeAnimation].cancel();
115
- btn[shapeAnimation] = btn.animate([{ width: `${currentWidth}px` }, { width: `${targetWidth}px` }], {
115
+ btn[shapeAnimation]?.cancel();
116
+ const anim = (btn[shapeAnimation] = btn.animate([{ width: `${currentWidth}px` }, { width: `${targetWidth}px` }], {
116
117
  ...spring,
117
118
  fill: 'forwards',
118
- });
119
- btn[shapeAnimation].onfinish = () => {
120
- if (pressedIndex === -1) {
121
- btn.style.width = '';
122
- btn[shapeAnimation].cancel();
119
+ }));
120
+ anim.finished
121
+ .then(() => {
122
+ if (btn[shapeAnimation] === anim) {
123
+ if (pressedIndex === -1) {
124
+ btn.style.width = '';
125
+ anim.cancel();
126
+ }
127
+ delete btn[shapeAnimation];
123
128
  }
124
- };
129
+ })
130
+ .catch(() => { });
125
131
  });
126
132
  }
127
133
  };
@@ -16,7 +16,7 @@ export declare class M3Checkbox extends Checkbox {
16
16
  static styles: any[];
17
17
  render(): import("lit-html").TemplateResult<1>;
18
18
  renderIcon(): import("lit-html").TemplateResult<1>;
19
- connectedCallback(): void;
19
+ firstUpdated(): void;
20
20
  }
21
21
  declare global {
22
22
  interface HTMLElementTagNameMap {
@@ -26,12 +26,9 @@ let M3Checkbox = class M3Checkbox extends Checkbox {
26
26
  renderIcon() {
27
27
  return html `<svg part="icon" viewBox="0 0 18 18" aria-hidden="true"><rect part="mark mark-short"></rect><rect part="mark mark-long"></rect></svg>`;
28
28
  }
29
- connectedCallback() {
30
- super.connectedCallback();
29
+ firstUpdated() {
31
30
  // SSR'd <md-checkbox> components don't have their labels set up on time
32
- this.updateComplete.then(() => {
33
- this.$ripple.attach(this, true);
34
- });
31
+ this.$ripple.attach(this, true);
35
32
  }
36
33
  };
37
34
  __decorate([
@@ -2,6 +2,8 @@ import { Dialog } from '../../base/dialog.js';
2
2
  /**
3
3
  * TODO: test if form actions `cancel` and other things related work - no,
4
4
  * event listener needed
5
+ * FIXME: on Firefox, the focus is not autoatically set to the first tabbable
6
+ * element
5
7
  *
6
8
  * @tag md-dialog
7
9
  *
@@ -35,7 +37,7 @@ export declare class M3Dialog extends Dialog {
35
37
  $scrim: HTMLDivElement;
36
38
  static styles: any[];
37
39
  render(): import("lit-html").TemplateResult<1>;
38
- connectedCallback(): void;
40
+ firstUpdated(): Promise<void>;
39
41
  show(): void;
40
42
  close(): void;
41
43
  }
@@ -1,11 +1,14 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { html } from 'lit';
3
3
  import { query, state, customElement } from 'lit/decorators.js';
4
+ import { ensureSlottedReady } from '../../core/ensure-ready.js';
4
5
  import { Dialog } from '../../base/dialog.js';
5
6
  import { dialogStyles } from './dialog-styles.css.js';
6
7
  /**
7
8
  * TODO: test if form actions `cancel` and other things related work - no,
8
9
  * event listener needed
10
+ * FIXME: on Firefox, the focus is not autoatically set to the first tabbable
11
+ * element
9
12
  *
10
13
  * @tag md-dialog
11
14
  *
@@ -50,10 +53,9 @@ let M3Dialog = class M3Dialog extends Dialog {
50
53
  render() {
51
54
  return html `<dialog part="dialog" @click="${this.#handleClick}" @cancel="${this._handleCancel}"><div part="container"><div part="body"><div part="headline" class="${this.hasIcon ? 'has-icon' : ''}"><slot name="icon" @slotchange="${this.#handleIconSlotChange}"></slot><slot name="headline"></slot></div><div part="content"><slot></slot></div><div class="actions-placeholder"></div></div><div part="actions"><slot name="actions"></slot></div></div></dialog><div part="scrim"></div>`;
52
55
  }
53
- connectedCallback() {
54
- super.connectedCallback();
55
- queueMicrotask(this.#handleIconSlotChange);
56
- // Which is not called on a SSR'd element
56
+ async firstUpdated() {
57
+ await ensureSlottedReady(this);
58
+ this.#handleIconSlotChange();
57
59
  }
58
60
  #activeAnimations;
59
61
  #clearAnimations() {
@@ -1,4 +1,5 @@
1
1
  import { LitElement } from 'lit';
2
+ import { handleControlChange } from '../../base/mixins/attachable.js';
2
3
  declare const M3FocusRing_base: Constructor<import("../../base/mixins/attachable.js").AttachableInterface> & Constructor<import("../../base/mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
3
4
  /**
4
5
  * @tag md-focus-ring
@@ -22,7 +23,7 @@ export declare class M3FocusRing extends M3FocusRing_base {
22
23
  constructor();
23
24
  visualFocus(): void;
24
25
  visualBlur(): void;
25
- handleControlChange(prev?: HTMLElement | null, next?: HTMLElement | null): void;
26
+ [handleControlChange](prev?: HTMLElement | null, next?: HTMLElement | null): void;
26
27
  }
27
28
  declare global {
28
29
  interface HTMLElementTagNameMap {
@@ -3,8 +3,8 @@ import { LitElement } from 'lit';
3
3
  import { property } from 'lit/decorators.js';
4
4
  import { customElement } from '../../core/decorators.js';
5
5
  import { focusVisible } from '../../core/focus-visible.js';
6
- import { Attachable } from '../../base/mixins/attachable.js';
7
- import { InternalsAttached, internals, } from '../../base/mixins/internals-attached.js';
6
+ import { Attachable, handleControlChange } from '../../base/mixins/attachable.js';
7
+ import { InternalsAttached, internals, replaceStates, } from '../../base/mixins/internals-attached.js';
8
8
  import { focusRingStyles } from './focus-ring-styles.css.js';
9
9
  /**
10
10
  * @tag md-focus-ring
@@ -27,10 +27,7 @@ let M3FocusRing = class M3FocusRing extends Attachable(InternalsAttached(LitElem
27
27
  super();
28
28
  this.inward = false;
29
29
  this.#handleFocusIn = () => {
30
- if (focusVisible)
31
- this[internals].states.add('visible');
32
- else
33
- this[internals].states.delete('visible');
30
+ this[replaceStates](['visible'], [focusVisible ? 'visible' : null]);
34
31
  };
35
32
  this.#handleFocusOut = () => {
36
33
  this[internals].states.delete('visible');
@@ -49,7 +46,7 @@ let M3FocusRing = class M3FocusRing extends Attachable(InternalsAttached(LitElem
49
46
  visualBlur() {
50
47
  this.#handleFocusOut();
51
48
  }
52
- handleControlChange(prev = null, next = null) {
49
+ [handleControlChange](prev = null, next = null) {
53
50
  const eventHandlers = {
54
51
  focusin: this.#handleFocusIn,
55
52
  focusout: this.#handleFocusOut,
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const navigationRailItemStyles = css`:host{color:var(--md-sys-color-on-surface-variant);cursor:pointer;outline:0;grid-template-rows:40px 1fr;justify-content:flex-start;width:max-content;display:grid;position:relative}.pill-root{grid-template:".start icon.label end."32px/20px 16px 24px 0 0fr 16px 20px;place-self:center;display:grid;position:relative}.pill{border-radius:9999px;grid-area:1/start-start/auto/end-end;position:relative}::slotted(*){fill:currentColor;place-self:center;block-size:1em;inline-size:1em;font-size:24px;position:absolute}::slotted([slot=active]){opacity:0}.label-inside{font:var(--md-sys-typography-label-large);display:none}.label-outside{font:var(--md-sys-typography-label-medium);pointer-events:none;text-align:center;word-break:break-word;grid-template-rows:1fr;width:96px;display:block;overflow:hidden}:host([active]){& .pill{color:var(--md-sys-color-on-secondary-container)}& md-ripple{background-color:var(--md-sys-color-secondary-container)}&::slotted([slot=active]){opacity:1}&::slotted(:not([slot=active])){opacity:0}& .label-outside{color:var(--md-sys-color-secondary)}}`;
@@ -0,0 +1,21 @@
1
+ import { type PropertyValues } from 'lit';
2
+ import { Button } from '../../base/button.js';
3
+ import '../focus-ring/focus-ring.js';
4
+ import '../ripple/ripple.js';
5
+ import { type M3FocusRing } from '../focus-ring/focus-ring.js';
6
+ import { type M3Ripple } from '../ripple/ripple.js';
7
+ export declare class M3NavigationRailItem extends Button {
8
+ label: string;
9
+ active: boolean;
10
+ $focusRing: M3FocusRing;
11
+ $ripple: M3Ripple;
12
+ static styles: any[];
13
+ render(): import("lit-html").TemplateResult<1>;
14
+ updated(changed: PropertyValues): void;
15
+ firstUpdated(): void;
16
+ }
17
+ declare global {
18
+ interface HTMLElementTagNameMap {
19
+ 'md-nav-rail-item': M3NavigationRailItem;
20
+ }
21
+ }