@vollowx/seele 0.14.1 → 0.14.2

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 (49) hide show
  1. package/custom-elements.json +195 -153
  2. package/package.json +4 -4
  3. package/src/all.d.ts +2 -1
  4. package/src/all.js +2 -1
  5. package/src/base/autocomplete.d.ts +1 -0
  6. package/src/base/autocomplete.js +17 -5
  7. package/src/base/composed-menu.d.ts +16 -0
  8. package/src/base/composed-menu.js +36 -0
  9. package/src/base/menu.js +26 -0
  10. package/src/base/popup-styles.css.js +1 -1
  11. package/src/base/popup.d.ts +22 -9
  12. package/src/base/popup.js +131 -83
  13. package/src/base/positioning.d.ts +2 -0
  14. package/src/base/positioning.js +23 -0
  15. package/src/base/progressbar.d.ts +13 -0
  16. package/src/base/progressbar.js +32 -0
  17. package/src/base/slider.d.ts +1 -0
  18. package/src/base/slider.js +1 -0
  19. package/src/base/tooltip-styles.css.js +2 -0
  20. package/src/base/tooltip.d.ts +9 -8
  21. package/src/base/tooltip.js +143 -56
  22. package/src/m3/autocomplete/autocomplete-styles.css.js +1 -1
  23. package/src/m3/autocomplete/autocomplete.js +1 -1
  24. package/src/m3/badge/badge-styles.css.js +1 -1
  25. package/src/m3/item/item-styles.css.js +1 -1
  26. package/src/m3/list/list-item-styles.css.js +1 -1
  27. package/src/m3/loading/loading-styles.css.js +2 -0
  28. package/src/m3/{loading-indicator/loading-indicator.d.ts → loading/loading.d.ts} +9 -3
  29. package/src/m3/{loading-indicator/loading-indicator.js → loading/loading.js} +15 -9
  30. package/src/m3/menu/composed-menu.d.ts +4 -9
  31. package/src/m3/menu/composed-menu.js +7 -27
  32. package/src/m3/menu/menu-styles.css.js +1 -1
  33. package/src/m3/navigation/navigation-rail-styles.css.js +1 -1
  34. package/src/m3/popup/popup-styles.css.js +1 -1
  35. package/src/m3/popup/popup.d.ts +1 -0
  36. package/src/m3/popup/popup.js +4 -0
  37. package/src/m3/progress/linear-progress-styles.css.js +2 -0
  38. package/src/m3/progress/linear-progress.d.ts +22 -0
  39. package/src/m3/progress/linear-progress.js +35 -0
  40. package/src/m3/radio/radio-styles.css.js +1 -1
  41. package/src/m3/select/select-styles.css.js +1 -1
  42. package/src/m3/slider/slider-styles.css.js +1 -1
  43. package/src/m3/switch/switch-styles.css.js +1 -1
  44. package/src/m3/tooltip/tooltip-styles.css.js +1 -1
  45. package/src/m3/tooltip/tooltip.d.ts +0 -7
  46. package/src/m3/tooltip/tooltip.js +1 -8
  47. package/src/base/controllers/popover-controller.d.ts +0 -35
  48. package/src/base/controllers/popover-controller.js +0 -163
  49. package/src/m3/loading-indicator/loading-indicator-styles.css.js +0 -2
@@ -1,23 +1,32 @@
1
+ // References:
2
+ // - https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
3
+ // - https://sarahmhigley.com/writing/tooltips-in-wcag-21/#best-practices-summary
1
4
  import { __decorate } from "tslib";
2
- import { LitElement, html } from 'lit';
3
- import { property, query } from 'lit/decorators.js';
5
+ import { LitElement, html, isServer } from 'lit';
6
+ import { property } from 'lit/decorators.js';
7
+ import { autoUpdate, computePosition, arrow, flip, offset, shift, } from '@floating-ui/dom';
4
8
  import { focusVisible } from '../core/focus.js';
5
9
  import { Attachable, handleControlChange } from './mixins/attachable.js';
6
10
  import { InternalsAttached, internals } from './mixins/internals-attached.js';
7
- import { PopoverController } from './controllers/popover-controller.js';
11
+ import { transformOriginFromArrow } from './positioning.js';
12
+ import { tooltipStyles } from './tooltip-styles.css.js';
8
13
  let lastHidingTime = 0;
14
+ let userSelectHolders = { count: 0, prevUserSelect: '' };
15
+ // To prevent text zoom bubbles in Safari. Ignore the `webkitUserSelect`s.
9
16
  const Base = Attachable(InternalsAttached(LitElement));
10
17
  /**
11
18
  * TODO: Check if $control controls a menu/popover and keep hidden when it is open.
19
+ * TODO: Allow adding a real arrow
12
20
  */
13
21
  export class Tooltip extends Base {
22
+ static { this.styles = [tooltipStyles]; }
14
23
  render() {
15
- return html `<slot @slotchange="${this.#handleSlotChange}"></slot>`;
24
+ return html `<slot></slot>`;
16
25
  }
17
- // Different from those in the popoverController, these timers are used to
18
- // manage the delay before showing/hiding the tooltip.
26
+ // Used to manage the delay before showing/hiding the tooltip.
19
27
  #openTimer;
20
28
  #closeTimer;
29
+ #holdingUserSelect;
21
30
  constructor() {
22
31
  super();
23
32
  this._delays = {
@@ -26,62 +35,75 @@ export class Tooltip extends Base {
26
35
  touch: { show: 700, hide: 1500 },
27
36
  recentlyShowed: 800,
28
37
  };
29
- this._durations = { show: 100, hide: 100 };
30
38
  this.align = 'top';
31
39
  this.offset = 4;
40
+ this.windowPadding = 8;
32
41
  this.forceInvisible = false;
33
- // Different from those in the popoverController, these timers are used to
34
- // manage the delay before showing/hiding the tooltip.
42
+ this.open = false;
43
+ // Used to manage the delay before showing/hiding the tooltip.
35
44
  this.#openTimer = null;
36
45
  this.#closeTimer = null;
37
- this.popoverController = new PopoverController(this, {
38
- popover: () => this,
39
- trigger: () => this.$control,
40
- positioning: {
41
- placement: () => this.align,
42
- strategy: () => 'absolute',
43
- offset: () => this.offset,
44
- windowPadding: () => 16,
45
- },
46
- durations: {
47
- open: () => this._durations.show,
48
- close: () => this._durations.hide,
49
- },
50
- onClickAway: () => {
51
- this.visible = false;
52
- },
53
- });
54
- this.#handleSlotChange = () => {
55
- if (!this.visible)
56
- this.$control.setAttribute('aria-label', this.textContent ?? '');
57
- };
46
+ this.#holdingUserSelect = false;
58
47
  this.#handleFocusIn = () => {
59
48
  if (!focusVisible)
60
49
  return;
61
- this.#show(this._delays.focus.show);
50
+ this.#scheduleShow(this._delays.focus.show, true);
62
51
  };
63
52
  this.#handleFocusOut = () => {
64
- this.#hide(this._delays.focus.hide);
53
+ this.#scheduleHide(this._delays.focus.hide);
65
54
  };
66
55
  this.#handlePointerEnter = (e) => {
67
56
  const evt = e;
68
57
  if (evt.pointerType === 'touch')
69
58
  return;
70
- this.#show(this._delays.mouse.show);
59
+ this.#scheduleShow(this._delays.mouse.show, true);
71
60
  };
72
61
  this.#handlePointerLeave = (e) => {
73
62
  const evt = e;
74
63
  if (evt.pointerType === 'touch')
75
64
  return;
76
- this.#hide(this._delays.mouse.hide);
65
+ this.#scheduleHide(this._delays.mouse.hide);
77
66
  };
78
67
  this.#handleTouchStart = () => {
79
- this.#show(this._delays.touch.show);
68
+ this.#editUserSelect();
69
+ this.#scheduleShow(this._delays.touch.show);
80
70
  };
81
71
  this.#handleTouchEnd = () => {
82
- this.#hide(this._delays.touch.hide);
72
+ this.#restoreUserSelect();
73
+ this.#scheduleHide(this._delays.touch.hide);
74
+ };
75
+ this.#handleGlobalPointerUp = (event) => {
76
+ const trigger = this.$control;
77
+ const path = event.composedPath();
78
+ if (trigger && path.includes(trigger))
79
+ return;
80
+ if (path.includes(this))
81
+ return;
82
+ this.open = false;
83
+ };
84
+ this.#handleGlobalKeyDown = (event) => {
85
+ if (event.key === 'Escape')
86
+ this.open = false;
83
87
  };
88
+ this.#dummyArrow = isServer ? null : document.createElement('div');
84
89
  this[internals].role = 'tooltip';
90
+ if (!this.hasAttribute('popover'))
91
+ this.setAttribute('popover', 'manual');
92
+ }
93
+ disconnectedCallback() {
94
+ this._cleanup();
95
+ window.removeEventListener('pointerup', this.#handleGlobalPointerUp);
96
+ window.removeEventListener('keydown', this.#handleGlobalKeyDown);
97
+ this.#restoreUserSelect();
98
+ super.disconnectedCallback();
99
+ }
100
+ updated(changedProperties) {
101
+ if (!changedProperties.has('open'))
102
+ return;
103
+ if (this.open)
104
+ this.#show();
105
+ else
106
+ this.#hide();
85
107
  }
86
108
  [handleControlChange](prev = null, next = null) {
87
109
  const eventHandlers = {
@@ -97,50 +119,115 @@ export class Tooltip extends Base {
97
119
  next?.addEventListener(key, eventHandlers[key]);
98
120
  });
99
121
  if (prev)
100
- prev.removeAttribute('aria-label');
122
+ prev.ariaDescribedByElements = [];
101
123
  if (next)
102
- next.setAttribute('aria-label', this.textContent ?? '');
103
- }
104
- set visible(value) {
105
- if (value) {
106
- this.popoverController.animateOpen();
107
- }
108
- else {
109
- this.popoverController.animateClose();
110
- }
124
+ next.ariaDescribedByElements = [this];
111
125
  }
112
- #handleSlotChange;
113
126
  #handleFocusIn;
114
127
  #handleFocusOut;
115
128
  #handlePointerEnter;
116
129
  #handlePointerLeave;
117
130
  #handleTouchStart;
118
131
  #handleTouchEnd;
119
- #show(delay) {
132
+ #handleGlobalPointerUp;
133
+ #handleGlobalKeyDown;
134
+ #scheduleShow(delay, allowInstantShow = false) {
120
135
  clearTimeout(this.#closeTimer);
121
136
  this.#openTimer = setTimeout(() => {
122
- this.visible = true;
123
- }, Math.max(Date.now() - lastHidingTime < this._delays.recentlyShowed ? 0 : delay));
137
+ this.open = true;
138
+ }, allowInstantShow &&
139
+ Date.now() - lastHidingTime < this._delays.recentlyShowed
140
+ ? 0
141
+ : delay);
124
142
  }
125
- #hide(delay) {
126
- if (this.popoverController.open) {
143
+ #scheduleHide(delay) {
144
+ if (this.open) {
127
145
  lastHidingTime = Date.now();
128
146
  }
129
147
  clearTimeout(this.#openTimer);
130
148
  this.#closeTimer = setTimeout(() => {
131
- this.visible = false;
149
+ this.open = false;
132
150
  }, delay);
133
151
  }
152
+ async #show() {
153
+ setTimeout(() => {
154
+ if (this.open)
155
+ window.addEventListener('pointerup', this.#handleGlobalPointerUp);
156
+ }, 0);
157
+ window.addEventListener('keydown', this.#handleGlobalKeyDown);
158
+ const trigger = this.$control;
159
+ if (this.isConnected && !this.matches(':popover-open'))
160
+ this.showPopover({ source: trigger ?? undefined });
161
+ if (trigger) {
162
+ this._cleanup();
163
+ this.#cleanupAutoUpdate = autoUpdate(trigger, this, () => this.reposition());
164
+ await this.reposition();
165
+ }
166
+ }
167
+ #hide() {
168
+ window.removeEventListener('pointerup', this.#handleGlobalPointerUp);
169
+ window.removeEventListener('keydown', this.#handleGlobalKeyDown);
170
+ this._cleanup();
171
+ this.#restoreUserSelect();
172
+ if (this.matches(':popover-open'))
173
+ this.hidePopover();
174
+ }
175
+ #editUserSelect() {
176
+ if (!userSelectHolders.count) {
177
+ userSelectHolders.prevUserSelect = document.body.style.webkitUserSelect;
178
+ document.body.style.webkitUserSelect = 'none';
179
+ }
180
+ ++userSelectHolders.count;
181
+ this.#holdingUserSelect = true;
182
+ }
183
+ #restoreUserSelect() {
184
+ if (!this.#holdingUserSelect)
185
+ return;
186
+ this.#holdingUserSelect = false;
187
+ userSelectHolders.count = Math.max(0, userSelectHolders.count - 1);
188
+ if (!userSelectHolders.count)
189
+ document.body.style.webkitUserSelect = userSelectHolders.prevUserSelect;
190
+ }
191
+ #cleanupAutoUpdate;
192
+ #dummyArrow;
193
+ async reposition() {
194
+ const trigger = this.$control;
195
+ if (!trigger)
196
+ return Promise.resolve();
197
+ return computePosition(trigger, this, {
198
+ placement: this.align,
199
+ strategy: 'absolute',
200
+ middleware: [
201
+ offset(this.offset),
202
+ flip({ padding: this.windowPadding }),
203
+ shift({ padding: this.windowPadding, crossAxis: true }),
204
+ arrow({ element: this.#dummyArrow }),
205
+ ],
206
+ }).then(({ x, y, placement, middlewareData }) => {
207
+ Object.assign(this.style, {
208
+ left: `${x}px`,
209
+ top: `${y}px`,
210
+ transformOrigin: transformOriginFromArrow(placement, middlewareData.arrow),
211
+ });
212
+ });
213
+ }
214
+ _cleanup() {
215
+ this.#cleanupAutoUpdate?.();
216
+ this.#cleanupAutoUpdate = undefined;
217
+ }
134
218
  }
135
219
  __decorate([
136
220
  property({ reflect: true })
137
221
  ], Tooltip.prototype, "align", void 0);
138
222
  __decorate([
139
- property({ type: Number, reflect: true })
223
+ property({ type: Number })
140
224
  ], Tooltip.prototype, "offset", void 0);
225
+ __decorate([
226
+ property({ type: Number, attribute: 'window-padding' })
227
+ ], Tooltip.prototype, "windowPadding", void 0);
141
228
  __decorate([
142
229
  property({ type: Boolean, reflect: true, attribute: 'force-invisible' })
143
230
  ], Tooltip.prototype, "forceInvisible", void 0);
144
231
  __decorate([
145
- query('slot')
146
- ], Tooltip.prototype, "$slot", void 0);
232
+ property({ type: Boolean, reflect: true })
233
+ ], Tooltip.prototype, "open", void 0);
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const autocompleteStyles = css`:host{-webkit-user-select:none;user-select:none;min-width:210px;display:inline-block;position:relative}::slotted([slot=input]){cursor:pointer;outline:none;width:100%}[part=popup]{--md-menu-max-height:300px;min-width:inherit}`;
2
+ export const autocompleteStyles = css`:host{-webkit-user-select:none;user-select:none;width:300px;display:inline-block;position:relative}::slotted([slot=input]){cursor:pointer;outline:none;width:100%}[part=popup]{width:inherit}[part=menu]{max-height:400px}`;
@@ -11,7 +11,7 @@ import { autocompleteStyles } from './autocomplete-styles.css.js';
11
11
  let M3Autocomplete = class M3Autocomplete extends Autocomplete {
12
12
  static { this.styles = [autocompleteStyles]; }
13
13
  renderMenu() {
14
- return html `<md-popup part="popup" role="presentation" no-focus-control quick .open="${this.open}" @toggle="${(e) => (this.open = e.newState === 'open')}"><md-menu part="menu" id="menu" type="listbox" tabindex="-1" @action="${this.handleMenuAction}"><slot @slotchange="${this.handleItemsSlotChange}"></slot></md-menu></md-popup>`;
14
+ return html `<md-popup part="popup" role="presentation" no-focus-control quick .open="${this.open}" @toggle="${(e) => (this.open = e.newState === 'open')}"><md-menu part="menu" id="menu" role="listbox" tabindex="-1" @action="${this.handleMenuAction}"><slot @slotchange="${this.handleItemsSlotChange}"></slot></md-menu></md-popup>`;
15
15
  }
16
16
  };
17
17
  M3Autocomplete = __decorate([
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const badgeStyles = css`:host{--md-badge-offset:0}[part~=root]{vertical-align:middle;flex-shrink:0;display:inline-flex;position:relative}[part~=badge]{--md-badge-scale:1;top:var(--md-badge-offset);right:var(--md-badge-offset);box-sizing:border-box;font:var(--md-sys-typography-label-small);z-index:1;background-color:var(--md-sys-color-error);min-width:16px;height:16px;color:var(--md-sys-color-on-error);transform:scale(var(--md-badge-scale)) translate(50%, -50%);transform-origin:100% 0;transition:transform var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast), color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);pointer-events:none;border-radius:8px;flex-flow:wrap;justify-content:center;align-items:center;padding:0 2.5px;display:flex;position:absolute}:host([small]) [part~=badge]{--md-badge-scale:.375;color:#0000}:host([hidden]){display:hidden}:host([value="0"]:not([show-zero])) [part~=badge],:host([hidden]) [part~=badge]{--md-badge-scale:0}:host([circle]){--md-badge-offset:14%}:host([left]:not([dir=rtl])) [part~=badge],:host(:not([left])[dir=rtl]) [part~=badge]{right:auto;left:var(--md-badge-offset);transform:scale(var(--md-badge-scale)) translate(-50%, -50%);transform-origin:0 0}:host([bottom]) [part~=badge]{top:auto;bottom:var(--md-badge-offset);transform:scale(var(--md-badge-scale)) translate(50%, 50%);transform-origin:100% 100%}:host([left][bottom]:not([dir=rtl])) [part~=badge],:host(:not([left])[dir=rtl][bottom]) [part~=badge]{transform:scale(var(--md-badge-scale)) translate(-50%, 50%);transform-origin:0 100%}`;
2
+ export const badgeStyles = css`:host{--md-badge-offset:0}[part~=root]{vertical-align:middle;flex-shrink:0;display:inline-flex;position:relative}[part~=badge]{--md-badge-scale:1;top:var(--md-badge-offset);right:var(--md-badge-offset);box-sizing:border-box;font:var(--md-sys-typography-label-small);background-color:var(--md-sys-color-error);min-width:16px;height:16px;color:var(--md-sys-color-on-error);transform:scale(var(--md-badge-scale)) translate(50%, -50%);transform-origin:100% 0;transition:transform var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast), color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);pointer-events:none;border-radius:8px;flex-flow:wrap;justify-content:center;align-items:center;padding:0 2.5px;display:flex;position:absolute}:host([small]) [part~=badge]{--md-badge-scale:.375;color:#0000}:host([hidden]){display:hidden}:host([value="0"]:not([show-zero])) [part~=badge],:host([hidden]) [part~=badge]{--md-badge-scale:0}:host([circle]){--md-badge-offset:14%}:host([left]:not([dir=rtl])) [part~=badge],:host(:not([left])[dir=rtl]) [part~=badge]{right:auto;left:var(--md-badge-offset);transform:scale(var(--md-badge-scale)) translate(-50%, -50%);transform-origin:0 0}:host([bottom]) [part~=badge]{top:auto;bottom:var(--md-badge-offset);transform:scale(var(--md-badge-scale)) translate(50%, 50%);transform-origin:100% 100%}:host([left][bottom]:not([dir=rtl])) [part~=badge],:host(:not([left])[dir=rtl][bottom]) [part~=badge]{transform:scale(var(--md-badge-scale)) translate(-50%, 50%);transform-origin:0 100%}`;
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const itemStyles = css`:host{border-radius:inherit;box-sizing:border-box;font:var(--md-sys-typography-body-large);text-overflow:ellipsis;-webkit-user-select:none;user-select:none;flex:1;align-items:center;gap:16px;min-height:48px;padding:12px;display:flex;position:relative}:host([multiline]){min-height:72px}[name=container]{border-radius:inherit}[name=container]::slotted(*){position:absolute;inset:0}.default-slot{display:inline}.default-slot,.text ::slotted(*){text-overflow:ellipsis;overflow:hidden}.text{flex-direction:column;flex:1;display:flex;overflow:hidden}[name=overline]{font:var(--md-sys-typography-label-small)}[name=supporting-text]{color:var(--md-item-supporting-text-color,var(--md-sys-color-on-surface-variant));font:var(--md-sys-typography-body-medium)}`;
2
+ export const itemStyles = css`:host{border-radius:inherit;box-sizing:border-box;font:var(--md-sys-typography-body-large);min-height:calc(48px + 4px * var(--md-sys-spacing-density,0));padding:calc(12px + 2px * var(--md-sys-spacing-density,0)) 12px;text-overflow:ellipsis;-webkit-user-select:none;user-select:none;flex:1;align-items:center;gap:16px;display:flex;position:relative}:host([multiline]){min-height:72px}[name=container]{border-radius:inherit}[name=container]::slotted(*){position:absolute;inset:0}.default-slot{display:inline}.default-slot,.text ::slotted(*){text-overflow:ellipsis;overflow:hidden}.text{flex-direction:column;flex:1;display:flex;overflow:hidden}[name=overline]{font:var(--md-sys-typography-label-small)}[name=supporting-text]{color:var(--md-item-supporting-text-color,var(--md-sys-color-on-surface-variant));font:var(--md-sys-typography-body-medium)}`;
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const listItemStyles = css`:host{--md-focus-ring-shape:12px;--md-focus-ring-inward-offset:-3px;background-color:var(--_background,transparent);-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;border-radius:4px;outline:0;display:flex}:host(:not([hidden]):first-of-type){border-radius:12px 12px 4px 4px}:host(:not([hidden]):last-of-type){border-radius:4px 4px 12px 12px}:host(:state(selected)){--md-item-supporting-text-color:var(--md-sys-color-on-tertiary-container);background-color:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container);border-radius:12px}:host(:disabled){cursor:default;opacity:.3;pointer-events:none}md-item,md-item div[slot=container]{border-radius:inherit}@media (forced-colors:active){:host{forced-color-adjust:none;color:canvastext}:host(:hover),:host(:state(selected)){color:highlighttext;background:highlight}}`;
2
+ export const listItemStyles = css`:host{--md-focus-ring-shape:12px;--md-focus-ring-inward-offset:-3px;background-color:var(--_background,transparent);-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;border-radius:4px;outline:0;display:flex}:host(:not([hidden],:state(selected)):first-of-type){border-radius:12px 12px 4px 4px}:host(:not([hidden],:state(selected)):last-of-type){border-radius:4px 4px 12px 12px}:host(:state(selected)){--md-item-supporting-text-color:var(--md-sys-color-on-tertiary-container);background-color:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container);border-radius:12px}:host(:disabled){cursor:default;opacity:.3;pointer-events:none}md-item,md-item div[slot=container]{border-radius:inherit}md-ripple:before{transition:none}md-ripple::part(ripple){display:none}@media (forced-colors:active){:host{forced-color-adjust:none;color:canvastext}:host(:hover),:host(:state(selected)){color:highlighttext;background:highlight}}`;
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const loadingStyles = css`:host{aspect-ratio:1;background-color:var(--md-sys-color-surface);border-radius:9999px;place-items:center;width:48px;display:grid}:host([contained]){background-color:var(--md-sys-color-primary-container)}canvas{width:82%;height:82%;display:block}`;
@@ -1,5 +1,11 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 brahmkshatriya
4
+ * Modifications Copyright 2026 Lucas X. Zhao
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
1
7
  import { LitElement } from 'lit';
2
- declare const M3LoadingIndicator_base: Constructor<import("../../base/mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
8
+ declare const M3Loading_base: Constructor<import("../../base/mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
3
9
  /**
4
10
  * @tag md-loading
5
11
  *
@@ -8,7 +14,7 @@ declare const M3LoadingIndicator_base: Constructor<import("../../base/mixins/int
8
14
  * pulls the color once per second
9
15
  * NOTE: the pantagon is completely not following the shape system
10
16
  */
11
- export declare class M3LoadingIndicator extends M3LoadingIndicator_base {
17
+ export declare class M3Loading extends M3Loading_base {
12
18
  #private;
13
19
  contained: boolean;
14
20
  $canvas: HTMLCanvasElement;
@@ -20,7 +26,7 @@ export declare class M3LoadingIndicator extends M3LoadingIndicator_base {
20
26
  }
21
27
  declare global {
22
28
  interface HTMLElementTagNameMap {
23
- 'md-loading': M3LoadingIndicator;
29
+ 'md-loading': M3Loading;
24
30
  }
25
31
  }
26
32
  export {};
@@ -1,9 +1,15 @@
1
- // Origin: https://github.com/brahmkshatriya/material-osc/blob/main/assets/loading-shape-morph-demo.html
1
+ /**
2
+ * @license
3
+ * Copyright 2026 brahmkshatriya
4
+ * Modifications Copyright 2026 Lucas X. Zhao
5
+ * SPDX-License-Identifier: Apache-2.0
6
+ */
2
7
  import { __decorate } from "tslib";
8
+ // Origin: https://github.com/brahmkshatriya/material-osc/blob/main/assets/loading-shape-morph-demo.html
3
9
  import { LitElement, html } from 'lit';
4
10
  import { customElement, property, query } from 'lit/decorators.js';
5
11
  import { InternalsAttached, internals } from '../../base/mixins/internals-attached.js';
6
- import { loadingIndicatorStyles } from './loading-indicator-styles.css.js';
12
+ import { loadingStyles } from './loading-styles.css.js';
7
13
  const STEP_MS = 650;
8
14
  const FULL_ROTATION_MS = 4666;
9
15
  const QUARTER_ROTATION = 90;
@@ -19,8 +25,8 @@ const CENTER = CANVAS_SIZE / 2;
19
25
  * pulls the color once per second
20
26
  * NOTE: the pantagon is completely not following the shape system
21
27
  */
22
- let M3LoadingIndicator = class M3LoadingIndicator extends InternalsAttached(LitElement) {
23
- static { this.styles = [loadingIndicatorStyles]; }
28
+ let M3Loading = class M3Loading extends InternalsAttached(LitElement) {
29
+ static { this.styles = [loadingStyles]; }
24
30
  render() {
25
31
  return html `<canvas width="${CANVAS_SIZE}" height="${CANVAS_SIZE}" aria-hidden="true"></canvas>`;
26
32
  }
@@ -102,14 +108,14 @@ let M3LoadingIndicator = class M3LoadingIndicator extends InternalsAttached(LitE
102
108
  };
103
109
  __decorate([
104
110
  property({ type: Boolean, reflect: true })
105
- ], M3LoadingIndicator.prototype, "contained", void 0);
111
+ ], M3Loading.prototype, "contained", void 0);
106
112
  __decorate([
107
113
  query('canvas')
108
- ], M3LoadingIndicator.prototype, "$canvas", void 0);
109
- M3LoadingIndicator = __decorate([
114
+ ], M3Loading.prototype, "$canvas", void 0);
115
+ M3Loading = __decorate([
110
116
  customElement('md-loading')
111
- ], M3LoadingIndicator);
112
- export { M3LoadingIndicator };
117
+ ], M3Loading);
118
+ export { M3Loading };
113
119
  const lerp = (a, b, t) => a + (b - a) * t;
114
120
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
115
121
  const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
@@ -1,16 +1,11 @@
1
- import { handleControlChange } from '../../base/mixins/attachable.js';
2
- import { Popup } from '../../base/popup.js';
3
- import { M3Menu } from './menu.js';
1
+ import { ComposedMenu } from '../../base/composed-menu.js';
2
+ import './menu.js';
4
3
  /**
5
- * TODO: consider adding ariaLabel that is passed to the menu
4
+ * @tag md-composed-menu
6
5
  */
7
- export declare class M3ComposedMenu extends Popup {
6
+ export declare class M3ComposedMenu extends ComposedMenu {
8
7
  vibrant: boolean;
9
- $menu: M3Menu;
10
8
  static styles: any[];
11
- render(): import("lit-html").TemplateResult<1>;
12
- constructor();
13
- [handleControlChange](prev: HTMLElement | null, next: HTMLElement | null): void;
14
9
  }
15
10
  declare global {
16
11
  interface HTMLElementTagNameMap {
@@ -1,41 +1,21 @@
1
1
  import { __decorate } from "tslib";
2
- import { html } from 'lit';
3
- import { property, customElement, query } from 'lit/decorators.js';
4
- import { ensureReady } from '../../core/ensure-ready.js';
5
- import { internals } from '../../base/mixins/internals-attached.js';
6
- import { handleControlChange } from '../../base/mixins/attachable.js';
7
- import { Popup } from '../../base/popup.js';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { ComposedMenu } from '../../base/composed-menu.js';
4
+ import './menu.js';
8
5
  import { popupStyles } from '../popup/popup-styles.css.js';
9
6
  /**
10
- * TODO: consider adding ariaLabel that is passed to the menu
7
+ * @tag md-composed-menu
11
8
  */
12
- let M3ComposedMenu = class M3ComposedMenu extends Popup {
13
- static { this.styles = [...super.styles, popupStyles]; }
14
- render() {
15
- return html `<md-menu><slot></slot></md-menu>`;
16
- }
9
+ let M3ComposedMenu = class M3ComposedMenu extends ComposedMenu {
17
10
  constructor() {
18
- super();
11
+ super(...arguments);
19
12
  this.vibrant = false;
20
- this[internals].role = 'application';
21
- }
22
- [handleControlChange](prev, next) {
23
- if (next) {
24
- next.ariaHasPopup = 'menu';
25
- ensureReady(this.$menu).then(() => {
26
- next.ariaControlsElements = [this.$menu];
27
- this.$menu[internals].ariaLabelledByElements = [this.$control];
28
- });
29
- }
30
- super[handleControlChange](prev, next);
31
13
  }
14
+ static { this.styles = [...super.styles, popupStyles]; }
32
15
  };
33
16
  __decorate([
34
17
  property({ type: Boolean, reflect: true })
35
18
  ], M3ComposedMenu.prototype, "vibrant", void 0);
36
- __decorate([
37
- query('md-menu')
38
- ], M3ComposedMenu.prototype, "$menu", void 0);
39
19
  M3ComposedMenu = __decorate([
40
20
  customElement('md-composed-menu')
41
21
  ], M3ComposedMenu);
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const menuStyles = css`:host{box-sizing:border-box;height:inherit;max-height:300px;width:inherit;-webkit-user-select:none;user-select:none;outline:0;padding:4px;scroll-padding-block:4px;display:block;overflow-y:auto}[part=items]{flex-direction:column;gap:2px;display:flex}::slotted(md-divider){margin-block:6px}@media (forced-colors:active){:host{border:1px solid canvastext}}`;
2
+ export const menuStyles = css`:host{box-sizing:border-box;height:inherit;max-height:calc(100dvh - 16px);min-width:inherit;width:inherit;-webkit-user-select:none;user-select:none;outline:0;padding:4px;scroll-padding-block:4px;display:block;overflow-y:auto}[part=items]{flex-direction:column;gap:2px;display:flex}::slotted(md-divider){margin-block:6px}@media (forced-colors:active){:host{border:1px solid canvastext}}`;
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const navigationRailStyles = css`:host{background-color:var(--md-sys-color-surface-container);box-sizing:border-box;-webkit-user-select:none;user-select:none;z-index:10;flex-shrink:0;grid-template-rows:min-content auto;justify-content:space-between;gap:40px;width:96px;height:100%;padding-block:20px;display:grid;overflow:visible}.actions{z-index:10;place-self:center;display:grid;position:sticky;top:-44px}::slotted(md-fab){box-shadow:none}.items{grid-template-areas:"stack";display:grid}slot:not([name]){flex-direction:column;grid-area:stack;gap:8px;padding:0;list-style:none;display:flex;position:relative}::slotted([end]){margin-block-start:auto}`;
2
+ export const navigationRailStyles = css`:host{background-color:var(--md-sys-color-surface-container);box-sizing:border-box;-webkit-user-select:none;user-select:none;flex-shrink:0;grid-template-rows:min-content auto;justify-content:space-between;gap:40px;width:96px;height:100%;padding-block:20px;display:grid;overflow:visible}.actions{place-self:center;display:grid;position:sticky;top:-44px}::slotted(md-fab){box-shadow:none}.items{grid-template-areas:"stack";display:grid}slot:not([name]){flex-direction:column;grid-area:stack;gap:8px;padding:0;list-style:none;display:flex;position:relative}::slotted([end]){margin-block-start:auto}`;
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const popupStyles = css`:host{--_fast:var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);--_d:var(--md-sys-motion-spatial-default-duration);--_default:var(--_d) var(--md-sys-motion-spatial-default);background:var(--md-sys-color-surface-container);box-shadow:var(--md-sys-elevation-shadow-2);color:var(--md-sys-color-on-surface);opacity:0;transition:opacity var(--md-sys-motion-effects-fast-duration), transform var(--_fast), border-radius var(--_fast), display var(--_d) allow-discrete, overlay var(--_d) allow-discrete;border-radius:16px/46px;transform:scaleY(.35)}:host([strategy=fixed]){position:fixed}:host([vibrant]){--md-divider-color:var(--md-sys-color-tertiary);--md-item-supporting-text-color:var(--md-sys-color-on-tertiary-container);background-color:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container);& ::slotted([selected]){--md-item-supporting-text-color:var(--md-sys-color-on-tertiary);background-color:var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary)}}slot{opacity:0;transition:opacity 67ms;display:block}:host(:popover-open){opacity:1;transition:opacity 67ms linear, transform var(--_default), border-radius var(--_default);border-radius:16px;transform:scaleY(1);& slot{opacity:1;transition:opacity 67ms linear 67ms}}@starting-style{:host(:popover-open){opacity:0;border-radius:16px/46px;transform:scaleY(.35);& slot{opacity:0}}}`;
2
+ export const popupStyles = css`:host{--_f:var(--md-sys-motion-spatial-fast-duration);--_fast:var(--_f) var(--md-sys-motion-spatial-fast);--_default:var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-spatial-default);background:var(--md-sys-color-surface-container);box-shadow:var(--md-sys-elevation-shadow-2);color:var(--md-sys-color-on-surface);opacity:0;transition:opacity .15s linear, transform var(--_fast), border-radius var(--_fast), display var(--_f) allow-discrete, overlay var(--_f) allow-discrete;border-radius:16px/32px;transform:scaleY(.5)}:host([quick]),:host([quick]) slot{transition:none!important}:host([vibrant]){--md-divider-color:var(--md-sys-color-tertiary);--md-item-supporting-text-color:var(--md-sys-color-on-tertiary-container);background-color:var(--md-sys-color-tertiary-container);color:var(--md-sys-color-on-tertiary-container);& ::slotted([selected]){--md-item-supporting-text-color:var(--md-sys-color-on-tertiary);background-color:var(--md-sys-color-tertiary);color:var(--md-sys-color-on-tertiary)}}slot{opacity:0;transition:opacity 67ms;display:block}:host(:popover-open){opacity:1;transition:opacity 83ms linear, transform var(--_default), border-radius var(--_default);border-radius:16px;transform:scaleY(1);& slot{opacity:1;transition:opacity 67ms linear 67ms}}@starting-style{:host(:popover-open){opacity:0;border-radius:16px/32px;transform:scaleY(.5);& slot{opacity:0}}}`;
@@ -1,5 +1,6 @@
1
1
  import { Popup } from '../../base/popup.js';
2
2
  export declare class M3Popup extends Popup {
3
+ quick: boolean;
3
4
  vibrant: boolean;
4
5
  static styles: any[];
5
6
  }
@@ -5,10 +5,14 @@ import { popupStyles } from './popup-styles.css.js';
5
5
  let M3Popup = class M3Popup extends Popup {
6
6
  constructor() {
7
7
  super(...arguments);
8
+ this.quick = false;
8
9
  this.vibrant = false;
9
10
  }
10
11
  static { this.styles = [...super.styles, popupStyles]; }
11
12
  };
13
+ __decorate([
14
+ property({ type: Boolean, reflect: true })
15
+ ], M3Popup.prototype, "quick", void 0);
12
16
  __decorate([
13
17
  property({ type: Boolean, reflect: true })
14
18
  ], M3Popup.prototype, "vibrant", void 0);
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const linearProgressStyles = css`:host{--_thickness:var(--md-linear-progress-thickness,4px);height:var(--_thickness);align-items:center;width:100%;display:flex;position:relative;overflow-x:hidden}[part~=gap]{max-width:4px;height:4px;position:relative}[part~=gap][part~=after]{flex-shrink:0;width:4px}[part~=track]{background-color:var(--md-sys-color-secondary-container);height:var(--_thickness);border-radius:9999px}[part~=track][part~=before]{flex-shrink:0;width:0}[part~=track][part~=after]{flex-shrink:0;width:100%}[part=active]{background-color:var(--md-sys-color-primary);height:var(--_thickness);transform-origin:0 0;border-radius:9999px;flex-grow:1;flex-shrink:1;width:0;margin:auto 0;transition:min-width .25s cubic-bezier(.4,0,.6,1);inset-block:0}[part=stop]{background-color:var(--md-sys-color-primary);height:4px;border-radius:9999px;width:4px;position:absolute;inset-inline-end:calc(2px * clamp(0, (var(--_thickness) - 4px) / 1px * 1000000, 1))}:host([indeterminate]){& [part~=gap][part~=before]{width:4px;animation:1.3s linear infinite gap-before}& [part~=gap][part~=after]{flex-shrink:1;animation:1.3s linear infinite gap-after}& [part~=track][part~=before]{animation:1.3s cubic-bezier(.3,0,.8,.15) infinite track-before}& [part~=track][part~=after]{animation:1.3s cubic-bezier(.3,0,.8,.15) infinite track-after}& [part=stop]{display:none}}@keyframes gap-before{0%{width:0}9.8%{width:0}20%{width:4px}to{width:4px}}@keyframes gap-after{61.5385%{width:4px}61.6385%{width:0}to{width:0}}@keyframes track-before{0%{width:0}18%{width:0}61.5385%{width:calc(50% - 4px);animation-timing-function:cubic-bezier(.05,.7,.1,1)}to{width:100%}}@keyframes track-after{0%{width:100%}61.5385%{width:0}to{width:0}}`;
@@ -0,0 +1,22 @@
1
+ import { ProgressBar } from '../../base/progressbar.js';
2
+ /**
3
+ * TODO: wavy?
4
+ * NOTE: buffering progress indicator is no longer documented in MD3
5
+ *
6
+ * @tag md-linear-progress
7
+ *
8
+ * @csspart track
9
+ * @csspart gap
10
+ * @csspart active
11
+ * @csspart before
12
+ * @csspart after
13
+ */
14
+ export declare class M3LinearProgress extends ProgressBar {
15
+ static styles: any[];
16
+ render(): import("lit-html").TemplateResult<1>;
17
+ }
18
+ declare global {
19
+ interface HTMLElementTagNameMap {
20
+ 'md-linear-progress': M3LinearProgress;
21
+ }
22
+ }
@@ -0,0 +1,35 @@
1
+ import { __decorate } from "tslib";
2
+ import { html } from 'lit';
3
+ import { styleMap } from 'lit/directives/style-map.js';
4
+ import { customElement } from '../../core/decorators.js';
5
+ import { ProgressBar } from '../../base/progressbar.js';
6
+ import { linearProgressStyles } from './linear-progress-styles.css.js';
7
+ /**
8
+ * TODO: wavy?
9
+ * NOTE: buffering progress indicator is no longer documented in MD3
10
+ *
11
+ * @tag md-linear-progress
12
+ *
13
+ * @csspart track
14
+ * @csspart gap
15
+ * @csspart active
16
+ * @csspart before
17
+ * @csspart after
18
+ */
19
+ let M3LinearProgress = class M3LinearProgress extends ProgressBar {
20
+ static { this.styles = [linearProgressStyles]; }
21
+ render() {
22
+ const progress = Math.min(Math.max(this.value, 0), 100);
23
+ const gapAfterStyle = {
24
+ width: this.indeterminate || (progress > 0 && progress < 100) ? '' : '0',
25
+ };
26
+ const trackAfterStyle = {
27
+ width: this.indeterminate ? '' : `${100 - progress}%`,
28
+ };
29
+ return html `<div part="track before"></div><div part="gap before"></div><div part="active"></div><div part="gap after" style="${styleMap(gapAfterStyle)}"></div><div part="track after" style="${styleMap(trackAfterStyle)}"></div><div part="stop"></div>`;
30
+ }
31
+ };
32
+ M3LinearProgress = __decorate([
33
+ customElement('md-linear-progress')
34
+ ], M3LinearProgress);
35
+ export { M3LinearProgress };
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const radioStyles = css`:host{--_d:var(--md-sys-motion-effects-default-duration);--_default:var(--_d) var(--md-sys-motion-effects-default);--_slow:var(--md-sys-motion-effects-slow-duration) var(--md-sys-motion-effects-slow);cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;border-radius:20px;outline:0;justify-content:center;align-items:center;width:40px;height:40px;display:inline-flex;position:relative}.icon{z-index:1;width:20px;height:20px}.ring{fill:none;r:9px;stroke:var(--md-sys-color-on-surface-variant);stroke-width:2px;transition:stroke var(--_default);will-change:r, stroke-width}:host(:state(checked)) .ring{stroke:var(--md-sys-color-primary)}.dot{fill:var(--md-sys-color-primary);r:0px;will-change:r}:host(:state(checked)) .dot{r:5px}:host(:state(checking)) .ring{animation:check-ring-1 var(--_default), check-ring-2 var(--_slow) var(--_d)}:host(:state(checking)) .dot{animation:check-dot-1 var(--_default), check-dot-2 var(--_slow) var(--_d)}@keyframes check-ring-1{0%{r:9px;stroke-width:2px}to{r:4.5px;stroke-width:9px}}@keyframes check-ring-2{0%{r:8px;stroke-width:2px}to{r:9px;stroke-width:2px}}@keyframes check-dot-1{0%,to{r:0px}}@keyframes check-dot-2{0%{r:7px}to{r:5px}}:host(:state(unchecking)) .ring{animation:uncheck-ring-1 var(--_default), uncheck-ring-2 var(--_slow) var(--_d)}:host(:state(unchecking)) .dot{animation:uncheck-dot-1 var(--_default)}@keyframes uncheck-ring-1{0%{r:9px;stroke:var(--md-sys-color-primary);stroke-width:2px}to{r:8px;stroke:var(--md-sys-color-on-surface-variant);stroke-width:2px}}@keyframes uncheck-ring-2{0%{r:4.5px;stroke-width:9px}to{r:9px;stroke-width:2px}}@keyframes uncheck-dot-1{0%{fill:var(--md-sys-color-primary);r:5px}to{fill:var(--md-sys-color-on-surface-variant);r:7.5px}}:host([disabled]){pointer-events:none}:host([disabled]) .ring{stroke:color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent)}:host([disabled]) .dot{fill:color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent)}@media (forced-colors:active){:host{--md-sys-color-primary:SelectedItem;--md-sys-color-on-surface-variant:CanvasText}:host(:disabled) .ring{stroke:graytext}:host(:disabled) .dot{fill:graytext}}`;
2
+ export const radioStyles = css`:host{--_d:var(--md-sys-motion-effects-default-duration);--_default:var(--_d) var(--md-sys-motion-effects-default);--_slow:var(--md-sys-motion-effects-slow-duration) var(--md-sys-motion-effects-slow);cursor:pointer;isolation:isolate;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;border-radius:20px;outline:0;justify-content:center;align-items:center;width:40px;height:40px;display:inline-flex;position:relative}.icon{z-index:1;width:20px;height:20px}.ring{fill:none;r:9px;stroke:var(--md-sys-color-on-surface-variant);stroke-width:2px;transition:stroke var(--_default);will-change:r, stroke-width}:host(:state(checked)) .ring{stroke:var(--md-sys-color-primary)}.dot{fill:var(--md-sys-color-primary);r:0px;will-change:r}:host(:state(checked)) .dot{r:5px}:host(:state(checking)) .ring{animation:check-ring-1 var(--_default), check-ring-2 var(--_slow) var(--_d)}:host(:state(checking)) .dot{animation:check-dot-1 var(--_default), check-dot-2 var(--_slow) var(--_d)}@keyframes check-ring-1{0%{r:9px;stroke-width:2px}to{r:4.5px;stroke-width:9px}}@keyframes check-ring-2{0%{r:8px;stroke-width:2px}to{r:9px;stroke-width:2px}}@keyframes check-dot-1{0%,to{r:0px}}@keyframes check-dot-2{0%{r:7px}to{r:5px}}:host(:state(unchecking)) .ring{animation:uncheck-ring-1 var(--_default), uncheck-ring-2 var(--_slow) var(--_d)}:host(:state(unchecking)) .dot{animation:uncheck-dot-1 var(--_default)}@keyframes uncheck-ring-1{0%{r:9px;stroke:var(--md-sys-color-primary);stroke-width:2px}to{r:8px;stroke:var(--md-sys-color-on-surface-variant);stroke-width:2px}}@keyframes uncheck-ring-2{0%{r:4.5px;stroke-width:9px}to{r:9px;stroke-width:2px}}@keyframes uncheck-dot-1{0%{fill:var(--md-sys-color-primary);r:5px}to{fill:var(--md-sys-color-on-surface-variant);r:7.5px}}:host([disabled]){pointer-events:none}:host([disabled]) .ring{stroke:color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent)}:host([disabled]) .dot{fill:color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent)}@media (forced-colors:active){:host{--md-sys-color-primary:SelectedItem;--md-sys-color-on-surface-variant:CanvasText}:host(:disabled) .ring{stroke:graytext}:host(:disabled) .dot{fill:graytext}}`;
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const selectStyles = css`:host{-webkit-user-select:none;user-select:none;min-width:210px;display:inline-block;position:relative}[part=field]{cursor:pointer;outline:none;width:100%}[part=value]{font:var(--md-sys-typography-body-large);height:1.5em}[part=popup]{--md-menu-max-height:300px;min-width:inherit}`;
2
+ export const selectStyles = css`:host{-webkit-user-select:none;user-select:none;min-width:210px;display:inline-block;position:relative}[part=field]{cursor:pointer;outline:none;width:100%}[part=value]{font:var(--md-sys-typography-body-large);height:1.5em}[part=popup]{min-width:inherit}[part=menu]{max-height:400px}`;