@vollowx/seele 0.13.3 → 0.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,20 +51,22 @@ Check out [Overview](./docs/00-Overview.md).
51
51
  seele relies on the following modern web standards:
52
52
 
53
53
  - [`ElementInternals`](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), Baseline 2023
54
- - [Constructable Stylesheets](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet), Baseline 2024, C[^1] 90, F 126
55
54
  - [`:dir()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:dir), Baseline 2023, C 120, F 49
55
+ - [Constructable Stylesheets](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet), Baseline 2024, C[^1] 90, F 126
56
56
  - [`:state()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:state), Baseline 2024, C 125, F 126
57
+ - [`ariaActiveDescendantElement`](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/ariaActiveDescendantElement) and other similar ones, Baseline 2025, C 135, F 136
57
58
 
58
59
  And all that result in:
59
60
 
60
- - Chromium: >= 125
61
- - Firefox: >= 126
61
+ - Chromium: >= 135
62
+ - Firefox: >= 136
62
63
 
63
64
  It is 2026 now, you don't really need to worry about this. But in the future,
64
65
  these following web features might be used and require higher browser versions:
65
66
 
66
67
  - [anchor()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/anchor), Baseline 2026, C 125, F 147, will remove the dependency `floating-dom`
67
68
  - [`::view-*`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::view-transition), Baseline 2025, C 111, F 144, will optimize some animations for menu, dialog, etc
69
+ - [`CSSNumericValue`](https://developer.mozilla.org/en-US/docs/Web/API/CSSNumericValue), not Baseline, C 66, F Nightly, worked around currently
68
70
 
69
71
  [^1]: C = Chromium, F = Firefox.
70
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vollowx/seele",
3
- "version": "0.13.3",
3
+ "version": "0.13.5",
4
4
  "description": "A extensible Web Components library, with Material Design 3 components pre-provided.",
5
5
  "author": "vollowx",
6
6
  "license": "Apache-2.0",
@@ -9,8 +9,7 @@
9
9
  "module": "./src/all.js",
10
10
  "exports": {
11
11
  ".": "./src/all.js",
12
- "./base/*": "./src/base/*",
13
- "./m3/*": "./src/m3/*"
12
+ "./*.js": "./src/*.js"
14
13
  },
15
14
  "files": [
16
15
  "src/**/*.js",
package/src/all.js CHANGED
@@ -1,34 +1,34 @@
1
- // TODO: export core, base elements, mixins and controllers
2
- export { M3Autocomplete } from './m3/autocomplete/autocomplete.js';
3
- export { M3Badge } from './m3/badge/badge.js';
4
- export { M3Button } from './m3/button/common-button.js';
5
- export { M3ButtonToggle } from './m3/button/common-button-toggle.js';
6
- export { M3IconButton } from './m3/button/icon-button.js';
7
- export { M3IconButtonToggle } from './m3/button/icon-button-toggle.js';
8
- export { M3StandardButtonGroup } from './m3/button-group/standard-button-group.js';
9
- // export { M3ConnectedButtonGroup } from './m3/button-group/connected-button-group.js';
10
- export { M3Checkbox } from './m3/checkbox/checkbox.js';
11
- export { M3Dialog } from './m3/dialog/dialog.js';
12
- export { M3Divider } from './m3/divider/divider.js';
13
- export { M3Fab } from './m3/fab/fab.js';
14
- export { M3FocusRing } from './m3/focus-ring/focus-ring.js';
15
- export { M3Item } from './m3/item/item.js';
16
- export { M3List } from './m3/list/list.js';
17
- export { M3ListItem } from './m3/list/list-item.js';
18
- export { M3LoadingIndicator } from './m3/loading-indicator/loading-indicator.js';
19
- export { M3Menu } from './m3/menu/menu.js';
20
- export { M3MenuItem } from './m3/menu/menu-item.js';
21
- export { M3Ripple } from './m3/ripple/ripple.js';
22
- export { MdFilledSelect } from './m3/select/filled-select.js';
23
- export { MdOutlinedSelect } from './m3/select/outlined-select.js';
24
- export { M3Option } from './m3/select/option.js';
25
- export { M3Slider } from './m3/slider/slider.js';
26
- export { M3Switch } from './m3/switch/switch.js';
27
- export { M3Tabs } from './m3/tab/tabs.js';
28
- export { M3Tab } from './m3/tab/tab.js';
29
- export { M3TabPanel } from './m3/tab/tab-panel.js';
30
- export { M3FilledTextField } from './m3/text-field/filled-text-field.js';
31
- export { M3OutlinedTextField } from './m3/text-field/outlined-text-field.js';
32
- export { M3Toolbar } from './m3/toolbar/toolbar.js';
33
- export { M3Tooltip } from './m3/tooltip/tooltip.js';
34
- export { Win98Button } from './win98/button/button.js';
1
+ import './m3/autocomplete/autocomplete.js';
2
+ import './m3/badge/badge.js';
3
+ import './m3/button/common-button.js';
4
+ import './m3/button/common-button-toggle.js';
5
+ import './m3/button/icon-button.js';
6
+ import './m3/button/icon-button-toggle.js';
7
+ import './m3/button-group/standard-button-group.js';
8
+ import './m3/button-group/connected-button-group.js';
9
+ import './m3/checkbox/checkbox.js';
10
+ import './m3/dialog/dialog.js';
11
+ import './m3/divider/divider.js';
12
+ import './m3/fab/fab.js';
13
+ import './m3/focus-ring/focus-ring.js';
14
+ import './m3/item/item.js';
15
+ import './m3/list/list.js';
16
+ import './m3/list/list-item.js';
17
+ import './m3/loading-indicator/loading-indicator.js';
18
+ import './m3/menu/menu.js';
19
+ import './m3/menu/menu-item.js';
20
+ import './m3/radio/radio.js';
21
+ import './m3/ripple/ripple.js';
22
+ import './m3/select/filled-select.js';
23
+ import './m3/select/outlined-select.js';
24
+ import './m3/select/option.js';
25
+ import './m3/slider/slider.js';
26
+ import './m3/switch/switch.js';
27
+ import './m3/tab/tabs.js';
28
+ import './m3/tab/tab.js';
29
+ import './m3/tab/tab-panel.js';
30
+ import './m3/text-field/filled-text-field.js';
31
+ import './m3/text-field/outlined-text-field.js';
32
+ import './m3/toolbar/toolbar.js';
33
+ import './m3/tooltip/tooltip.js';
34
+ import './win98/button/button.js';
@@ -111,11 +111,18 @@ const Base = FocusDelegated(InternalsAttached(LitElement));
111
111
  }
112
112
  }
113
113
  handleMenuSelect(event) {
114
+ // FIXME: pressing <Enter> in a 'both' mode autocomplete triggers this
115
+ // function, which overrides the current value in the text field
116
+ // since the selected value in the menu is not the same as the
117
+ // one in autocomplete
118
+ //
119
+ // if (this.mode === 'list') {
114
120
  const selectedItem = event.detail.item;
115
121
  const newValue = selectedItem.getAttribute('value') || selectedItem.textContent?.trim() || '';
116
122
  if (this.$input) {
117
123
  this.$input.value = newValue;
118
124
  }
125
+ // }
119
126
  if (!this.keepOpenSelect) {
120
127
  this.open = false;
121
128
  }
@@ -28,12 +28,18 @@ export class Button extends Base {
28
28
  };
29
29
  this[internals].role = 'button';
30
30
  this.updateState();
31
+ // This is set to prevent a first-paint-time transition for styles like
32
+ // `border-radius` on `:host`. Also in `ToggleButton`.
33
+ // Double `requestAnimationFrame` is necessary since the callback inside is
34
+ // called before the nth frame is printed.
35
+ this.setAttribute('notransition', '');
31
36
  }
32
37
  connectedCallback() {
33
38
  super.connectedCallback();
34
39
  this.addEventListener('keydown', this.#handleKeyDown);
35
40
  this.addEventListener('keyup', this.#handleKeyUp);
36
41
  this.addEventListener('click', this.#handleClick);
42
+ requestAnimationFrame(()=>requestAnimationFrame(()=>this.removeAttribute('notransition')));
37
43
  }
38
44
  disconnectedCallback() {
39
45
  super.disconnectedCallback();
@@ -45,10 +45,10 @@ export class Checkbox extends Base {
45
45
  this.addEventListener('keyup', this.#handleKeyUp);
46
46
  }
47
47
  disconnectedCallback() {
48
- super.disconnectedCallback();
49
48
  this.removeEventListener('click', this.#handleClick);
50
49
  this.removeEventListener('keydown', this.#handleKeyDown);
51
50
  this.removeEventListener('keyup', this.#handleKeyUp);
51
+ super.disconnectedCallback();
52
52
  }
53
53
  updated(changed) {
54
54
  if (changed.has('checked') || changed.has('disabled') || changed.has('indeterminate') || changed.has('required')) {
@@ -94,6 +94,12 @@ export class Checkbox extends Base {
94
94
  detail: this.checked
95
95
  }));
96
96
  }
97
+ formResetCallback() {
98
+ this.checked = this.hasAttribute('checked');
99
+ }
100
+ formStateRestoreCallback(state, _reason) {
101
+ this.checked = state === 'on';
102
+ }
97
103
  }
98
104
  _ts_decorate([
99
105
  property({
package/src/base/list.js CHANGED
@@ -45,11 +45,11 @@ const Base = InternalsAttached(LitElement);
45
45
  },
46
46
  wrapNavigation: ()=>false
47
47
  });
48
+ this[internals].role = 'listbox';
49
+ if (!this.hasAttribute('tabindex')) {
50
+ this.setAttribute('tabindex', '0');
51
+ }
48
52
  if (!isServer) {
49
- this[internals].role = 'listbox';
50
- if (!this.hasAttribute('tabindex')) {
51
- this.setAttribute('tabindex', '0');
52
- }
53
53
  this.addEventListener('keydown', (e)=>this.#handleKeyDown(e));
54
54
  this.addEventListener('focusin', ()=>this.#handleFocusIn());
55
55
  this.addEventListener('focusout', ()=>this.#handleFocusOut());
@@ -0,0 +1,162 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { LitElement } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
+ import { FormAssociated } from './mixins/form-associated.js';
5
+ import { InternalsAttached, internals } from './mixins/internals-attached.js';
6
+ export class Radio extends FormAssociated(InternalsAttached(LitElement)) {
7
+ // Static hidden registry: Scope (Form or RootNode) -> Name -> Set<Radio>
8
+ static #registry = new Map();
9
+ #currentScope;
10
+ #currentName;
11
+ constructor(){
12
+ super(), this.#currentScope = null, this.#currentName = '', this.checked = false;
13
+ this[internals].role = 'radio';
14
+ }
15
+ connectedCallback() {
16
+ super.connectedCallback();
17
+ this.#registerToGroup();
18
+ this.#updateAriaState();
19
+ this.addEventListener('click', this.#handleClick);
20
+ this.addEventListener('keydown', this.#handleKeyDown);
21
+ }
22
+ disconnectedCallback() {
23
+ this.removeEventListener('click', this.#handleClick);
24
+ this.removeEventListener('keydown', this.#handleKeyDown);
25
+ this.#unregisterFromGroup();
26
+ super.disconnectedCallback();
27
+ }
28
+ updated(changedProperties) {
29
+ super.updated(changedProperties);
30
+ if (changedProperties.has('name')) {
31
+ this.#unregisterFromGroup();
32
+ this.#registerToGroup();
33
+ }
34
+ if (changedProperties.has('checked')) {
35
+ if (this.checked) {
36
+ this.#uncheckSiblings();
37
+ }
38
+ this[internals].setFormValue(this.checked ? this.value : null);
39
+ this.#updateGroupTabIndices();
40
+ this.dispatchEvent(new Event('change', {
41
+ bubbles: true,
42
+ composed: true
43
+ }));
44
+ }
45
+ if (changedProperties.has('disabled') || changedProperties.has('checked')) this.#updateAriaState();
46
+ }
47
+ #handleClick() {
48
+ if (this.disabled || this.checked) return;
49
+ this.checked = true;
50
+ this.focus();
51
+ }
52
+ #handleKeyDown(event) {
53
+ if (this.disabled) return;
54
+ switch(event.key){
55
+ case ' ':
56
+ event.preventDefault();
57
+ if (!this.checked) this.checked = true;
58
+ break;
59
+ case 'ArrowDown':
60
+ case 'ArrowRight':
61
+ event.preventDefault();
62
+ this.#navigateGroup(1);
63
+ break;
64
+ case 'ArrowUp':
65
+ case 'ArrowLeft':
66
+ event.preventDefault();
67
+ this.#navigateGroup(-1);
68
+ break;
69
+ }
70
+ }
71
+ #getScope() {
72
+ return this.form ?? this.getRootNode();
73
+ }
74
+ #registerToGroup() {
75
+ if (!this.name) return;
76
+ const scope = this.#getScope();
77
+ this.#currentScope = scope;
78
+ this.#currentName = this.name;
79
+ let scopeMap = Radio.#registry.get(scope);
80
+ if (!scopeMap) {
81
+ scopeMap = new Map();
82
+ Radio.#registry.set(scope, scopeMap);
83
+ }
84
+ let group = scopeMap.get(this.name);
85
+ if (!group) {
86
+ group = new Set();
87
+ scopeMap.set(this.name, group);
88
+ }
89
+ group.add(this);
90
+ this.#updateGroupTabIndices(group);
91
+ }
92
+ #unregisterFromGroup() {
93
+ if (!this.#currentScope || !this.#currentName) return;
94
+ const scopeMap = Radio.#registry.get(this.#currentScope);
95
+ if (scopeMap) {
96
+ const group = scopeMap.get(this.#currentName);
97
+ if (group) {
98
+ group.delete(this);
99
+ if (group.size === 0) {
100
+ scopeMap.delete(this.#currentName);
101
+ } else {
102
+ this.#updateGroupTabIndices(group);
103
+ }
104
+ }
105
+ if (scopeMap.size === 0) Radio.#registry.delete(this.#currentScope);
106
+ }
107
+ this.#currentScope = null;
108
+ this.#currentName = '';
109
+ }
110
+ #getGroupRadios() {
111
+ if (!this.name) return [
112
+ this
113
+ ];
114
+ const scope = this.#getScope();
115
+ const group = Radio.#registry.get(scope)?.get(this.name);
116
+ return group ? Array.from(group) : [
117
+ this
118
+ ];
119
+ }
120
+ #uncheckSiblings() {
121
+ const radios = this.#getGroupRadios();
122
+ for (const radio of radios)if (radio !== this && radio.checked) radio.checked = false;
123
+ }
124
+ #updateGroupTabIndices(groupSet) {
125
+ const radios = groupSet ? Array.from(groupSet) : this.#getGroupRadios();
126
+ if (radios.length === 0) return;
127
+ const hasChecked = radios.some((r)=>r.checked && !r.disabled);
128
+ let enabledFound = false;
129
+ for (const radio of radios){
130
+ const active = !radio.disabled && (hasChecked ? radio.checked : !enabledFound);
131
+ if (active) enabledFound = true;
132
+ radio.tabIndex = active ? 0 : -1;
133
+ }
134
+ }
135
+ #navigateGroup(direction) {
136
+ const radios = this.#getGroupRadios().filter((r)=>!r.disabled);
137
+ if (radios.length <= 1) return;
138
+ const currentIndex = radios.indexOf(this);
139
+ const nextIndex = (currentIndex + direction + radios.length) % radios.length;
140
+ const targetRadio = radios[nextIndex];
141
+ targetRadio.checked = true;
142
+ targetRadio.focus();
143
+ }
144
+ #updateAriaState() {
145
+ this[internals].ariaChecked = String(this.checked);
146
+ this[internals].ariaDisabled = String(this.disabled);
147
+ }
148
+ formResetCallback() {
149
+ this.checked = this.hasAttribute('checked');
150
+ }
151
+ }
152
+ _ts_decorate([
153
+ property({
154
+ type: String
155
+ })
156
+ ], Radio.prototype, "value", void 0);
157
+ _ts_decorate([
158
+ property({
159
+ type: Boolean,
160
+ reflect: true
161
+ })
162
+ ], Radio.prototype, "checked", void 0);
package/src/base/tabs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
- import { LitElement, html, isServer } from 'lit';
2
+ import { LitElement, html } from 'lit';
3
3
  import { property, queryAssignedElements } from 'lit/decorators.js';
4
4
  import { internals } from './mixins/internals-attached.js';
5
5
  /**
@@ -16,10 +16,8 @@ import { internals } from './mixins/internals-attached.js';
16
16
  }
17
17
  connectedCallback() {
18
18
  super.connectedCallback();
19
- if (!isServer) {
20
- this.addEventListener('keydown', this.#handleKeyDown.bind(this));
21
- this.addEventListener('click', this.#handleClick.bind(this));
22
- }
19
+ this.addEventListener('keydown', this.#handleKeyDown.bind(this));
20
+ this.addEventListener('click', this.#handleClick.bind(this));
23
21
  }
24
22
  updated(changed) {
25
23
  if (changed.has('selected') && this.selected) {
@@ -17,18 +17,18 @@ export class ToggleButton extends Base {
17
17
  }
18
18
  constructor(){
19
19
  super(), this.checked = false, /**
20
- * **Drag-and-drop to the other side**
20
+ * --> Drag-and-drop to the other side
21
21
  *
22
- * *`checked` is supposed to be changed once*
22
+ * `checked` is supposed to be changed once
23
23
  *
24
24
  * 1. `pointerdown`: _ignoreClick = false
25
25
  * 2. `pointermove`: _ignoreClick = true
26
26
  * 3. `pointerup`: `checked` changed
27
27
  * 4. `click`: ignored, _ignoreClick = false
28
28
  *
29
- * **Drag-and-drop to the other side, then click**
29
+ * --> Drag-and-drop to the other side, then click
30
30
  *
31
- * *`checked` is supposed to be changed twice*
31
+ * `checked` is supposed to be changed twice
32
32
  *
33
33
  * 1. `pointerdown`: _ignoreClick = false
34
34
  * 2. `pointermove`: _ignoreClick = true
@@ -36,9 +36,9 @@ export class ToggleButton extends Base {
36
36
  * 4. `click`: ignored, _ignoreClick = false
37
37
  * 5. `click`: `checked` changed(2)
38
38
  *
39
- * **Drag-and-drop to the other side, then click the label**
39
+ * --> Drag-and-drop to the other side, then click the label
40
40
  *
41
- * *`checked` is supposed to be changed twice*
41
+ * `checked` is supposed to be changed twice
42
42
  *
43
43
  * 1. `pointerdown`: _ignoreClick = false
44
44
  * 2. `pointermove`: _ignoreClick = true
@@ -70,6 +70,7 @@ export class ToggleButton extends Base {
70
70
  this[internals].role = 'button';
71
71
  this.checked = this.hasAttribute('checked');
72
72
  this._updateInternals();
73
+ this.setAttribute('notransition', '');
73
74
  }
74
75
  connectedCallback() {
75
76
  super.connectedCallback();
@@ -79,15 +80,16 @@ export class ToggleButton extends Base {
79
80
  this.labels.forEach((label)=>{
80
81
  label.addEventListener('click', this.#handleLabelClick);
81
82
  });
83
+ requestAnimationFrame(()=>requestAnimationFrame(()=>this.removeAttribute('notransition')));
82
84
  }
83
85
  disconnectedCallback() {
84
- super.disconnectedCallback();
85
86
  this.removeEventListener('click', this.#handleClick);
86
87
  this.removeEventListener('keydown', this.#handleKeyDown);
87
88
  this.removeEventListener('keyup', this.#handleKeyUp);
88
89
  this.labels.forEach((label)=>{
89
90
  label.removeEventListener('click', this.#handleLabelClick);
90
91
  });
92
+ super.disconnectedCallback();
91
93
  }
92
94
  updated(changed) {
93
95
  if (changed.has('checked') || changed.has('disabled')) {
@@ -116,6 +118,12 @@ export class ToggleButton extends Base {
116
118
  detail: this.checked
117
119
  }));
118
120
  }
121
+ formResetCallback() {
122
+ this.checked = this.hasAttribute('checked');
123
+ }
124
+ formStateRestoreCallback(state, _reason) {
125
+ this.checked = state === 'on';
126
+ }
119
127
  }
120
128
  _ts_decorate([
121
129
  property({
@@ -2,7 +2,7 @@ import { isServer } from 'lit';
2
2
  import { customElement } from 'lit/decorators.js';
3
3
  const wrappedCustomElement = (tagName, allowSSR = true)=>{
4
4
  if (isServer && !allowSSR) {
5
- console.log(`[SEELE]: <${tagName}> will not be rendered server-side.`);
5
+ console.log(`[seele] <${tagName}> will not be rendered server-side`);
6
6
  return (cls)=>cls;
7
7
  } else {
8
8
  return customElement(tagName);
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const commonButtonStyles = css`@layer common-button{:host{--_padding:16px;--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);min-width:64px;padding-inline:var(--_padding);transition:box-shadow var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default), border-radius var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);gap:8px}:host([size=xsmall]){--_padding:12px;gap:4px}:host([size=medium]){--_padding:24px;font:var(--md-sys-typography-title-medium);gap:8px}:host([size=large]){--_padding:48px;font:var(--md-sys-typography-headline-small);gap:12px}:host([size=xlarge]){--_padding:64px;font:var(--md-sys-typography-headline-medium);gap:16px}:host([trailing-icon]){flex-direction:row-reverse}:host([variant=outlined]){padding-inline:calc(var(--_padding) - 1px)}:host([variant=text]){padding-inline:calc(var(--_padding) / 4 * 3)}[part~=icon]{fill:currentColor;block-size:1em;font-size:var(--_icon-size);inline-size:1em}}@layer override{:host([variant=elevated]){--_background-color:var(--md-sys-color-surface-container-low);--_text-color:var(--_color);box-shadow:var(--md-sys-elevation-shadow-1)}@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)}}}`;
2
+ export const commonButtonStyles = css`@layer common-button{:host{--_padding:16px;--md-focus-ring-shape:calc(var(--_h) / 2);background-color:color-mix(in srgb, var(--_background-color) var(--_background-opacity,100%), transparent);color:color-mix(in srgb, var(--_text-color) var(--_text-opacity,100%), transparent);font:var(--md-sys-typography-label-large);min-width:64px;padding-inline:var(--_padding);transition:box-shadow var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default), border-radius var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);gap:8px}:host([size=xs]){--_padding:12px;gap:4px}:host([size=m]){--_padding:24px;font:var(--md-sys-typography-title-medium);gap:8px}:host([size=l]){--_padding:48px;font:var(--md-sys-typography-headline-small);gap:12px}:host([size=xl]){--_padding:64px;font:var(--md-sys-typography-headline-medium);gap:16px}:host([trailing-icon]){flex-direction:row-reverse}:host([variant=outlined]){padding-inline:calc(var(--_padding) - 1px)}:host([variant=text]){padding-inline:calc(var(--_padding) / 4 * 3)}::slotted([slot=icon]),::slotted([slot=icon-checked]){fill:currentColor;block-size:1em;font-size:var(--_icon-size);inline-size:1em}}@layer override{:host([variant=elevated]){--_background-color:var(--md-sys-color-surface-container-low);--_text-color:var(--_color);box-shadow:var(--md-sys-elevation-shadow-1)}@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)}}}`;
@@ -22,7 +22,7 @@ export class M3ButtonToggle extends ToggleButton {
22
22
  return html`<md-focus-ring></md-focus-ring><md-ripple></md-ripple><span part="target"></span><slot part="icon unchecked" name="icon" aria-hidden="true"></slot><slot part="icon checked" name="icon-checked" aria-hidden="true"></slot><slot part="label unchecked"></slot><slot part="label checked" name="checked"></slot>`;
23
23
  }
24
24
  constructor(...args){
25
- super(...args), this.size = 'small', this.shape = 'rounded', this.color = 'primary', this.variant = 'filled', /**
25
+ super(...args), this.size = 's', this.square = false, this.color = 'primary', this.variant = 'filled', /**
26
26
  * Whether to show the icon at the end of the button.
27
27
  */ this.trailingIcon = false;
28
28
  }
@@ -34,9 +34,10 @@ _ts_decorate([
34
34
  ], M3ButtonToggle.prototype, "size", void 0);
35
35
  _ts_decorate([
36
36
  property({
37
- reflect: true
37
+ reflect: true,
38
+ type: Boolean
38
39
  })
39
- ], M3ButtonToggle.prototype, "shape", void 0);
40
+ ], M3ButtonToggle.prototype, "square", void 0);
40
41
  _ts_decorate([
41
42
  property({
42
43
  reflect: true
@@ -20,7 +20,7 @@ export class M3Button extends Button {
20
20
  return html`<md-focus-ring></md-focus-ring><md-ripple></md-ripple><span part="target"></span><slot part="icon" name="icon" aria-hidden="true"></slot><slot part="label"></slot>`;
21
21
  }
22
22
  constructor(...args){
23
- super(...args), this.size = 'small', this.shape = 'rounded', this.color = 'primary', this.variant = 'filled', /**
23
+ super(...args), this.size = 's', this.color = 'primary', this.variant = 'filled', /**
24
24
  * Whether to show the icon at the end of the button.
25
25
  */ this.trailingIcon = false;
26
26
  }
@@ -32,9 +32,10 @@ _ts_decorate([
32
32
  ], M3Button.prototype, "size", void 0);
33
33
  _ts_decorate([
34
34
  property({
35
- reflect: true
35
+ reflect: true,
36
+ type: Boolean
36
37
  })
37
- ], M3Button.prototype, "shape", void 0);
38
+ ], M3Button.prototype, "square", void 0);
38
39
  _ts_decorate([
39
40
  property({
40
41
  reflect: true
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const iconButtonStyles = css`@layer icon-button{:host{--_wn:32px;--_ww:52px;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);transition:border-radius var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);width:var(--_h)}:host([size=xsmall]){--_wn:28px;--_ww:40px}:host([size=medium]){--_wn:48px;--_ww:72px}:host([size=large]){--_wn:64px;--_ww:128px}:host([size=xlarge]){--_wn:104px;--_ww:184px}:host([width=narrow]){width:var(--_wn)}:host([width=wide]){width:var(--_ww)}::slotted(*){fill:currentColor;block-size:1em;font-size:var(--_icon-size);inline-size:1em}}@layer override{:host([size=small]){--_icon-size:24px}:host([variant=text]){--_text-color:var(--md-sys-color-on-surface-variant)}:host([variant=text]:state(checked)){--_text-color:var(--_color)}}`;
2
+ export const iconButtonStyles = css`@layer icon-button{:host{--_wn:32px;--_ww:52px;background-color:color-mix(in srgb, var(--_background-color) var(--_background-opacity,100%), transparent);color:color-mix(in srgb, var(--_text-color) var(--_text-opacity,100%), transparent);transition:border-radius var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast);width:var(--_h)}:host([size=xs]){--_wn:28px;--_ww:40px}:host([size=m]){--_wn:48px;--_ww:72px}:host([size=l]){--_wn:64px;--_ww:128px}:host([size=xl]){--_wn:104px;--_ww:184px}:host([width=narrow]){width:var(--_wn)}:host([width=wide]){width:var(--_ww)}::slotted(*){fill:currentColor;block-size:1em;font-size:var(--_icon-size);inline-size:1em}}@layer override{:host([size=small]){--_icon-size:24px}:host([variant=text]){--_text-color:var(--md-sys-color-on-surface-variant)}:host([variant=text]:state(checked)){--_text-color:var(--_color)}}`;
@@ -22,7 +22,7 @@ export class M3IconButtonToggle extends ToggleButton {
22
22
  return html`<md-focus-ring></md-focus-ring><md-ripple enterbehavior="always"></md-ripple><span part="target"></span><slot part="icon unchecked"></slot><slot part="icon checked" name="checked"></slot>`;
23
23
  }
24
24
  constructor(...args){
25
- super(...args), this.size = 'small', this.color = 'primary', this.variant = 'text', this.width = 'standard';
25
+ super(...args), this.size = 's', this.color = 'primary', this.variant = 'text', this.width = 'standard';
26
26
  }
27
27
  }
28
28
  _ts_decorate([
@@ -20,7 +20,7 @@ export class M3IconButton extends Button {
20
20
  return html`<md-focus-ring></md-focus-ring><md-ripple></md-ripple><span part="target"></span><slot part="icon"></slot>`;
21
21
  }
22
22
  constructor(...args){
23
- super(...args), this.size = 'small', this.color = 'primary', this.variant = 'text', this.width = 'standard';
23
+ super(...args), this.size = 's', this.color = 'primary', this.variant = 'text', this.width = 'standard';
24
24
  }
25
25
  }
26
26
  _ts_decorate([
@@ -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;--_rad-sq:12px;--_rad-pr:8px;--_text-color:var(--_on-color);--_background-color:var(--_color);--_outline-color:var(--md-sys-color-outline-variant);box-sizing:border-box;cursor:pointer;height:var(--_h);-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;vertical-align:top;outline:0;flex-shrink:0;justify-content:center;align-items:center;display:inline-flex;position:relative}:host([size=xsmall]){--_h:32px}:host([size=medium]){--_h:56px;--_icon-size:24px;--_rad-sq:16px;--_rad-pr:12px}:host([size=large]){--_h:96px;--_icon-size:32px;--_rad-sq:28px;--_rad-pr:16px}:host([size=xlarge]){--_h:136px;--_icon-size:40px;--_rad-sq:28px;--_rad-pr: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(--_outline-color) var(--_outline-opacity,100%), transparent);box-shadow:none;border-style:solid;border-width:1px}:host([variant=text]){--_text-color:var(--_color);--_background-color:transparent!important}:host([shape=square]){--md-focus-ring-shape:var(--_rad-sq);border-radius:var(--_rad-sq)}}@layer shared-toggle,override;@layer active{:host(:active){--md-focus-ring-shape:var(--_rad-pr);border-radius:var(--_rad-pr)}}@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%;--_outline-color:var(--md-sys-color-on-surface);--_outline-opacity:10%;box-shadow:none;pointer-events:none}}`;
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)}}@layer shared-toggle,override;@layer active{:host(: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}}`;
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const sharedButtonToggleStyles = css`@layer shared-toggle{:host{transition:border-radius var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast), box-shadow var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default), background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast), color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast)}:host(:state(checked)){--md-focus-ring-shape:var(--_rad-sq);border-radius:var(--_rad-sq)}:host(:state(checked)) [part~=unchecked],:host(:not(:state(checked))) [part~=checked]{display:none}: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);--_text-color:var(--md-sys-color-inverse-on-surface);border-width:0}}`;
2
+ export const sharedButtonToggleStyles = css`@layer shared-toggle{:host{transition:border-radius var(--md-sys-motion-spatial-fast-duration) var(--md-sys-motion-spatial-fast), box-shadow var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default), background-color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast), color var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast)}:host(:state(checked)){--md-focus-ring-shape:var(--_rs);border-radius:var(--_rs)}:host(:state(checked)) [part~=unchecked],:host(:not(:state(checked))) [part~=checked]{display:none}: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);--_text-color:var(--md-sys-color-inverse-on-surface);border-width:0}}`;
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const connectedButtonGroupStyles = css`:host{gap:2px;display:inline-flex}::slotted(*){flex:1}::slotted(:not(:first-child)[size=xs]){--_inner-left-radius:4px}::slotted(:not(:last-child)[size=xs]){--_inner-right-radius:4px}::slotted(:not(:first-child)){--_inner-left-radius:8px}::slotted(:not(:last-child)){--_inner-right-radius:8px}::slotted(:not(:first-child)[size=l]){--_inner-left-radius:16px}::slotted(:not(:last-child)[size=l]){--_inner-right-radius:16px}::slotted(:not(:first-child)[size=xl]){--_inner-left-radius:20px}::slotted(:not(:last-child)[size=xl]){--_inner-right-radius:20px}::slotted(:first-child:state(checked):not(:active,[square])){border-start-start-radius:calc(var(--_h) / 2);border-end-start-radius:calc(var(--_h) / 2)}::slotted(:last-child:state(checked):not(:active,[square])){border-start-end-radius:calc(var(--_h) / 2);border-end-end-radius:calc(var(--_h) / 2)}`;
@@ -0,0 +1,37 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { LitElement, html } from 'lit';
3
+ import { property, queryAssignedElements } from 'lit/decorators.js';
4
+ import { customElement } from '../../core/decorators.js';
5
+ import { connectedButtonGroupStyles } from './connected-button-group-styles.css.js';
6
+ export class M3ConnectedButtonGroup extends LitElement {
7
+ static{
8
+ this.styles = [
9
+ connectedButtonGroupStyles
10
+ ];
11
+ }
12
+ render() {
13
+ return html`<slot @change="${this.#handleChange}"></slot>`;
14
+ }
15
+ #handleChange;
16
+ constructor(...args){
17
+ super(...args), this.multiple = false, this.#handleChange = (e)=>{
18
+ console.log('Boom!', e);
19
+ this.$buttons.forEach((btn, _)=>{
20
+ if (btn !== e.target && !this.multiple) // NOTE: there will not be an event dispatched and user should listen on
21
+ // the button group directly.
22
+ btn.checked = false;
23
+ });
24
+ };
25
+ }
26
+ }
27
+ _ts_decorate([
28
+ property({
29
+ type: Boolean
30
+ })
31
+ ], M3ConnectedButtonGroup.prototype, "multiple", void 0);
32
+ _ts_decorate([
33
+ queryAssignedElements()
34
+ ], M3ConnectedButtonGroup.prototype, "$buttons", void 0);
35
+ M3ConnectedButtonGroup = _ts_decorate([
36
+ customElement('md-connected-button-group')
37
+ ], M3ConnectedButtonGroup);
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const standardButtonGroupStyles = css`:host{gap:8px;display:inline-flex}`;
2
+ export const standardButtonGroupStyles = css`:host{gap:8px;display:inline-flex}:host([size=s]){gap:12px}:host([size=ss]){gap:18px}`;
@@ -1,7 +1,8 @@
1
1
  import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
2
  import { LitElement, html } from 'lit';
3
+ import { property, queryAssignedElements } from 'lit/decorators.js';
3
4
  import { customElement } from '../../core/decorators.js';
4
- import { queryAssignedElements } from 'lit/decorators.js';
5
+ import { getSpring } from '../system/motion.js';
5
6
  import { standardButtonGroupStyles } from './standard-button-group-styles.css.js';
6
7
  const EXPAND_FACTOR = 1.15;
7
8
  const shapeAnimation = Symbol('shapeAnimation');
@@ -74,11 +75,7 @@ export class M3StandardButtonGroup extends LitElement {
74
75
  else targetWidth = this.#baseWidths[i] - expansionDelta / 2;
75
76
  }
76
77
  }
77
- const styles = getComputedStyle(this);
78
- const rawDuration = styles.getPropertyValue('--md-sys-motion-spatial-fast-duration');
79
- const rawEasing = styles.getPropertyValue('--md-sys-motion-spatial-fast');
80
- const duration = parseInt(rawDuration.substring(0, rawDuration.length - 2)) || 0;
81
- const easing = rawEasing || 'ease';
78
+ const spring = getSpring(this, 'spatial', 'fast');
82
79
  if (btn[shapeAnimation]) btn[shapeAnimation].cancel();
83
80
  btn[shapeAnimation] = btn.animate([
84
81
  {
@@ -88,8 +85,7 @@ export class M3StandardButtonGroup extends LitElement {
88
85
  width: `${targetWidth}px`
89
86
  }
90
87
  ], {
91
- duration,
92
- easing,
88
+ ...spring,
93
89
  fill: 'forwards'
94
90
  });
95
91
  btn[shapeAnimation].onfinish = ()=>{
@@ -101,7 +97,7 @@ export class M3StandardButtonGroup extends LitElement {
101
97
  });
102
98
  }
103
99
  constructor(...args){
104
- super(...args), this.#activeIndex = -1, this.#baseWidths = [], this.#handleSlotChange = ()=>{
100
+ super(...args), this.size = '', this.#activeIndex = -1, this.#baseWidths = [], this.#handleSlotChange = ()=>{
105
101
  this.#resizeObserver.disconnect();
106
102
  this.$buttons.forEach((btn, i)=>{
107
103
  this.#baseWidths[i] = btn.getBoundingClientRect().width;
@@ -122,9 +118,14 @@ export class M3StandardButtonGroup extends LitElement {
122
118
  };
123
119
  }
124
120
  }
121
+ _ts_decorate([
122
+ property({
123
+ reflect: true
124
+ })
125
+ ], M3StandardButtonGroup.prototype, "size", void 0);
125
126
  _ts_decorate([
126
127
  queryAssignedElements()
127
128
  ], M3StandardButtonGroup.prototype, "$buttons", void 0);
128
129
  M3StandardButtonGroup = _ts_decorate([
129
- customElement('md-button-group', false)
130
+ customElement('md-button-group')
130
131
  ], M3StandardButtonGroup);
@@ -1,8 +1,9 @@
1
1
  import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
2
  import { html } from 'lit';
3
- import { customElement, property } from 'lit/decorators.js';
3
+ import { customElement, property, query } from 'lit/decorators.js';
4
4
  import { Checkbox } from '../../base/checkbox.js';
5
5
  import '../focus-ring/focus-ring.js';
6
+ import '../ripple/ripple.js';
6
7
  import { checkboxStyles } from './checkbox-styles.css.js';
7
8
  import { targetStyles } from '../target-styles.css.js';
8
9
  export class M3Checkbox extends Checkbox {
@@ -19,6 +20,13 @@ export class M3Checkbox extends Checkbox {
19
20
  renderIcon() {
20
21
  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>`;
21
22
  }
23
+ connectedCallback() {
24
+ super.connectedCallback();
25
+ // SSR'd <md-checkbox> components don't have their labels set up on time
26
+ this.updateComplete.then(()=>{
27
+ this.$ripple.attach(this, true);
28
+ });
29
+ }
22
30
  constructor(...args){
23
31
  super(...args), this.error = false;
24
32
  }
@@ -29,6 +37,9 @@ _ts_decorate([
29
37
  reflect: true
30
38
  })
31
39
  ], M3Checkbox.prototype, "error", void 0);
40
+ _ts_decorate([
41
+ query('md-ripple')
42
+ ], M3Checkbox.prototype, "$ripple", void 0);
32
43
  M3Checkbox = _ts_decorate([
33
44
  customElement('md-checkbox')
34
45
  ], M3Checkbox);
@@ -0,0 +1,179 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { html } from 'lit';
3
+ import { query, state, customElement } from 'lit/decorators.js';
4
+ import { Dialog } from '../../base/dialog.js';
5
+ import { getSpring } from '../system/motion.js';
6
+ import { dialogStyles } from './dialog-styles.css.js';
7
+ export class M3Dialog extends Dialog {
8
+ static{
9
+ this.styles = [
10
+ dialogStyles
11
+ ];
12
+ }
13
+ render() {
14
+ 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>`;
15
+ }
16
+ #activeAnimations;
17
+ #clearAnimations() {
18
+ this.#activeAnimations.forEach((anim)=>anim.cancel());
19
+ this.#activeAnimations = [];
20
+ this.#opening = this.#closing = false;
21
+ }
22
+ #handleClick;
23
+ #handleIconSlotChange;
24
+ get #springs() {
25
+ return {
26
+ spatialDefault: getSpring(this, 'spatial', 'default'),
27
+ spatialFast: getSpring(this, 'spatial', 'fast'),
28
+ effectsDefault: getSpring(this, 'effects', 'default'),
29
+ effectsFast: getSpring(this, 'effects', 'fast')
30
+ };
31
+ }
32
+ #opening;
33
+ show() {
34
+ if (this.#opening) return;
35
+ this.#clearAnimations();
36
+ this.#opening = true;
37
+ // Reset margins to allow browser centering
38
+ this.$dialog.style.marginTop = 'auto';
39
+ this.$dialog.style.marginBottom = 'auto';
40
+ super.show();
41
+ const actionsHeight = this.$actions.offsetHeight;
42
+ this.$actionsPlaceholder.style.height = `${actionsHeight}px`;
43
+ this.$container.style.height = 'auto';
44
+ const startHeight = 0;
45
+ const endHeight = this.$container.offsetHeight;
46
+ // Lock the position to prevent auto-centering during animation
47
+ const rect = this.$container.getBoundingClientRect();
48
+ this.$dialog.style.marginTop = `${rect.top}px`;
49
+ this.$dialog.style.marginBottom = 'auto';
50
+ this.$container.style.minHeight = '0px';
51
+ const container = this.$container.animate([
52
+ {
53
+ height: `${startHeight}px`,
54
+ transform: `translateY(-40px)`
55
+ },
56
+ {
57
+ height: `${endHeight}px`,
58
+ transform: 'translateY(0px)'
59
+ }
60
+ ], this.#springs.spatialDefault);
61
+ const scrim = this.$scrim.animate([
62
+ {
63
+ opacity: 0
64
+ },
65
+ {
66
+ opacity: 0.32
67
+ }
68
+ ], {
69
+ ...this.#springs.effectsDefault,
70
+ fill: 'forwards'
71
+ });
72
+ const body = this.$body.animate([
73
+ {
74
+ opacity: 0.2
75
+ },
76
+ {
77
+ opacity: 1
78
+ }
79
+ ], {
80
+ ...this.#springs.effectsDefault,
81
+ fill: 'forwards'
82
+ });
83
+ const actions = this.$actions.animate([
84
+ {
85
+ opacity: 0.5
86
+ },
87
+ {
88
+ opacity: 1
89
+ }
90
+ ], {
91
+ ...this.#springs.effectsDefault,
92
+ fill: 'forwards'
93
+ });
94
+ this.#activeAnimations.push(container, scrim, body, actions);
95
+ container.onfinish = ()=>{
96
+ this.$container.style.minHeight = '';
97
+ this.#clearAnimations();
98
+ };
99
+ }
100
+ #closing;
101
+ close() {
102
+ if (this.#closing) return;
103
+ this.#clearAnimations();
104
+ this.#closing = true;
105
+ const startHeight = this.$container.offsetHeight;
106
+ const endHeight = startHeight * 0.35;
107
+ this.$container.style.minHeight = '0px';
108
+ const containerSpatial = this.$container.animate([
109
+ {
110
+ height: `${startHeight}px`,
111
+ transform: 'translateY(0px)'
112
+ },
113
+ {
114
+ height: `${endHeight}px`,
115
+ transform: `translateY(-40px)`
116
+ }
117
+ ], this.#springs.spatialFast);
118
+ const containerEffects = this.$container.animate([
119
+ {
120
+ opacity: 1
121
+ },
122
+ {
123
+ opacity: 0
124
+ }
125
+ ], {
126
+ ...this.#springs.effectsFast,
127
+ fill: 'forwards'
128
+ });
129
+ const scrim = this.$scrim.animate([
130
+ {
131
+ opacity: 0.32
132
+ },
133
+ {
134
+ opacity: 0
135
+ }
136
+ ], {
137
+ ...this.#springs.effectsFast,
138
+ fill: 'forwards'
139
+ });
140
+ this.#activeAnimations.push(containerSpatial, containerEffects, scrim);
141
+ containerEffects.onfinish = ()=>{
142
+ this.$container.style.minHeight = '';
143
+ super.close();
144
+ this.#clearAnimations();
145
+ };
146
+ }
147
+ constructor(...args){
148
+ super(...args), this.hasIcon = false, this.#activeAnimations = [], this.#handleClick = (e)=>{
149
+ const target = e.target;
150
+ if (!this.$container.contains(target) && !this.contains(target)) this.close();
151
+ }, this.#handleIconSlotChange = (e)=>{
152
+ const slot = e.target;
153
+ this.hasIcon = slot.assignedElements({
154
+ flatten: true
155
+ }).length > 0;
156
+ }, this.#opening = false, this.#closing = false;
157
+ }
158
+ }
159
+ _ts_decorate([
160
+ state()
161
+ ], M3Dialog.prototype, "hasIcon", void 0);
162
+ _ts_decorate([
163
+ query('[part=container]')
164
+ ], M3Dialog.prototype, "$container", void 0);
165
+ _ts_decorate([
166
+ query('[part=body]')
167
+ ], M3Dialog.prototype, "$body", void 0);
168
+ _ts_decorate([
169
+ query('[part=actions]')
170
+ ], M3Dialog.prototype, "$actions", void 0);
171
+ _ts_decorate([
172
+ query('.actions-placeholder')
173
+ ], M3Dialog.prototype, "$actionsPlaceholder", void 0);
174
+ _ts_decorate([
175
+ query('[part=scrim]')
176
+ ], M3Dialog.prototype, "$scrim", void 0);
177
+ M3Dialog = _ts_decorate([
178
+ customElement('md-dialog')
179
+ ], M3Dialog);
@@ -172,16 +172,17 @@ export class M3Dialog extends Dialog {
172
172
  };
173
173
  }
174
174
  constructor(...args){
175
- super(...args), this._config = {
175
+ super(...args), // Was based on https://m3.material.io/styles/motion/easing-and-duration/applying-easing-and-duration#e5b958f0-435d-4e84-aed4-8d1ea395fa5c
176
+ // TODO: use curves and durations from motion physics
177
+ this._config = {
176
178
  openEase: 'cubic-bezier(0, 0, 0, 1)',
177
179
  closeEase: 'cubic-bezier(0.3, 0, 1, 1)',
178
- vertSlide: 36,
180
+ vertSlide: 40,
179
181
  openDur: 500,
180
182
  closeDur: 200,
181
183
  bodyFadeInDur: 200,
182
184
  bodyFadeOutDur: 100
183
- } /* Perhaps make them constants? I don't think that there will be anyone
184
- making classes inheriting this class... */ , this.hasIcon = false, this.#activeAnimations = [], this.#handleClick = (e)=>{
185
+ }, this.hasIcon = false, this.#activeAnimations = [], this.#handleClick = (e)=>{
185
186
  const target = e.target;
186
187
  if (!this.$container.contains(target) && !this.contains(target)) this.close();
187
188
  }, this.#handleIconSlotChange = (e)=>{
@@ -1,2 +1,2 @@
1
1
  import { css } from 'lit';
2
- export const fabStyles = css`:host{--md-focus-ring-shape:var(--_border-radius);--_text-color:var(--md-sys-color-on-primary);--_background-color:var(--md-sys-color-primary);--_size:56px;--_border-radius:16px;--_icon-size:24px;background-color:color-mix(in srgb, var(--_background-color) var(--_background-opacity,100%), transparent);border-radius:var(--_border-radius);box-shadow:var(--md-sys-elevation-shadow-3);box-sizing:border-box;color:color-mix(in srgb, var(--_text-color) var(--_text-opacity,100%), transparent);cursor:pointer;font:var(--md-sys-typography-label-large);height:var(--_size);min-width:var(--_size);padding-inline:calc((var(--_size) - var(--_icon-size)) / 2);-webkit-tap-highlight-color:transparent;transition:box-shadow var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default);-webkit-user-select:none;user-select:none;vertical-align:middle;outline:0;justify-content:center;align-items:center;gap:8px;display:inline-flex;position:relative}:host([size=medium]){--_size:80px;--_border-radius:20px;--_icon-size:28px}:host([size=large]){--_size:96px;--_border-radius:28px;--_icon-size:36px}:host([color=primary-container]){--_text-color:var(--md-sys-color-on-primary-container);--_background-color:var(--md-sys-color-primary-container)}:host([color=secondary-container]){--_text-color:var(--md-sys-color-on-secondary-container);--_background-color:var(--md-sys-color-secondary-container)}:host([color=tertiary-container]){--_text-color:var(--md-sys-color-on-tertiary-container);--_background-color:var(--md-sys-color-tertiary-container)}:host([color=secondary]){--_text-color:var(--md-sys-color-on-secondary);--_background-color:var(--md-sys-color-secondary)}:host([color=tertiary]){--_text-color:var(--md-sys-color-on-tertiary);--_background-color:var(--md-sys-color-tertiary)}:host([color=surface]){--_text-color:var(--md-sys-color-primary);--_background-color:var(--md-sys-color-surface-container-high)}:host(:disabled){--_background-color:var(--md-sys-color-on-surface);--_background-opacity:12%;--_text-color:var(--md-sys-color-on-surface);--_text-opacity:38%;box-shadow:none;cursor:default;pointer-events:none}@media (hover:hover) and (pointer:fine){:host(:hover:not(:active)){box-shadow:var(--md-sys-elevation-shadow-4)}}::slotted(:not([slot=label])){fill:currentColor;block-size:1em;font-size:var(--_icon-size);inline-size:1em;display:inline-flex}::slotted([slot=label]){margin-inline:4px}`;
2
+ export const fabStyles = css`:host{--md-focus-ring-shape:var(--_border-radius);--_text-color:var(--md-sys-color-on-primary);--_background-color:var(--md-sys-color-primary);--_size:56px;--_border-radius:16px;--_icon-size:24px;background-color:color-mix(in srgb, var(--_background-color) var(--_background-opacity,100%), transparent);border-radius:var(--_border-radius);box-shadow:var(--md-sys-elevation-shadow-3);box-sizing:border-box;color:color-mix(in srgb, var(--_text-color) var(--_text-opacity,100%), transparent);cursor:pointer;font:var(--md-sys-typography-label-large);height:var(--_size);min-width:var(--_size);padding-inline:calc((var(--_size) - var(--_icon-size)) / 2);-webkit-tap-highlight-color:transparent;transition:box-shadow var(--md-sys-motion-effects-default-duration) var(--md-sys-motion-effects-default);-webkit-user-select:none;user-select:none;vertical-align:middle;outline:0;justify-content:center;align-items:center;gap:8px;display:inline-flex;position:relative}md-ripple,md-focus-ring{position:absolute}:host([size=m]){--_size:80px;--_border-radius:20px;--_icon-size:28px}:host([size=l]){--_size:96px;--_border-radius:28px;--_icon-size:36px}:host([color=primary-container]){--_text-color:var(--md-sys-color-on-primary-container);--_background-color:var(--md-sys-color-primary-container)}:host([color=secondary-container]){--_text-color:var(--md-sys-color-on-secondary-container);--_background-color:var(--md-sys-color-secondary-container)}:host([color=tertiary-container]){--_text-color:var(--md-sys-color-on-tertiary-container);--_background-color:var(--md-sys-color-tertiary-container)}:host([color=secondary]){--_text-color:var(--md-sys-color-on-secondary);--_background-color:var(--md-sys-color-secondary)}:host([color=tertiary]){--_text-color:var(--md-sys-color-on-tertiary);--_background-color:var(--md-sys-color-tertiary)}:host([color=surface]){--_text-color:var(--md-sys-color-primary);--_background-color:var(--md-sys-color-surface-container-high)}:host(:disabled){--_background-color:var(--md-sys-color-on-surface);--_background-opacity:12%;--_text-color:var(--md-sys-color-on-surface);--_text-opacity:38%;box-shadow:none;pointer-events:none}@media (hover:hover) and (pointer:fine){:host(:hover:not(:active)){box-shadow:var(--md-sys-elevation-shadow-4)}}::slotted(:not([slot=label])){fill:currentColor;block-size:1em;font-size:var(--_icon-size);inline-size:1em}::slotted([slot=label]){margin-inline:4px}`;
package/src/m3/fab/fab.js CHANGED
@@ -18,7 +18,8 @@ export class M3Fab extends Button {
18
18
  return html`<md-focus-ring></md-focus-ring><md-ripple></md-ripple><span part="target"></span><slot part="icon" aria-hidden="true"></slot><slot part="label" name="label"></slot>`;
19
19
  }
20
20
  constructor(...args){
21
- super(...args), this.size = 'default', this.color = 'primary';
21
+ super(...args), this.size = 's' // s for default
22
+ , this.color = 'primary';
22
23
  }
23
24
  }
24
25
  _ts_decorate([
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const radioStyles = css`:host{--_default:var(--md-sys-motion-effects-default-duration) 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}:host:before,:host:after{content:"";box-sizing:border-box;border-radius:9999px;position:absolute}:host:before{z-index:1;background-color:var(--md-sys-color-on-surface-variant);width:20px;height:20px;transition:background var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast)}.middle{background-color:var(--md-sys-color-surface);z-index:2;width:16px;height:16px;animation:uncheck-middle-2 var(--_slow) var(--md-sys-motion-effects-default-duration);border-radius:9999px}:host:after{z-index:3;background-color:var(--md-sys-color-primary);width:10px;height:10px;animation:uncheck-inner-1 var(--_default), uncheck-inner-2 var(--_slow) var(--md-sys-motion-effects-default-duration);transform:scale(0)}@keyframes uncheck-middle-2{0%{z-index:4;transform:scale(0)}to{z-index:4;transform:scale(1)}}@keyframes uncheck-inner-1{0%{background-color:var(--md-sys-color-primary);transform:scale(1)}to{background-color:var(--md-sys-color-on-surface-variant);transform:scale(1.6)}}@keyframes uncheck-inner-2{0%{background-color:var(--md-sys-color-on-surface-variant);transform:scale(1.6)}to{background-color:var(--md-sys-color-on-surface-variant);transform:scale(1.6)}}:host([checked]):before{background-color:var(--md-sys-color-primary)}:host([checked]) .middle{animation:check-middle-1 var(--_default), check-middle-2 var(--_slow) var(--md-sys-motion-effects-default-duration);animation-fill-mode:forwards}:host([checked]):after{animation:check-inner-2 var(--_slow) var(--md-sys-motion-effects-default-duration);animation-fill-mode:forwards}@keyframes check-middle-1{0%{transform:scale(1)}to{transform:scale(0)}}@keyframes check-middle-2{0%{transform:scale(.625)}to{transform:scale(1)}}@keyframes check-inner-2{0%,to{transform:scale(1)}}:host([disabled]){pointer-events:none}:host([disabled]):before,:host([disabled]):after{background:color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent)}`;
@@ -0,0 +1,22 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { html } from 'lit';
3
+ import { Radio } from '../../base/radio.js';
4
+ import { customElement } from '../../core/decorators.js';
5
+ import { radioStyles } from './radio-styles.css.js';
6
+ import { targetStyles } from '../target-styles.css.js';
7
+ import '../focus-ring/focus-ring.js';
8
+ import '../ripple/ripple.js';
9
+ export class M3Radio extends Radio {
10
+ static{
11
+ this.styles = [
12
+ targetStyles,
13
+ radioStyles
14
+ ];
15
+ }
16
+ render() {
17
+ return html`<md-focus-ring></md-focus-ring><md-ripple></md-ripple><span part="target"></span> <span class="middle"></span>`;
18
+ }
19
+ }
20
+ M3Radio = _ts_decorate([
21
+ customElement('md-radio')
22
+ ], M3Radio);
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Workaround for `CSSNumericValue.parse()` not being supported by Firefox
3
+ * https://developer.mozilla.org/en-US/docs/Web/API/CSSNumericValue/parse_static
4
+ */ export const parseCSSNum = (css)=>{
5
+ if (typeof CSSNumericValue !== 'undefined') {
6
+ try {
7
+ return CSSNumericValue.parse(css).to('ms').value;
8
+ } catch {
9
+ return 0;
10
+ }
11
+ } else {
12
+ css = css.trim().toLowerCase();
13
+ if (css.endsWith('ms')) return parseFloat(css.substring(0, css.length - 2)) | 0;
14
+ else if (css.endsWith('s')) return parseFloat(css.substring(0, css.length - 1)) * 1000 | 0;
15
+ else return 0;
16
+ }
17
+ };
18
+ /**
19
+ * https://m3.material.io/styles/motion/overview/specs
20
+ */ export const getSpring = (element, type, duration)=>{
21
+ const styles = getComputedStyle(element);
22
+ const get = (suffix)=>styles.getPropertyValue(`--md-sys-motion-${type}-${duration}${suffix}`);
23
+ return {
24
+ easing: get('') || 'ease',
25
+ duration: parseCSSNum(get('-duration'))
26
+ };
27
+ };
@@ -0,0 +1,8 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { Radio } from '../../base/radio.js';
3
+ import { customElement } from '../../core/decorators.js';
4
+ export class Win98Radio extends Radio {
5
+ }
6
+ Win98Radio = _ts_decorate([
7
+ customElement('win98-radio')
8
+ ], Win98Radio);
@@ -0,0 +1,8 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { Radio } from '../../base/radio.js';
3
+ import { customElement } from '../../core/decorators.js';
4
+ export class Win98Radio extends Radio {
5
+ }
6
+ Win98Radio = _ts_decorate([
7
+ customElement('win98-radio')
8
+ ], Win98Radio);