@vollowx/seele 0.12.7 → 0.12.9

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
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![npm package](https://img.shields.io/npm/v/%40vollowx%2Fseele)](https://www.npmjs.com/package/@vollowx/seele)
4
4
  [![builds.sr.ht status](https://builds.sr.ht/~lucaz/seele.svg)](https://builds.sr.ht/~lucaz/seele?)
5
+ [![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/@vollowx/seele)
5
6
 
6
7
  Seele (**S**tandard **E**xtensible **Ele**ments) is a extensible
7
8
  [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)
@@ -11,7 +12,7 @@ It also provides styled components in the following design guideline(s):
11
12
 
12
13
  - [Material You Expressive](https://m3.material.io/)
13
14
 
14
- Documentations and demos are at [seele.projects.v9.nz](https://seele.projects.v9.nz/).
15
+ Documentations and demos are at [tideover.cc/seele/](https://tideover.cc/seele/).
15
16
 
16
17
  ## Installation
17
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vollowx/seele",
3
- "version": "0.12.7",
3
+ "version": "0.12.9",
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",
@@ -27,7 +27,7 @@
27
27
  "lit",
28
28
  "ui-components"
29
29
  ],
30
- "homepage": "https://seele.projects.v9.nz",
30
+ "homepage": "https://tideover.cc/seele/",
31
31
  "repository": {
32
32
  "type": "git",
33
33
  "url": "https://github.com/vollowx/seele.git"
@@ -46,13 +46,13 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@custom-elements-manifest/analyzer": "^0.11.0",
49
- "@swc/core": "^1.15.40",
49
+ "@swc/core": "^1.15.43",
50
50
  "@types/bun": "^1.3.14",
51
51
  "@web/dev-server": "^0.4.6",
52
52
  "@web/dev-server-esbuild": "^1.0.5",
53
53
  "chokidar": "^5.0.0",
54
54
  "lightningcss": "^1.32.0",
55
- "prettier": "^3.8.3"
55
+ "prettier": "^3.9.1"
56
56
  },
57
57
  "dependencies": {
58
58
  "@floating-ui/dom": "^1.7.6",
package/src/all.js CHANGED
@@ -1,3 +1,4 @@
1
+ // TODO: export core, base elements, mixins and controllers
1
2
  export { M3Autocomplete } from './m3/autocomplete/autocomplete.js';
2
3
  export { M3Button } from './m3/button/common-button.js';
3
4
  export { M3ButtonToggle } from './m3/button/common-button-toggle.js';
@@ -18,6 +19,9 @@ export { MdOutlinedSelect } from './m3/select/outlined-select.js';
18
19
  export { M3Option } from './m3/select/option.js';
19
20
  export { M3Slider } from './m3/slider/slider.js';
20
21
  export { M3Switch } from './m3/switch/switch.js';
22
+ export { M3Tabs } from './m3/tab/tabs.js';
23
+ export { M3Tab } from './m3/tab/tab.js';
24
+ export { M3TabPanel } from './m3/tab/tab-panel.js';
21
25
  export { M3FilledTextField } from './m3/text-field/filled-text-field.js';
22
26
  export { M3OutlinedTextField } from './m3/text-field/outlined-text-field.js';
23
27
  export { M3Toolbar } from './m3/toolbar/toolbar.js';
@@ -16,6 +16,7 @@ export class ListItem extends FormAssociated(InternalsAttached(LitElement)) {
16
16
  }
17
17
  connectedCallback() {
18
18
  super.connectedCallback();
19
+ this.setAttribute('seele-base', 'option');
19
20
  this[internals].role = this._role;
20
21
  this.setAttribute('tabindex', '-1');
21
22
  this.#updateInternals();
@@ -40,7 +41,7 @@ export class ListItem extends FormAssociated(InternalsAttached(LitElement)) {
40
41
  this.focused = false;
41
42
  }
42
43
  constructor(...args){
43
- super(...args), this.selected = false, this.focused = false, this._role = 'option';
44
+ super(...args), this._role = 'option', this.selected = false, this.focused = false;
44
45
  }
45
46
  }
46
47
  _ts_decorate([
package/src/base/list.js CHANGED
@@ -16,9 +16,15 @@ const Base = InternalsAttached(LitElement);
16
16
  get $items() {
17
17
  return this.listController.items || [];
18
18
  }
19
+ render() {
20
+ return html` ${this.renderItemSlot()} `;
21
+ }
22
+ renderItemSlot() {
23
+ return html`<slot part="items"></slot>`;
24
+ }
19
25
  constructor(){
20
- super(), this._possibleItemTags = [], this._scrollPadding = 0, this.noFocusControl = false, this.listController = new ListController(this, {
21
- isItem: (item)=>this._possibleItemTags.includes(item.tagName.toLowerCase()) && !item.hasAttribute('disabled') && !item.hidden,
26
+ super(), this._scrollPadding = 0, this.noFocusControl = false, this.listController = new ListController(this, {
27
+ isItem: (item)=>item.getAttribute('seele-base') === 'option' && !item.hasAttribute('disabled') && !item.hidden,
22
28
  getPossibleItems: ()=>this.slotItems,
23
29
  blurItem: (item)=>{
24
30
  item.focused = false;
@@ -52,12 +58,6 @@ const Base = InternalsAttached(LitElement);
52
58
  this.addEventListener('mouseover', (e)=>this.#handleMouseOver(e));
53
59
  }
54
60
  }
55
- render() {
56
- return html` ${this.renderItemSlot()} `;
57
- }
58
- renderItemSlot() {
59
- return html`<slot part="items"></slot>`;
60
- }
61
61
  updated(changed) {
62
62
  // TODO: Find somewhere to put this
63
63
  // this.listController.clearSearch();
@@ -107,7 +107,7 @@ const Base = InternalsAttached(LitElement);
107
107
  }
108
108
  #handleMouseOver(event) {
109
109
  setFocusVisible(false);
110
- const item = event.target.closest(this._possibleItemTags.join(','));
110
+ const item = event.target.closest('[seele-base="option"]');
111
111
  if (item && this.listController.items.includes(item)) {
112
112
  this.listController._focusItem(item);
113
113
  }
@@ -133,8 +133,7 @@ const Base = InternalsAttached(LitElement);
133
133
  }));
134
134
  }
135
135
  #getEventItem(event) {
136
- const selector = this._possibleItemTags.join(',');
137
- return event.target.closest(selector);
136
+ return event.target.closest('[seele-base="option"]');
138
137
  }
139
138
  get currentIndex() {
140
139
  return this.listController?.currentIndex;
package/src/base/menu.js CHANGED
@@ -187,7 +187,7 @@ const Base = FocusDelegated(InternalsAttached(Attachable(LitElement)));
187
187
  this.open = false;
188
188
  }
189
189
  constructor(...args){
190
- super(...args), this._possibleItemTags = [], this._durations = {
190
+ super(...args), this._durations = {
191
191
  show: 0,
192
192
  hide: 0
193
193
  }, this._scrollPadding = 0, this.type = 'menu', this.open = false, this.quick = false, this.offset = 0, this.align = 'bottom-start', this.alignStrategy = 'absolute', this.keepOpenBlur = false, this.keepOpenSelect = false, this.keepOpenClickAway = false, this.noFocusControl = false, this.tabIndex = 0, this.$lastFocused = null, this.popoverController = new PopoverController(this, {
@@ -207,10 +207,9 @@ const Base = FocusDelegated(InternalsAttached(Attachable(LitElement)));
207
207
  if (!this.keepOpenClickAway) this.open = false;
208
208
  }
209
209
  }), this.listController = new ListController(this, {
210
- isItem: (item)=>this._possibleItemTags.includes(item.tagName.toLowerCase()) && !item.hasAttribute('disabled') && !item.hidden,
210
+ isItem: (item)=>item.getAttribute('seele-base') === 'option' && !item.hasAttribute('disabled') && !item.hidden,
211
211
  getPossibleItems: ()=>this.slotItems,
212
212
  blurItem: (item)=>{
213
- console.log(item);
214
213
  item.focused = false;
215
214
  },
216
215
  focusItem: (item)=>{
@@ -42,55 +42,6 @@ const Base = FormAssociated(FocusDelegated(InternalsAttached(LitElement)));
42
42
  get selectedOptions() {
43
43
  return (this.getSelectedOptions() ?? []).map(([option])=>option);
44
44
  }
45
- constructor(){
46
- super(), // Form-related
47
- this.required = false, // Passed to field
48
- this.error = false, // Passed to menu
49
- this.quick = false, this.offset = 0, this.align = 'bottom-start', this.alignStrategy = 'absolute', this.open = false, /**
50
- * Text to display in the field. Only set for SSR.
51
- */ this.displayText = '', this[VALUE] = '', this.lastUserSetValue = null, this.lastUserSetSelectedIndex = null, this.lastSelectedOption = null, this.lastSelectedOptionRecords = [], this.#dispatchChangeEvent = ()=>{
52
- this.dispatchEvent(new Event('input', {
53
- bubbles: true,
54
- composed: true
55
- }));
56
- this.dispatchEvent(new Event('change', {
57
- bubbles: true
58
- }));
59
- };
60
- if (!isServer) {
61
- this.addEventListener('focusout', this.handleFocusOut);
62
- }
63
- }
64
- async firstUpdated(changed) {
65
- // If this has been handled on update already due to SSR, try again.
66
- if (!this.lastSelectedOptionRecords.length) {
67
- this.initUserSelection();
68
- }
69
- // Case for when the DOM is streaming, there are no children, and a child
70
- // has [selected] set on it, we need to wait for DOM to render something.
71
- if (!this.lastSelectedOptionRecords.length && !isServer && !this.$options.length) {
72
- setTimeout(()=>{
73
- this.updateValueAndDisplayText();
74
- }, 0);
75
- }
76
- super.firstUpdated(changed);
77
- }
78
- update(changed) {
79
- if (!this.hasUpdated) {
80
- this.initUserSelection();
81
- }
82
- super.update(changed);
83
- }
84
- updated(changed) {
85
- super.updated(changed);
86
- if (changed.has('open')) {
87
- if (this.open) {
88
- this.#focusSelectedItemOrFirst();
89
- } else {
90
- this.$field.setAttribute('aria-activedescendant', '');
91
- }
92
- }
93
- }
94
45
  render() {
95
46
  return html`${this.renderField()}${this.renderMenu()}`;
96
47
  }
@@ -145,31 +96,80 @@ const Base = FormAssociated(FocusDelegated(InternalsAttached(LitElement)));
145
96
  renderFieldContent() {
146
97
  return html`<span part="value">${this.displayText || html`&nbsp;`}</span>`;
147
98
  }
99
+ constructor(){
100
+ super(), // Form-related
101
+ this.required = false, // Passed to field
102
+ this.error = false, // Passed to menu
103
+ this.quick = false, this.offset = 0, this.align = 'bottom-start', this.alignStrategy = 'absolute', this.open = false, /**
104
+ * Text to display in the field. Only set for SSR.
105
+ */ this.displayText = '', this[VALUE] = '', this.lastUserSetValue = null, this.lastUserSetSelectedIndex = null, this.lastSelectedOption = null, this.lastSelectedOptionRecords = [], this.#dispatchChangeEvent = ()=>{
106
+ this.dispatchEvent(new Event('input', {
107
+ bubbles: true,
108
+ composed: true
109
+ }));
110
+ this.dispatchEvent(new Event('change', {
111
+ bubbles: true
112
+ }));
113
+ };
114
+ if (!isServer) {
115
+ this.addEventListener('focusout', this._handleFocusOut);
116
+ }
117
+ }
118
+ async firstUpdated(changed) {
119
+ // If this has been handled on update already due to SSR, try again.
120
+ if (!this.lastSelectedOptionRecords.length) {
121
+ this.initUserSelection();
122
+ }
123
+ // Case for when the DOM is streaming, there are no children, and a child
124
+ // has [selected] set on it, we need to wait for DOM to render something.
125
+ if (!this.lastSelectedOptionRecords.length && !isServer && !this.$options.length) {
126
+ setTimeout(()=>{
127
+ this.updateValueAndDisplayText();
128
+ }, 0);
129
+ }
130
+ super.firstUpdated(changed);
131
+ }
132
+ update(changed) {
133
+ if (!this.hasUpdated) {
134
+ this.initUserSelection();
135
+ }
136
+ super.update(changed);
137
+ }
138
+ updated(changed) {
139
+ super.updated(changed);
140
+ if (changed.has('open')) {
141
+ if (this.open) {
142
+ this.#focusSelectedItemOrFirst();
143
+ } else {
144
+ this.$field.setAttribute('aria-activedescendant', '');
145
+ }
146
+ }
147
+ }
148
148
  /**
149
149
  * TODO: Handle clear action
150
- */ handleFieldKeydown(event) {
150
+ */ _handleFieldKeydown(event) {
151
151
  if (this.disabled) return;
152
152
  const eventClone = new KeyboardEvent(event.type, event);
153
153
  eventClone.preventDefault = ()=>event.preventDefault();
154
154
  eventClone.stopPropagation = ()=>event.stopPropagation();
155
155
  this.$menu.$menu.dispatchEvent(eventClone);
156
156
  }
157
- handleMenuSelect(event) {
157
+ _handleMenuSelect(event) {
158
158
  if (this.selectItem(event.detail.item)) {
159
159
  this.#dispatchChangeEvent();
160
160
  }
161
161
  this.open = false;
162
162
  }
163
- handleMenuItemFocus(event) {
163
+ _handleMenuItemFocus(event) {
164
164
  this.$field.setAttribute('aria-activedescendant', event.detail.item.id);
165
165
  }
166
- handleFocusOut(event) {
166
+ _handleFocusOut(event) {
167
167
  const relatedTarget = event.relatedTarget;
168
168
  if (!this.contains(relatedTarget) && !this.$menu.contains(relatedTarget)) {
169
169
  this.open = false;
170
170
  }
171
171
  }
172
- handleSlotChange() {
172
+ _handleSlotChange() {
173
173
  // When slots change, check for initially selected items if value is not set
174
174
  if (!this.value) {
175
175
  this.updateValueAndDisplayText();
@@ -0,0 +1,25 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { LitElement, html } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
+ import { InternalsAttached, internals } from './mixins/internals-attached.js';
5
+ import { genUniqueId } from '../core/unique-id.js';
6
+ export class TabPanel extends InternalsAttached(LitElement) {
7
+ render() {
8
+ return html`<slot></slot>`;
9
+ }
10
+ connectedCallback() {
11
+ super.connectedCallback();
12
+ this.setAttribute('seele-base', 'tabpanel');
13
+ this[internals].role = this._role;
14
+ if (!this.id) this.id = genUniqueId('tabpanel');
15
+ }
16
+ constructor(...args){
17
+ super(...args), this._role = 'tabpanel', this.value = '';
18
+ }
19
+ }
20
+ _ts_decorate([
21
+ property({
22
+ type: String,
23
+ reflect: true
24
+ })
25
+ ], TabPanel.prototype, "value", void 0);
package/src/base/tab.js CHANGED
@@ -0,0 +1,59 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { LitElement, html } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
+ import { InternalsAttached, internals } from './mixins/internals-attached.js';
5
+ import { genUniqueId } from '../core/unique-id.js';
6
+ /**
7
+ * TODO: disabled
8
+ */ export class Tab extends InternalsAttached(LitElement) {
9
+ render() {
10
+ return html`<slot></slot>`;
11
+ }
12
+ connectedCallback() {
13
+ super.connectedCallback();
14
+ this.setAttribute('seele-base', 'tab');
15
+ this[internals].role = this._role;
16
+ if (!this.id) this.id = genUniqueId('tab');
17
+ this.#updateInternals();
18
+ }
19
+ updated(changed) {
20
+ super.updated(changed);
21
+ if (changed.has('selected') || changed.has('focused')) {
22
+ this.#updateInternals();
23
+ }
24
+ }
25
+ #updateInternals() {
26
+ this[internals].ariaSelected = this.selected ? 'true' : 'false';
27
+ this.focused ? this[internals].states.add('focused') : this[internals].states.delete('focused');
28
+ this.selected ? this[internals].states.add('selected') : this[internals].states.delete('selected');
29
+ }
30
+ focus() {
31
+ this.focused = true;
32
+ super.focus();
33
+ }
34
+ blur() {
35
+ this.focused = false;
36
+ super.blur();
37
+ }
38
+ constructor(...args){
39
+ super(...args), this._role = 'tab', this.selected = false, this.focused = false, this.value = '';
40
+ }
41
+ }
42
+ _ts_decorate([
43
+ property({
44
+ type: Boolean,
45
+ reflect: true
46
+ })
47
+ ], Tab.prototype, "selected", void 0);
48
+ _ts_decorate([
49
+ property({
50
+ type: Boolean,
51
+ reflect: true
52
+ })
53
+ ], Tab.prototype, "focused", void 0);
54
+ _ts_decorate([
55
+ property({
56
+ type: String,
57
+ reflect: true
58
+ })
59
+ ], Tab.prototype, "value", void 0);
@@ -0,0 +1,144 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { LitElement, html, isServer } from 'lit';
3
+ import { property, queryAssignedElements } from 'lit/decorators.js';
4
+ import { internals } from './mixins/internals-attached.js';
5
+ /**
6
+ * @fires {CustomEvent} tab-select
7
+ */ export class Tabs extends LitElement {
8
+ get $tabs() {
9
+ return this.$slotItems.filter((item)=>item[internals].role === 'tab');
10
+ }
11
+ get $parent() {
12
+ return this.parentNode instanceof ShadowRoot ? this.parentNode.host : this.parentElement;
13
+ }
14
+ render() {
15
+ return html`
16
+ <div part="tablist" role="tablist">
17
+ <slot @slotchange=${this.#handleSlotChange}></slot>
18
+ </div>
19
+ `;
20
+ }
21
+ connectedCallback() {
22
+ super.connectedCallback();
23
+ if (!isServer) {
24
+ this.addEventListener('keydown', this.#handleKeyDown.bind(this));
25
+ this.addEventListener('click', this.#handleClick.bind(this));
26
+ }
27
+ }
28
+ updated(changed) {
29
+ if (changed.has('selected') && this.selected) {
30
+ const targetTab = this.$tabs.find((t)=>t.value === this.selected);
31
+ if (targetTab && !targetTab.selected) {
32
+ this.selectTab(targetTab);
33
+ }
34
+ }
35
+ }
36
+ #handleSlotChange;
37
+ #handleKeyDown;
38
+ #handleClick;
39
+ focusTab(tab) {
40
+ this.$tabs.forEach((t)=>{
41
+ t.blur();
42
+ t.tabIndex = -1;
43
+ });
44
+ tab.tabIndex = 0;
45
+ tab.focus();
46
+ }
47
+ selectTab(selectedTab) {
48
+ this.selected = selectedTab.value;
49
+ const parent = this.$parent;
50
+ const panels = Array.from(parent.querySelectorAll('[seele-base="tabpanel"]'));
51
+ this.$tabs.forEach((tab)=>{
52
+ tab.selected = tab === selectedTab;
53
+ tab.tabIndex = tab === selectedTab ? 0 : -1;
54
+ });
55
+ panels.forEach((panel)=>{
56
+ if (panel.value && selectedTab.value) {
57
+ panel.hidden = panel.value !== selectedTab.value;
58
+ }
59
+ });
60
+ this.dispatchEvent(new CustomEvent('tab-select', {
61
+ detail: {
62
+ tab: selectedTab,
63
+ value: this.selected
64
+ },
65
+ bubbles: true,
66
+ composed: true
67
+ }));
68
+ }
69
+ constructor(...args){
70
+ super(...args), this._role = 'tablist', this.switch = 'manual', this.selected = '', this.#handleSlotChange = (_)=>{
71
+ const parent = this.$parent;
72
+ this.$tabs.forEach((tab)=>{
73
+ const panel = parent.querySelector(`[seele-base="tab"][value="${tab.value}"]`);
74
+ if (!panel) console.error('[seele] cannot find a matching panel for tab ', tab);
75
+ // TODO: ariaLabelledByElements
76
+ tab.setAttribute('aria-controls', panel.id);
77
+ panel.setAttribute('aria-labelledby', tab.id);
78
+ });
79
+ }, this.#handleKeyDown = (event)=>{
80
+ const tabs = this.$tabs;
81
+ if (!tabs.length) return;
82
+ const activeElement = this.getRootNode().activeElement;
83
+ const currentIndex = tabs.findIndex((t)=>t === activeElement || t.focused);
84
+ let nextIndex = currentIndex !== -1 ? currentIndex : 0;
85
+ let handled = false;
86
+ switch(event.key){
87
+ case 'ArrowLeft':
88
+ nextIndex = currentIndex <= 0 ? tabs.length - 1 : currentIndex - 1;
89
+ handled = true;
90
+ break;
91
+ case 'ArrowRight':
92
+ nextIndex = currentIndex >= tabs.length - 1 ? 0 : currentIndex + 1;
93
+ handled = true;
94
+ break;
95
+ case 'Home':
96
+ nextIndex = 0;
97
+ handled = true;
98
+ break;
99
+ case 'End':
100
+ nextIndex = tabs.length - 1;
101
+ handled = true;
102
+ break;
103
+ case 'Enter':
104
+ case ' ':
105
+ if (currentIndex !== -1) {
106
+ this.selectTab(tabs[currentIndex]);
107
+ }
108
+ handled = true;
109
+ break;
110
+ }
111
+ if (handled) {
112
+ event.preventDefault();
113
+ event.stopPropagation();
114
+ if (event.key !== 'Enter' && event.key !== ' ') {
115
+ this.focusTab(tabs[nextIndex]);
116
+ if (this.switch === 'auto') this.selectTab(tabs[nextIndex]);
117
+ }
118
+ }
119
+ }, this.#handleClick = (event)=>{
120
+ const target = event.target;
121
+ const tab = target.closest('md-tab');
122
+ if (tab && this.$tabs.includes(tab)) {
123
+ this.focusTab(tab);
124
+ this.selectTab(tab);
125
+ }
126
+ };
127
+ }
128
+ }
129
+ _ts_decorate([
130
+ property({
131
+ type: String
132
+ })
133
+ ], Tabs.prototype, "switch", void 0);
134
+ _ts_decorate([
135
+ property({
136
+ type: String,
137
+ reflect: true
138
+ })
139
+ ], Tabs.prototype, "selected", void 0);
140
+ _ts_decorate([
141
+ queryAssignedElements({
142
+ flatten: true
143
+ })
144
+ ], Tabs.prototype, "$slotItems", void 0);
@@ -69,7 +69,7 @@ export class ToggleButton extends Base {
69
69
  };
70
70
  this[internals].role = 'button';
71
71
  this.checked = this.hasAttribute('checked');
72
- this.updateInternals();
72
+ this._updateInternals();
73
73
  }
74
74
  connectedCallback() {
75
75
  super.connectedCallback();
@@ -91,10 +91,10 @@ export class ToggleButton extends Base {
91
91
  }
92
92
  updated(changed) {
93
93
  if (changed.has('checked') || changed.has('disabled')) {
94
- this.updateInternals();
94
+ this._updateInternals();
95
95
  }
96
96
  }
97
- updateInternals() {
97
+ _updateInternals() {
98
98
  this[internals].states.delete('unchecked');
99
99
  this[internals].states.delete('checked');
100
100
  this[internals].ariaPressed = this.checked ? 'true' : 'false';
@@ -10,6 +10,9 @@ const Base = Attachable(InternalsAttached(LitElement));
10
10
  /**
11
11
  * TODO: Check if $control controls a menu/popover and keep hidden when it is open.
12
12
  */ export class Tooltip extends Base {
13
+ render() {
14
+ return html`<slot @slotchange="${this.#handleSlotChange}"></slot>`;
15
+ }
13
16
  // Different from those in the popoverController, these timers are used to
14
17
  // manage the delay before showing/hiding the tooltip.
15
18
  #openTimer;
@@ -70,9 +73,6 @@ const Base = Attachable(InternalsAttached(LitElement));
70
73
  };
71
74
  this[internals].role = 'tooltip';
72
75
  }
73
- render() {
74
- return html`<slot @slotchange="${this.#handleSlotChange}"></slot>`;
75
- }
76
76
  handleControlChange(prev = null, next = null) {
77
77
  const eventHandlers = {
78
78
  focusin: this.#handleFocusIn,
@@ -12,12 +12,6 @@ import { fieldStyles } from './field-styles.css.js';
12
12
  fieldStyles
13
13
  ];
14
14
  }
15
- update(changedProperties) {
16
- if (changedProperties.has('label')) {
17
- this.hasLabel = !!this.label;
18
- }
19
- super.update(changedProperties);
20
- }
21
15
  render() {
22
16
  return html`
23
17
  <div class="container">${this.renderContainerContent()}</div>
@@ -46,6 +40,12 @@ import { fieldStyles } from './field-styles.css.js';
46
40
  @slotchange=${this.handleSlotChange}
47
41
  ></slot>`;
48
42
  }
43
+ update(changedProperties) {
44
+ if (changedProperties.has('label')) {
45
+ this.hasLabel = !!this.label;
46
+ }
47
+ super.update(changedProperties);
48
+ }
49
49
  handleSlotChange(e) {
50
50
  const slot = e.target;
51
51
  const hasContent = slot.assignedNodes({
@@ -7,28 +7,25 @@ import { Attachable } from '../../base/mixins/attachable.js';
7
7
  import { InternalsAttached, internals } from '../../base/mixins/internals-attached.js';
8
8
  import { focusRingStyles } from './focus-ring-styles.css.js';
9
9
  export class M3FocusRing extends Attachable(InternalsAttached(LitElement)) {
10
- constructor(){
11
- super(), this.inward = false, this.#boundFocusIn = this.#handleFocusIn.bind(this), this.#boundFocusOut = this.#handleFocusOut.bind(this), this.#boundPointerDown = this.#handlePointerDown.bind(this);
12
- this[internals].ariaHidden = 'true';
13
- }
14
10
  static{
15
11
  this.styles = [
16
12
  focusRingStyles
17
13
  ];
18
14
  }
19
- #boundFocusIn;
20
- #boundFocusOut;
21
- #boundPointerDown;
22
- #handleFocusIn() {
23
- if (focusVisible) this[internals].states.add('visible');
24
- else this[internals].states.delete('visible');
25
- }
26
- #handleFocusOut() {
27
- this[internals].states.delete('visible');
28
- }
29
- #handlePointerDown() {
30
- this[internals].states.delete('visible');
15
+ constructor(){
16
+ super(), this.inward = false, this.#handleFocusIn = ()=>{
17
+ if (focusVisible) this[internals].states.add('visible');
18
+ else this[internals].states.delete('visible');
19
+ }, this.#handleFocusOut = ()=>{
20
+ this[internals].states.delete('visible');
21
+ }, this.#handlePointerDown = ()=>{
22
+ this[internals].states.delete('visible');
23
+ };
24
+ this[internals].ariaHidden = 'true';
31
25
  }
26
+ #handleFocusIn;
27
+ #handleFocusOut;
28
+ #handlePointerDown;
32
29
  visualFocus() {
33
30
  this.#handleFocusIn();
34
31
  }
@@ -37,9 +34,9 @@ export class M3FocusRing extends Attachable(InternalsAttached(LitElement)) {
37
34
  }
38
35
  handleControlChange(prev = null, next = null) {
39
36
  const eventHandlers = {
40
- focusin: this.#boundFocusIn,
41
- focusout: this.#boundFocusOut,
42
- pointerdown: this.#boundPointerDown
37
+ focusin: this.#handleFocusIn,
38
+ focusout: this.#handleFocusOut,
39
+ pointerdown: this.#handlePointerDown
43
40
  };
44
41
  Object.keys(eventHandlers).forEach((eventName)=>{
45
42
  prev?.removeEventListener(eventName, eventHandlers[eventName]);
@@ -5,29 +5,12 @@ import { ListItem } from '../../base/list-item.js';
5
5
  import '../item/item.js';
6
6
  import { listItemStyles } from './list-item-styles.css.js';
7
7
  export class M3ListItem extends ListItem {
8
- constructor(){
9
- super();
10
- this.updateComplete.then(()=>{
11
- this.ripple.$control = this;
12
- this.focusRing.$control = this;
13
- });
14
- }
15
8
  static{
16
9
  this.styles = [
17
10
  ...super.styles,
18
11
  listItemStyles
19
12
  ];
20
13
  }
21
- updated(changed) {
22
- super.updated(changed);
23
- if (changed.has('focused')) {
24
- if (this.focused) {
25
- this.focusRing.visualFocus();
26
- } else {
27
- this.focusRing.visualBlur();
28
- }
29
- }
30
- }
31
14
  render() {
32
15
  return html`
33
16
  <md-item>
@@ -48,6 +31,23 @@ export class M3ListItem extends ListItem {
48
31
  </md-item>
49
32
  `;
50
33
  }
34
+ constructor(){
35
+ super();
36
+ this.updateComplete.then(()=>{
37
+ this.ripple.$control = this;
38
+ this.focusRing.$control = this;
39
+ });
40
+ }
41
+ updated(changed) {
42
+ super.updated(changed);
43
+ if (changed.has('focused')) {
44
+ if (this.focused) {
45
+ this.focusRing.visualFocus();
46
+ } else {
47
+ this.focusRing.visualBlur();
48
+ }
49
+ }
50
+ }
51
51
  }
52
52
  _ts_decorate([
53
53
  query('md-ripple')
@@ -4,18 +4,14 @@ import { List } from '../../base/list.js';
4
4
  import { listStyles } from './list-styles.css.js';
5
5
  export class M3List extends List {
6
6
  static{
7
- // FIXME: Might cause a long list to scroll more than expected
8
- // override readonly _scrollPadding = 4;
9
7
  this.styles = [
10
8
  listStyles
11
9
  ];
12
10
  }
13
11
  constructor(...args){
14
- super(...args), this._possibleItemTags = [
15
- 'md-list-item',
16
- 'md-list-item-checkbox',
17
- 'md-list-item-radio'
18
- ], this.color = 'standard';
12
+ super(...args), // FIXME: Might cause a long list to scroll more than expected
13
+ // override readonly _scrollPadding = 4;
14
+ this.color = 'standard';
19
15
  }
20
16
  }
21
17
  _ts_decorate([
@@ -5,22 +5,17 @@ import { Menu } from '../../base/menu.js';
5
5
  import { menuStyles } from './menu-styles.css.js';
6
6
  export class M3Menu extends Menu {
7
7
  static{
8
- // FIXME: Might cause a long list to scroll more than expected
9
- // override readonly _scrollPadding = 4;
10
8
  this.styles = [
11
9
  menuStyles
12
10
  ];
13
11
  }
14
12
  constructor(...args){
15
- super(...args), this._possibleItemTags = [
16
- 'md-menu-item',
17
- 'md-menu-item-checkbox',
18
- 'md-menu-item-radio',
19
- 'md-option'
20
- ], this._durations = {
13
+ super(...args), this._durations = {
21
14
  show: 300,
22
15
  hide: 150
23
- }, this.color = 'standard';
16
+ }, // FIXME: Might cause a long list to scroll more than expected
17
+ // override readonly _scrollPadding = 4;
18
+ this.color = 'standard';
24
19
  }
25
20
  }
26
21
  _ts_decorate([
@@ -23,7 +23,7 @@ export class MdFilledSelect extends M3Select {
23
23
  .focused=${this.open || this.focused}
24
24
  supportingtext=${this.supportingText}
25
25
  @click=${this.toggle}
26
- @keydown=${this.handleFieldKeydown}
26
+ @keydown=${this._handleFieldKeydown}
27
27
  @focus=${()=>this.focused = true}
28
28
  @blur=${()=>this.focused = false}
29
29
  tabindex=${this.disabled ? '-1' : '0'}
@@ -23,7 +23,7 @@ export class MdOutlinedSelect extends M3Select {
23
23
  .focused=${this.open || this.focused}
24
24
  supportingtext=${this.supportingText}
25
25
  @click=${this.toggle}
26
- @keydown=${this.handleFieldKeydown}
26
+ @keydown=${this._handleFieldKeydown}
27
27
  @focus=${()=>this.focused = true}
28
28
  @blur=${()=>this.focused = false}
29
29
  tabindex=${this.disabled ? '-1' : '0'}
@@ -23,10 +23,10 @@ import { Select } from '../../base/select.js';
23
23
  ?open="${this.open}"
24
24
  @open="${()=>this.open = true}"
25
25
  @close="${()=>this.open = false}"
26
- @item-focus="${this.handleMenuItemFocus}"
27
- @select="${this.handleMenuSelect}"
26
+ @item-focus="${this._handleMenuItemFocus}"
27
+ @select="${this._handleMenuSelect}"
28
28
  >
29
- <slot part="items" @slotchange=${this.handleSlotChange}></slot>
29
+ <slot part="items" @slotchange=${this._handleSlotChange}></slot>
30
30
  </md-menu>
31
31
  `;
32
32
  }
@@ -74,12 +74,12 @@ export class M3Slider extends Slider {
74
74
  <div class="inactive-after"></div>
75
75
  </div>
76
76
  ${this.ticks ? html`
77
- <div class="tickmarks">
78
- <div class="inactive-before"></div>
79
- <div class="active"></div>
80
- <div class="inactive-after"></div>
81
- </div>
82
- ` : nothing}
77
+ <div class="tickmarks">
78
+ <div class="inactive-before"></div>
79
+ <div class="active"></div>
80
+ <div class="inactive-after"></div>
81
+ </div>
82
+ ` : nothing}
83
83
  `;
84
84
  }
85
85
  renderLabel(value) {
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const tabPanelStyles = css`:host{display:block}:host([hidden]){display:none}`;
@@ -0,0 +1,14 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { customElement } from 'lit/decorators.js';
3
+ import { TabPanel } from '../../base/tab-panel.js';
4
+ import { tabPanelStyles } from './tab-panel-styles.css.js';
5
+ export class M3TabPanel extends TabPanel {
6
+ static{
7
+ this.styles = [
8
+ tabPanelStyles
9
+ ];
10
+ }
11
+ }
12
+ M3TabPanel = _ts_decorate([
13
+ customElement('md-tab-panel')
14
+ ], M3TabPanel);
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const tabStyles = css`:host{--md-focus-ring-shape:8px;box-sizing:border-box;color:var(--md-sys-color-on-surface-variant);cursor:pointer;font:var(--md-sys-typography-title-small);user-select:none;outline:none;flex-shrink:0;justify-content:center;padding:0 16px;display:inline-flex;position:relative}:host([selected]){color:var(--_tab-selected-color)}:host([selected]) md-focus-ring{margin-bottom:4px}[part=content]{flex-direction:var(--_tab-flex-dir,row);height:var(--_tab-height,48px);justify-content:center;align-items:center;gap:8px;display:inline-flex}[part~=icon]{fill:currentColor;block-size:1em;inline-size:1em;font-size:24px}`;
@@ -0,0 +1,33 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { html } from 'lit';
3
+ import { customElement, query } from 'lit/decorators.js';
4
+ import { Tab } from '../../base/tab.js';
5
+ import '../focus-ring/focus-ring.js';
6
+ import '../ripple/ripple.js';
7
+ import { targetStyles } from '../target-styles.css.js';
8
+ import { tabStyles } from './tab-styles.css.js';
9
+ export class M3Tab extends Tab {
10
+ static{
11
+ this.styles = [
12
+ targetStyles,
13
+ tabStyles
14
+ ];
15
+ }
16
+ render() {
17
+ return html`
18
+ <md-focus-ring inward></md-focus-ring>
19
+ <md-ripple inward></md-ripple>
20
+ <span part="target"></span>
21
+ <div part="content">
22
+ <slot part="icon" name="icon" aria-hidden="true"></slot>
23
+ <slot part="label"></slot>
24
+ </div>
25
+ `;
26
+ }
27
+ }
28
+ _ts_decorate([
29
+ query('[part="content"]')
30
+ ], M3Tab.prototype, "$content", void 0);
31
+ M3Tab = _ts_decorate([
32
+ customElement('md-tab')
33
+ ], M3Tab);
@@ -0,0 +1,2 @@
1
+ import { css } from 'lit';
2
+ export const tabsStyles = css`:host{--_tab-selected-color:var(--md-sys-color-primary);border-bottom:1px solid var(--md-sys-color-outline-variant);box-sizing:border-box;scroll-behavior:smooth;scrollbar-width:none;display:block;position:relative;overflow:auto hidden}[part=tablist]{display:flex}[part=indicator]{background-color:var(--md-sys-color-primary);height:3px;transition:var(--md-sys-motion-std-effects-slow-duration) var(--md-sys-motion-std-effects-slow);will-change:left, width;border-radius:3px 3px 0 0;position:absolute;bottom:0}::slotted(*){flex:1}:host([secondary]){--_tab-selected-color:var(--md-sys-color-on-surface)}:host([secondary]) [part=indicator]{border-radius:0;height:2px}:host([iconsabove]){--_tab-flex-dir:column;--_tab-height:64px}`;
@@ -0,0 +1,48 @@
1
+ import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
+ import { html } from 'lit';
3
+ import { customElement, property, query } from 'lit/decorators.js';
4
+ import { Tabs } from '../../base/tabs.js';
5
+ import { tabsStyles } from './tabs-styles.css.js';
6
+ export class M3Tabs extends Tabs {
7
+ static{
8
+ this.styles = [
9
+ tabsStyles
10
+ ];
11
+ }
12
+ render() {
13
+ return html`
14
+ ${super.render()}
15
+ <div part="indicator" role="presentation"></div>
16
+ `;
17
+ }
18
+ selectTab(selectedTab) {
19
+ super.selectTab(selectedTab);
20
+ requestAnimationFrame(()=>{
21
+ const left = selectedTab.offsetLeft + (this.secondary ? 0 : selectedTab.$content.offsetLeft);
22
+ const width = this.secondary ? selectedTab.offsetWidth : selectedTab.$content.offsetWidth;
23
+ this.$indicator.style.left = `${left}px`;
24
+ this.$indicator.style.width = `${width}px`;
25
+ });
26
+ }
27
+ constructor(...args){
28
+ super(...args), this.secondary = false, this.iconsAbove = false;
29
+ }
30
+ }
31
+ _ts_decorate([
32
+ query('[part="indicator"]')
33
+ ], M3Tabs.prototype, "$indicator", void 0);
34
+ _ts_decorate([
35
+ property({
36
+ type: Boolean,
37
+ reflect: true
38
+ })
39
+ ], M3Tabs.prototype, "secondary", void 0);
40
+ _ts_decorate([
41
+ property({
42
+ type: Boolean,
43
+ reflect: true
44
+ })
45
+ ], M3Tabs.prototype, "iconsAbove", void 0);
46
+ M3Tabs = _ts_decorate([
47
+ customElement('md-tabs')
48
+ ], M3Tabs);
@@ -9,16 +9,6 @@ export class M3Toolbar extends InternalsAttached(LitElement) {
9
9
  toolbarStyles
10
10
  ];
11
11
  }
12
- constructor(){
13
- super(), this.type = 'docked', this.color = 'standard', this.orientation = 'horizontal';
14
- this[internals].role = 'toolbar';
15
- this[internals].ariaOrientation = this.orientation;
16
- }
17
- updated(changed) {
18
- if (changed.has('orientation')) {
19
- this[internals].ariaOrientation = this.orientation;
20
- }
21
- }
22
12
  render() {
23
13
  return html`
24
14
  <div part="container">
@@ -29,6 +19,16 @@ export class M3Toolbar extends InternalsAttached(LitElement) {
29
19
  </div>
30
20
  `;
31
21
  }
22
+ constructor(){
23
+ super(), this.type = 'docked', this.color = 'standard', this.orientation = 'horizontal';
24
+ this[internals].role = 'toolbar';
25
+ this[internals].ariaOrientation = this.orientation;
26
+ }
27
+ updated(changed) {
28
+ if (changed.has('orientation')) {
29
+ this[internals].ariaOrientation = this.orientation;
30
+ }
31
+ }
32
32
  }
33
33
  _ts_decorate([
34
34
  property({
@@ -1,3 +0,0 @@
1
- import { LitElement } from "lit";
2
- export class TabGroup extends LitElement {
3
- }