@vollowx/seele 0.7.0

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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +79 -0
  3. package/package.json +62 -0
  4. package/src/all.js +19 -0
  5. package/src/base/button.js +61 -0
  6. package/src/base/checkbox.js +118 -0
  7. package/src/base/controllers/list-controller.js +96 -0
  8. package/src/base/controllers/popover-controller.js +163 -0
  9. package/src/base/field.js +3 -0
  10. package/src/base/hidden-styles.css.js +2 -0
  11. package/src/base/input.js +182 -0
  12. package/src/base/item.js +7 -0
  13. package/src/base/list-item.js +54 -0
  14. package/src/base/menu-item.js +12 -0
  15. package/src/base/menu-utils.js +111 -0
  16. package/src/base/menu.js +244 -0
  17. package/src/base/mixins/attachable.js +71 -0
  18. package/src/base/mixins/form-associated.js +69 -0
  19. package/src/base/mixins/internals-attached.js +13 -0
  20. package/src/base/option.js +17 -0
  21. package/src/base/select.js +285 -0
  22. package/src/base/switch.js +86 -0
  23. package/src/base/tooltip.js +139 -0
  24. package/src/core/focus-visible.js +13 -0
  25. package/src/core/shared.d.ts +1 -0
  26. package/src/core/unique-id.js +11 -0
  27. package/src/m3/button/common-button-styles.css.js +2 -0
  28. package/src/m3/button/common-button-toggle-styles.css.js +2 -0
  29. package/src/m3/button/common-button-toggle.js +69 -0
  30. package/src/m3/button/common-button.js +65 -0
  31. package/src/m3/button/icon-button-styles.css.js +2 -0
  32. package/src/m3/button/icon-button-toggle-styles.css.js +2 -0
  33. package/src/m3/button/icon-button-toggle.js +57 -0
  34. package/src/m3/button/icon-button.js +51 -0
  35. package/src/m3/button/shared-button-styles.css.js +2 -0
  36. package/src/m3/checkbox-styles.css.js +2 -0
  37. package/src/m3/checkbox.js +46 -0
  38. package/src/m3/fab-styles.css.js +2 -0
  39. package/src/m3/fab.js +48 -0
  40. package/src/m3/field/field-styles.css.js +2 -0
  41. package/src/m3/field/field.js +93 -0
  42. package/src/m3/field/filled-field-styles.css.js +2 -0
  43. package/src/m3/field/filled-field.js +30 -0
  44. package/src/m3/field/outlined-field-styles.css.js +2 -0
  45. package/src/m3/field/outlined-field.js +34 -0
  46. package/src/m3/focus-ring-styles.css.js +2 -0
  47. package/src/m3/focus-ring.js +72 -0
  48. package/src/m3/item-styles.css.js +2 -0
  49. package/src/m3/item.js +46 -0
  50. package/src/m3/list-item-styles.css.js +2 -0
  51. package/src/m3/list-item.js +52 -0
  52. package/src/m3/list-styles.css.js +2 -0
  53. package/src/m3/list.js +16 -0
  54. package/src/m3/menu-item.js +15 -0
  55. package/src/m3/menu-part-styles.css.js +2 -0
  56. package/src/m3/menu-styles.css.js +2 -0
  57. package/src/m3/menu.js +30 -0
  58. package/src/m3/option.js +15 -0
  59. package/src/m3/ripple-styles.css.js +2 -0
  60. package/src/m3/ripple.js +199 -0
  61. package/src/m3/select/filled-select.js +41 -0
  62. package/src/m3/select/outlined-select.js +41 -0
  63. package/src/m3/select/select-styles.css.js +2 -0
  64. package/src/m3/select/select.js +34 -0
  65. package/src/m3/switch-styles.css.js +2 -0
  66. package/src/m3/switch.js +129 -0
  67. package/src/m3/target-styles.css.js +2 -0
  68. package/src/m3/text-field/filled-text-field.js +38 -0
  69. package/src/m3/text-field/outlined-text-field.js +40 -0
  70. package/src/m3/text-field/text-field-styles.css.js +2 -0
  71. package/src/m3/toolbar-styles.css.js +2 -0
  72. package/src/m3/toolbar.js +53 -0
  73. package/src/m3/tooltip-styles.css.js +2 -0
  74. package/src/m3/tooltip.js +18 -0
@@ -0,0 +1,69 @@
1
+ import { __decorate } from "tslib";
2
+ import { property } from 'lit/decorators.js';
3
+ import { internals } from './internals-attached.js';
4
+ export const FormAssociated = (superClass) => {
5
+ class FormAssociatedElement extends superClass {
6
+ static { this.formAssociated = true; }
7
+ get form() {
8
+ return this[internals].form;
9
+ }
10
+ get labels() {
11
+ return this[internals].labels;
12
+ }
13
+ // From https://github.com/material-components/material-web/blob/main/labs/behaviors/form-associated.ts
14
+ // Use @property for the `name` and `disabled` properties to add them to the
15
+ // `observedAttributes` array and trigger `attributeChangedCallback()`.
16
+ //
17
+ // We don't use Lit's default getter/setter (`noAccessor: true`) because
18
+ // the attributes need to be updated synchronously to work with synchronous
19
+ // form APIs, and Lit updates attributes async by default.
20
+ get name() {
21
+ return this.getAttribute('name') ?? '';
22
+ }
23
+ set name(name) {
24
+ // Note: setting name to null or empty does not remove the attribute.
25
+ this.setAttribute('name', name);
26
+ // We don't need to call `requestUpdate()` since it's called synchronously
27
+ // in `attributeChangedCallback()`.
28
+ }
29
+ get disabled() {
30
+ return this.hasAttribute('disabled');
31
+ }
32
+ set disabled(disabled) {
33
+ this.toggleAttribute('disabled', disabled);
34
+ // We don't need to call `requestUpdate()` since it's called synchronously
35
+ // in `attributeChangedCallback()`.
36
+ }
37
+ attributeChangedCallback(name, old, value) {
38
+ if (name === 'disabled') {
39
+ this.requestUpdate('disabled', old !== null);
40
+ }
41
+ super.attributeChangedCallback(name, old, value);
42
+ }
43
+ get validity() {
44
+ return this[internals].validity;
45
+ }
46
+ get validationMessage() {
47
+ return this[internals].validationMessage;
48
+ }
49
+ get willValidate() {
50
+ return this[internals].willValidate;
51
+ }
52
+ formDisabledCallback(disabled) {
53
+ this.disabled = disabled;
54
+ }
55
+ checkValidity() {
56
+ return this[internals].checkValidity();
57
+ }
58
+ reportValidity() {
59
+ return this[internals].reportValidity();
60
+ }
61
+ }
62
+ __decorate([
63
+ property({ noAccessor: true })
64
+ ], FormAssociatedElement.prototype, "name", null);
65
+ __decorate([
66
+ property({ type: Boolean, noAccessor: true })
67
+ ], FormAssociatedElement.prototype, "disabled", null);
68
+ return FormAssociatedElement;
69
+ };
@@ -0,0 +1,13 @@
1
+ export const internals = Symbol('internals');
2
+ const privateInternals = Symbol('privateInternals');
3
+ export const InternalsAttached = (superClass) => {
4
+ class InternalsAttachedElement extends superClass {
5
+ get [internals]() {
6
+ if (!this[privateInternals]) {
7
+ this[privateInternals] = this.attachInternals();
8
+ }
9
+ return this[privateInternals];
10
+ }
11
+ }
12
+ return InternalsAttachedElement;
13
+ };
@@ -0,0 +1,17 @@
1
+ import { __decorate } from "tslib";
2
+ import { property } from 'lit/decorators.js';
3
+ import { ListItem } from './list-item.js';
4
+ export const OptionMixin = (superClass) => {
5
+ class OptionElement extends superClass {
6
+ constructor() {
7
+ super(...arguments);
8
+ this.value = '';
9
+ }
10
+ }
11
+ __decorate([
12
+ property({ reflect: true })
13
+ ], OptionElement.prototype, "value", void 0);
14
+ return OptionElement;
15
+ };
16
+ export class Option extends OptionMixin(ListItem) {
17
+ }
@@ -0,0 +1,285 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement, html } from 'lit';
3
+ import { property, query, state } from 'lit/decorators.js';
4
+ import { FormAssociated } from './mixins/form-associated.js';
5
+ import { InternalsAttached } from './mixins/internals-attached.js';
6
+ import { PopoverController } from './controllers/popover-controller.js';
7
+ import { ListController } from './controllers/list-controller.js';
8
+ import { MenuActions, getActionFromKey, getUpdatedIndex, scrollItemIntoView, } from './menu-utils.js';
9
+ const Base = FormAssociated(InternalsAttached(LitElement));
10
+ /**
11
+ * @csspart field
12
+ * @csspart menu
13
+ * @csspart items
14
+ *
15
+ * @fires {Event} change - Fired when the selected value has changed.
16
+ * @fires {Event} input - Fired when the selected value has changed.
17
+ *
18
+ * TODO: Consider handle mouseover to focus items.
19
+ */
20
+ export class Select extends Base {
21
+ constructor() {
22
+ super(...arguments);
23
+ this._possibleItemTags = [];
24
+ this._durations = { show: 0, hide: 0 };
25
+ this._scrollPadding = 0;
26
+ this.value = '';
27
+ this.displayValue = '';
28
+ this.placeholder = '';
29
+ this.open = false;
30
+ this.required = false;
31
+ this.quick = false;
32
+ this.align = 'bottom-start';
33
+ this.alignStrategy = 'absolute';
34
+ this.offset = 0;
35
+ this.popoverController = new PopoverController(this, {
36
+ popover: () => this.$menu,
37
+ trigger: () => this.$field,
38
+ positioning: {
39
+ placement: () => this.align,
40
+ strategy: () => this.alignStrategy,
41
+ offset: () => this.offset,
42
+ windowPadding: () => 16,
43
+ },
44
+ durations: {
45
+ open: () => (this.quick ? 0 : this._durations.show),
46
+ close: () => (this.quick ? 0 : this._durations.hide),
47
+ },
48
+ onClickOutside: () => {
49
+ this.open = false;
50
+ },
51
+ });
52
+ this.listController = new ListController(this, {
53
+ isItem: (item) => this._possibleItemTags.includes(item.tagName.toLowerCase()) &&
54
+ !item.hasAttribute('disabled'),
55
+ getPossibleItems: () => Array.from(this.children).filter((child) => this._possibleItemTags.includes(child.tagName.toLowerCase()) &&
56
+ !child.hasAttribute('disabled')),
57
+ blurItem: (item) => {
58
+ item.focused = false;
59
+ },
60
+ focusItem: (item) => {
61
+ item.focused = true;
62
+ if (this.$field && item.id) {
63
+ this.$field.setAttribute('aria-activedescendant', item.id);
64
+ }
65
+ scrollItemIntoView(this.$menu, item, this._scrollPadding);
66
+ },
67
+ wrapNavigation: () => false,
68
+ });
69
+ }
70
+ render() {
71
+ return html `${this.renderField()} ${this.renderMenu()}`;
72
+ }
73
+ renderField() {
74
+ return html `
75
+ <div
76
+ part="field"
77
+ @click=${this.toggle}
78
+ @keydown=${this.handleFieldKeydown}
79
+ tabindex=${this.disabled ? '-1' : '0'}
80
+ role="combobox"
81
+ aria-haspopup="listbox"
82
+ aria-expanded=${this.open}
83
+ aria-controls="menu"
84
+ aria-disabled=${this.disabled}
85
+ aria-required=${this.required}
86
+ >
87
+ ${this.renderFieldContent()}
88
+ </div>
89
+ `;
90
+ }
91
+ renderMenu() {
92
+ return html `
93
+ <div part="menu" id="menu" role="listbox" tabindex="-1">
94
+ <slot part="items" @slotchange=${this.handleSlotChange}></slot>
95
+ </div>
96
+ `;
97
+ }
98
+ renderFieldContent() {
99
+ return html `<span part="value"
100
+ >${this.displayValue || this.placeholder}</span
101
+ >`;
102
+ }
103
+ connectedCallback() {
104
+ super.connectedCallback();
105
+ this.addEventListener('click', this.#handleOptionClick);
106
+ this.addEventListener('focusout', this.#handleFocusOut);
107
+ }
108
+ disconnectedCallback() {
109
+ super.disconnectedCallback();
110
+ this.removeEventListener('click', this.#handleOptionClick);
111
+ this.removeEventListener('focusout', this.#handleFocusOut);
112
+ }
113
+ updated(changed) {
114
+ super.updated(changed);
115
+ if (changed.has('value')) {
116
+ this.#updateDisplayValue();
117
+ this.#updateSelection();
118
+ }
119
+ if (changed.has('open')) {
120
+ if (this.open) {
121
+ this.popoverController.animateOpen();
122
+ // Focus current item when opening
123
+ const index = this.listController.items.findIndex((item) => (item.value || item.innerText) === this.value);
124
+ if (index >= 0) {
125
+ this.listController._focusItem(this.listController.items[index]);
126
+ }
127
+ else {
128
+ this.listController.focusFirstItem();
129
+ }
130
+ }
131
+ else {
132
+ this.popoverController.animateClose();
133
+ this.listController.clearSearch();
134
+ }
135
+ }
136
+ }
137
+ toggle() {
138
+ if (this.disabled)
139
+ return;
140
+ this.open = !this.open;
141
+ }
142
+ handleFieldKeydown(event) {
143
+ if (this.disabled)
144
+ return;
145
+ const action = getActionFromKey(event, this.open);
146
+ const items = this.listController.items;
147
+ const currentIndex = this.listController.currentIndex;
148
+ const maxIndex = items.length - 1;
149
+ switch (action) {
150
+ case MenuActions.Last:
151
+ case MenuActions.First:
152
+ this.open = true;
153
+ // intentional fallthrough
154
+ case MenuActions.Next:
155
+ case MenuActions.Previous:
156
+ case MenuActions.PageUp:
157
+ case MenuActions.PageDown:
158
+ event.preventDefault();
159
+ const nextIndex = getUpdatedIndex(currentIndex, maxIndex, action);
160
+ this.#onOptionChange(nextIndex);
161
+ return;
162
+ case MenuActions.CloseSelect:
163
+ event.preventDefault();
164
+ this.#selectOption(currentIndex);
165
+ // intentional fallthrough
166
+ case MenuActions.Close:
167
+ event.preventDefault();
168
+ this.open = false;
169
+ return;
170
+ case MenuActions.Type:
171
+ this.open = true;
172
+ this.listController.handleType(event.key);
173
+ return;
174
+ case MenuActions.Open:
175
+ event.preventDefault();
176
+ this.open = true;
177
+ return;
178
+ }
179
+ }
180
+ #onOptionChange(index) {
181
+ const items = this.listController.items;
182
+ if (index >= 0 && index < items.length) {
183
+ this.listController._focusItem(items[index]);
184
+ }
185
+ }
186
+ #selectOption(index) {
187
+ const items = this.listController.items;
188
+ if (index >= 0 && index < items.length) {
189
+ const item = items[index];
190
+ const newValue = item.value || item.innerText;
191
+ if (this.value !== newValue) {
192
+ this.value = newValue;
193
+ // According to https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event#event_type
194
+ // the change and input events are just Event and should not include detail.
195
+ this.dispatchEvent(new Event('input', { bubbles: true }));
196
+ this.dispatchEvent(new Event('change', { bubbles: true }));
197
+ }
198
+ this.open = false;
199
+ }
200
+ }
201
+ #handleOptionClick(event) {
202
+ const target = event.target;
203
+ const item = target.closest(this._possibleItemTags.join(','));
204
+ if (item && this.listController.items.includes(item)) {
205
+ const index = this.listController.items.indexOf(item);
206
+ this.#selectOption(index);
207
+ }
208
+ }
209
+ #handleFocusOut(event) {
210
+ const relatedTarget = event.relatedTarget;
211
+ if (!this.contains(relatedTarget) && !this.$menu.contains(relatedTarget)) {
212
+ this.open = false;
213
+ }
214
+ }
215
+ #updateDisplayValue() {
216
+ // We need to wait for items to be available.
217
+ // Using a microtask or just checking if items exist.
218
+ // Since this is called in updated(), items might be available if children are slotted.
219
+ const items = this.listController.items;
220
+ const selectedItem = items.find((item) => (item.value || item.innerText) === this.value);
221
+ if (selectedItem) {
222
+ this.displayValue = selectedItem.innerText;
223
+ }
224
+ else if (!this.value) {
225
+ this.displayValue = '';
226
+ }
227
+ }
228
+ // TODO: Store previously selected item to avoid looping through all items
229
+ #updateSelection() {
230
+ const items = this.listController.items;
231
+ items.forEach((item) => {
232
+ const itemValue = item.value || item.innerText;
233
+ item.selected = itemValue === this.value;
234
+ });
235
+ }
236
+ // TODO: Handle multiple selected items
237
+ handleSlotChange() {
238
+ const items = this.listController.items;
239
+ if (!this.value && items.length > 0) {
240
+ const selectedItem = items.find((item) => item.hasAttribute('selected'));
241
+ if (selectedItem) {
242
+ this.value = selectedItem.value || selectedItem.innerText;
243
+ }
244
+ else {
245
+ const firstItem = items[0];
246
+ this.value = firstItem.value || firstItem.innerText;
247
+ }
248
+ }
249
+ this.#updateDisplayValue();
250
+ this.#updateSelection();
251
+ }
252
+ }
253
+ __decorate([
254
+ property({ reflect: true })
255
+ ], Select.prototype, "value", void 0);
256
+ __decorate([
257
+ state()
258
+ ], Select.prototype, "displayValue", void 0);
259
+ __decorate([
260
+ property({ type: String })
261
+ ], Select.prototype, "placeholder", void 0);
262
+ __decorate([
263
+ property({ type: Boolean, reflect: true })
264
+ ], Select.prototype, "open", void 0);
265
+ __decorate([
266
+ property({ type: Boolean, reflect: true })
267
+ ], Select.prototype, "required", void 0);
268
+ __decorate([
269
+ property({ type: Boolean, reflect: true })
270
+ ], Select.prototype, "quick", void 0);
271
+ __decorate([
272
+ property({ reflect: true })
273
+ ], Select.prototype, "align", void 0);
274
+ __decorate([
275
+ property({ type: String, reflect: true })
276
+ ], Select.prototype, "alignStrategy", void 0);
277
+ __decorate([
278
+ property({ type: Number, reflect: true })
279
+ ], Select.prototype, "offset", void 0);
280
+ __decorate([
281
+ query('[part="field"]')
282
+ ], Select.prototype, "$field", void 0);
283
+ __decorate([
284
+ query('[part="menu"]')
285
+ ], Select.prototype, "$menu", void 0);
@@ -0,0 +1,86 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
+ import { InternalsAttached, internals } from './mixins/internals-attached.js';
5
+ import { FormAssociated } from './mixins/form-associated.js';
6
+ import { hiddenStyles } from './hidden-styles.css.js';
7
+ const PROPERTY_FROM_ARIA_PRESSED = {
8
+ true: 'checked',
9
+ false: 'unchecked',
10
+ };
11
+ const Base = FormAssociated(InternalsAttached(LitElement));
12
+ export class Switch extends Base {
13
+ static { this.styles = [hiddenStyles]; }
14
+ constructor() {
15
+ super();
16
+ this.checked = false;
17
+ this._ignoreClick = false;
18
+ this.#handleClick = (e) => {
19
+ e.stopPropagation();
20
+ e.preventDefault();
21
+ if (this._ignoreClick)
22
+ return;
23
+ this.#toggleChecked();
24
+ };
25
+ this.#handleKeyDown = (e) => {
26
+ if (e.key !== ' ' && e.key !== 'Enter')
27
+ return;
28
+ e.preventDefault();
29
+ e.stopPropagation();
30
+ if (e.key === 'Enter')
31
+ this.#toggleChecked();
32
+ };
33
+ this.#handleKeyUp = (e) => {
34
+ if (e.key === ' ') {
35
+ e.preventDefault();
36
+ e.stopPropagation();
37
+ this.#toggleChecked();
38
+ }
39
+ };
40
+ this[internals].role = 'switch';
41
+ this.checked = this.hasAttribute('checked');
42
+ this.updateInternals();
43
+ }
44
+ connectedCallback() {
45
+ super.connectedCallback();
46
+ this.addEventListener('click', this.#handleClick);
47
+ this.addEventListener('keydown', this.#handleKeyDown);
48
+ this.addEventListener('keyup', this.#handleKeyUp);
49
+ }
50
+ disconnectedCallback() {
51
+ super.disconnectedCallback();
52
+ this.removeEventListener('click', this.#handleClick);
53
+ this.removeEventListener('keydown', this.#handleKeyDown);
54
+ this.removeEventListener('keyup', this.#handleKeyUp);
55
+ }
56
+ updated(changed) {
57
+ if (changed.has('checked') || changed.has('disabled')) {
58
+ this.updateInternals();
59
+ }
60
+ }
61
+ updateInternals() {
62
+ this[internals].states.delete('unchecked');
63
+ this[internals].states.delete('checked');
64
+ this[internals].ariaPressed = this.checked ? 'true' : 'false';
65
+ this[internals].states.add(`${PROPERTY_FROM_ARIA_PRESSED[this[internals].ariaPressed]}`);
66
+ this.setAttribute('tabindex', this.disabled ? '-1' : '0');
67
+ this[internals].ariaDisabled = this.disabled ? 'true' : 'false';
68
+ this[internals].setFormValue(this.checked ? 'on' : null);
69
+ }
70
+ #handleClick;
71
+ #handleKeyDown;
72
+ #handleKeyUp;
73
+ #toggleChecked() {
74
+ if (this.disabled)
75
+ return;
76
+ this.checked = !this.checked;
77
+ this.dispatchEvent(new CustomEvent('change', {
78
+ bubbles: true,
79
+ composed: true,
80
+ detail: this.checked,
81
+ }));
82
+ }
83
+ }
84
+ __decorate([
85
+ property({ type: Boolean })
86
+ ], Switch.prototype, "checked", void 0);
@@ -0,0 +1,139 @@
1
+ import { __decorate } from "tslib";
2
+ import { LitElement, html } from 'lit';
3
+ import { property, query } from 'lit/decorators.js';
4
+ import { focusVisible } from '../core/focus-visible.js';
5
+ import { Attachable } from './mixins/attachable.js';
6
+ import { InternalsAttached, internals } from './mixins/internals-attached.js';
7
+ import { PopoverController } from './controllers/popover-controller.js';
8
+ let lastHidingTime = 0;
9
+ const Base = Attachable(InternalsAttached(LitElement));
10
+ export class Tooltip extends Base {
11
+ // Different from those in the popoverController, these timers are used to
12
+ // manage the delay before showing/hiding the tooltip.
13
+ #openTimer;
14
+ #closeTimer;
15
+ constructor() {
16
+ super();
17
+ this._delays = {
18
+ mouse: { show: 500, hide: 0 },
19
+ focus: { show: 100, hide: 0 },
20
+ touch: { show: 700, hide: 1500 },
21
+ recentlyShowed: 800,
22
+ };
23
+ this._durations = { show: 100, hide: 100 };
24
+ this.align = 'top';
25
+ this.offset = 4;
26
+ // Different from those in the popoverController, these timers are used to
27
+ // manage the delay before showing/hiding the tooltip.
28
+ this.#openTimer = null;
29
+ this.#closeTimer = null;
30
+ this.popoverController = new PopoverController(this, {
31
+ popover: () => this,
32
+ trigger: () => this.$control,
33
+ positioning: {
34
+ placement: () => this.align,
35
+ strategy: () => 'absolute',
36
+ offset: () => this.offset,
37
+ windowPadding: () => 16,
38
+ },
39
+ durations: {
40
+ open: () => this._durations.show,
41
+ close: () => this._durations.hide,
42
+ },
43
+ onClickOutside: () => {
44
+ this.visible = false;
45
+ },
46
+ });
47
+ this.#handleSlotChange = () => {
48
+ if (!this.visible)
49
+ this.$control.setAttribute('aria-label', this.textContent ?? '');
50
+ };
51
+ this.#handleFocusIn = () => {
52
+ if (!focusVisible)
53
+ return;
54
+ this.#show(this._delays.focus.show);
55
+ };
56
+ this.#handleFocusOut = () => {
57
+ this.#hide(this._delays.focus.hide);
58
+ };
59
+ this.#handlePointerEnter = (e) => {
60
+ const evt = e;
61
+ if (evt.pointerType === 'touch')
62
+ return;
63
+ this.#show(this._delays.mouse.show);
64
+ };
65
+ this.#handlePointerLeave = (e) => {
66
+ const evt = e;
67
+ if (evt.pointerType === 'touch')
68
+ return;
69
+ this.#hide(this._delays.mouse.hide);
70
+ };
71
+ this.#handleTouchStart = () => {
72
+ this.#show(this._delays.touch.show);
73
+ };
74
+ this.#handleTouchEnd = () => {
75
+ this.#hide(this._delays.touch.hide);
76
+ };
77
+ this[internals].role = 'tooltip';
78
+ }
79
+ render() {
80
+ return html `<slot @slotchange="${this.#handleSlotChange}"></slot>`;
81
+ }
82
+ handleControlChange(prev = null, next = null) {
83
+ const eventHandlers = {
84
+ focusin: this.#handleFocusIn,
85
+ focusout: this.#handleFocusOut,
86
+ pointerenter: this.#handlePointerEnter,
87
+ pointerleave: this.#handlePointerLeave,
88
+ touchstart: this.#handleTouchStart,
89
+ touchend: this.#handleTouchEnd,
90
+ };
91
+ Object.keys(eventHandlers).forEach((key) => {
92
+ prev?.removeEventListener(key, eventHandlers[key]);
93
+ next?.addEventListener(key, eventHandlers[key]);
94
+ });
95
+ if (prev)
96
+ prev.removeAttribute('aria-label');
97
+ if (next)
98
+ next.setAttribute('aria-label', this.textContent ?? '');
99
+ }
100
+ set visible(value) {
101
+ if (value) {
102
+ this.popoverController.animateOpen();
103
+ }
104
+ else {
105
+ this.popoverController.animateClose();
106
+ }
107
+ }
108
+ #handleSlotChange;
109
+ #handleFocusIn;
110
+ #handleFocusOut;
111
+ #handlePointerEnter;
112
+ #handlePointerLeave;
113
+ #handleTouchStart;
114
+ #handleTouchEnd;
115
+ #show(delay) {
116
+ clearTimeout(this.#closeTimer);
117
+ this.#openTimer = setTimeout(() => {
118
+ this.visible = true;
119
+ }, Math.max(Date.now() - lastHidingTime < this._delays.recentlyShowed ? 0 : delay));
120
+ }
121
+ #hide(delay) {
122
+ if (this.popoverController.open) {
123
+ lastHidingTime = Date.now();
124
+ }
125
+ clearTimeout(this.#openTimer);
126
+ this.#closeTimer = setTimeout(() => {
127
+ this.visible = false;
128
+ }, delay);
129
+ }
130
+ }
131
+ __decorate([
132
+ property({ reflect: true })
133
+ ], Tooltip.prototype, "align", void 0);
134
+ __decorate([
135
+ property({ type: Number, reflect: true })
136
+ ], Tooltip.prototype, "offset", void 0);
137
+ __decorate([
138
+ query('slot')
139
+ ], Tooltip.prototype, "$slot", void 0);
@@ -0,0 +1,13 @@
1
+ import { isServer } from 'lit';
2
+ export let focusVisible = false;
3
+ export function setFocusVisible(value) {
4
+ focusVisible = value;
5
+ }
6
+ if (!isServer) {
7
+ window.addEventListener('keydown', () => (focusVisible = true), {
8
+ capture: true,
9
+ });
10
+ window.addEventListener('mousedown', () => (focusVisible = false), {
11
+ capture: true,
12
+ });
13
+ }
@@ -0,0 +1 @@
1
+ type Constructor<T> = new (...args: any[]) => T;
@@ -0,0 +1,11 @@
1
+ let uniqueIdCounters = {};
2
+ /**
3
+ * Generates a unique ID with an prefix.
4
+ */
5
+ export function genUniqueId(prefix) {
6
+ if (!(prefix in uniqueIdCounters)) {
7
+ uniqueIdCounters[prefix] = 0;
8
+ }
9
+ const id = `${prefix}-${uniqueIdCounters[prefix]++}`;
10
+ return id;
11
+ }
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const commonButtonStyles = css `:host{--md-focus-ring-shape:calc(var(--_h)/2);background-color:color-mix(in srgb,var(--_background-color)var(--_background-opacity,100%),transparent);border-radius:calc(var(--_h)/2);color:color-mix(in srgb,var(--_text-color)var(--_text-opacity,100%),transparent);font:var(--md-sys-typography-label-large);gap:var(--_gap);height:var(--_h);min-width:64px;padding-inline:var(--_pad);transition:box-shadow var(--md-sys-motion-std-effects-slow-duration)var(--md-sys-motion-std-effects-slow),border-radius var(--md-sys-motion-std-effects-slow-duration)var(--md-sys-motion-std-effects-slow)}:host([trailingicon]){flex-direction:row-reverse}:host([variant=elevated]){--_background-color:var(--md-sys-color-surface-container-low);--_text-color:var(--_color);box-shadow:var(--md-sys-elevation-shadow-1)}:host([variant=outlined]){padding-inline:calc(var(--_pad) - 1px)}:host([variant=text]){padding-inline:12px;--_background-color:transparent!important}@media (hover:hover) and (pointer:fine){:host([variant=filled]:hover:not(:active)),:host([variant=tonal]:hover:not(:active)){box-shadow:var(--md-sys-elevation-shadow-1)}:host([variant=elevated]:hover:not(:active)){box-shadow:var(--md-sys-elevation-shadow-2)}}:host(:disabled){--_background-color:var(--md-sys-color-on-surface);--_background-opacity:10%;--_text-color:var(--md-sys-color-on-surface);--_text-opacity:38%;box-shadow:none;cursor:default;pointer-events:none}:host([variant=outlined]:disabled){--_background-color:var(--md-sys-color-outline)!important}[part~=icon]{fill:currentColor;block-size:1em;font-size:var(--_icon-size);inline-size:1em}`;
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const commonButtonToggleStyles = css `:host([variant=filled]:not(:state(checked))){--_background-color:var(--md-sys-color-surface-container);--_text-color:var(--md-sys-color-on-surface-variant)}:host([variant=tonal]:not(:state(checked))){--_background-color:var(--md-sys-color-surface-container-highest);--_text-color:var(--md-sys-color-on-surface-variant)}:host([variant=outlined]:state(checked)){--_background-color:var(--md-sys-color-inverse-surface);--_outline-color:var(--md-sys-color-inverse-surface);--_text-color:var(--md-sys-color-inverse-on-surface)}`;