@vollowx/seele 0.13.4 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vollowx/seele",
3
- "version": "0.13.4",
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",
package/src/all.js CHANGED
@@ -17,6 +17,7 @@ import './m3/list/list-item.js';
17
17
  import './m3/loading-indicator/loading-indicator.js';
18
18
  import './m3/menu/menu.js';
19
19
  import './m3/menu/menu-item.js';
20
+ import './m3/radio/radio.js';
20
21
  import './m3/ripple/ripple.js';
21
22
  import './m3/select/filled-select.js';
22
23
  import './m3/select/outlined-select.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,16 +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);
37
- // This is set to prevent a first-paint-time transition for styles like
38
- // `border-radius` on `:host`. Also in `ToggleButton`.
39
- this.setAttribute('notransition', '');
40
- requestAnimationFrame(()=>this.removeAttribute('notransition'));
42
+ requestAnimationFrame(()=>requestAnimationFrame(()=>this.removeAttribute('notransition')));
41
43
  }
42
44
  disconnectedCallback() {
43
45
  super.disconnectedCallback();
@@ -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);
@@ -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,8 +80,7 @@ export class ToggleButton extends Base {
79
80
  this.labels.forEach((label)=>{
80
81
  label.addEventListener('click', this.#handleLabelClick);
81
82
  });
82
- this.setAttribute('notransition', '');
83
- requestAnimationFrame(()=>this.removeAttribute('notransition'));
83
+ requestAnimationFrame(()=>requestAnimationFrame(()=>this.removeAttribute('notransition')));
84
84
  }
85
85
  disconnectedCallback() {
86
86
  this.removeEventListener('click', this.#handleClick);
@@ -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);
@@ -3,7 +3,7 @@ import { LitElement, html } from 'lit';
3
3
  import { property, queryAssignedElements } from 'lit/decorators.js';
4
4
  import { customElement } from '../../core/decorators.js';
5
5
  import { connectedButtonGroupStyles } from './connected-button-group-styles.css.js';
6
- export class M3StandardButtonGroup extends LitElement {
6
+ export class M3ConnectedButtonGroup extends LitElement {
7
7
  static{
8
8
  this.styles = [
9
9
  connectedButtonGroupStyles
@@ -28,10 +28,10 @@ _ts_decorate([
28
28
  property({
29
29
  type: Boolean
30
30
  })
31
- ], M3StandardButtonGroup.prototype, "multiple", void 0);
31
+ ], M3ConnectedButtonGroup.prototype, "multiple", void 0);
32
32
  _ts_decorate([
33
33
  queryAssignedElements()
34
- ], M3StandardButtonGroup.prototype, "$buttons", void 0);
35
- M3StandardButtonGroup = _ts_decorate([
36
- customElement('md-connected-button-group', false)
37
- ], M3StandardButtonGroup);
34
+ ], M3ConnectedButtonGroup.prototype, "$buttons", void 0);
35
+ M3ConnectedButtonGroup = _ts_decorate([
36
+ customElement('md-connected-button-group')
37
+ ], M3ConnectedButtonGroup);
@@ -127,5 +127,5 @@ _ts_decorate([
127
127
  queryAssignedElements()
128
128
  ], M3StandardButtonGroup.prototype, "$buttons", void 0);
129
129
  M3StandardButtonGroup = _ts_decorate([
130
- customElement('md-button-group', false)
130
+ customElement('md-button-group')
131
131
  ], M3StandardButtonGroup);
@@ -3,6 +3,7 @@ import { html } from 'lit';
3
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 {
@@ -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,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);