@vollowx/seele 0.12.7 → 0.12.8

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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vollowx/seele",
3
- "version": "0.12.7",
3
+ "version": "0.12.8",
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"
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();
package/src/base/list.js CHANGED
@@ -17,8 +17,8 @@ const Base = InternalsAttached(LitElement);
17
17
  return this.listController.items || [];
18
18
  }
19
19
  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,
20
+ super(), this._scrollPadding = 0, this.noFocusControl = false, this.listController = new ListController(this, {
21
+ isItem: (item)=>item.getAttribute('seele-base') === 'option' && !item.hasAttribute('disabled') && !item.hidden,
22
22
  getPossibleItems: ()=>this.slotItems,
23
23
  blurItem: (item)=>{
24
24
  item.focused = false;
@@ -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)=>{
@@ -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
+ connectedCallback() {
8
+ super.connectedCallback();
9
+ this.setAttribute('seele-base', 'tabpanel');
10
+ this[internals].role = this._role;
11
+ if (!this.id) this.id = genUniqueId('tabpanel');
12
+ }
13
+ render() {
14
+ return html`<slot></slot>`;
15
+ }
16
+ constructor(...args){
17
+ super(...args), this.value = '', this._role = 'tabpanel';
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
+ connectedCallback() {
10
+ super.connectedCallback();
11
+ this.setAttribute('seele-base', 'tab');
12
+ this[internals].role = this._role;
13
+ if (!this.id) this.id = genUniqueId('tab');
14
+ this.#updateInternals();
15
+ }
16
+ updated(changed) {
17
+ super.updated(changed);
18
+ if (changed.has('selected') || changed.has('focused')) {
19
+ this.#updateInternals();
20
+ }
21
+ }
22
+ #updateInternals() {
23
+ this[internals].ariaSelected = this.selected ? 'true' : 'false';
24
+ this.focused ? this[internals].states.add('focused') : this[internals].states.delete('focused');
25
+ this.selected ? this[internals].states.add('selected') : this[internals].states.delete('selected');
26
+ }
27
+ focus() {
28
+ this.focused = true;
29
+ super.focus();
30
+ }
31
+ blur() {
32
+ this.focused = false;
33
+ super.blur();
34
+ }
35
+ render() {
36
+ return html`<slot></slot>`;
37
+ }
38
+ constructor(...args){
39
+ super(...args), this.selected = false, this.focused = false, this.value = '', this._role = 'tab';
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
+ connectedCallback() {
15
+ super.connectedCallback();
16
+ if (!isServer) {
17
+ this.addEventListener('keydown', this.#handleKeyDown.bind(this));
18
+ this.addEventListener('click', this.#handleClick.bind(this));
19
+ }
20
+ }
21
+ updated(changed) {
22
+ if (changed.has('selected') && this.selected) {
23
+ const targetTab = this.$tabs.find((t)=>t.value === this.selected);
24
+ if (targetTab && !targetTab.selected) {
25
+ this.selectTab(targetTab);
26
+ }
27
+ }
28
+ }
29
+ #handleSlotChange(_) {
30
+ const parent = this.$parent;
31
+ this.$tabs.forEach((tab)=>{
32
+ const panel = parent.querySelector(`[seele-base="tab"][value="${tab.value}"]`);
33
+ if (!panel) console.error('[seele] cannot find a matching panel for tab ', tab);
34
+ // TODO: ariaLabelledByElements
35
+ tab.setAttribute('aria-controls', panel.id);
36
+ panel.setAttribute('aria-labelledby', tab.id);
37
+ });
38
+ }
39
+ #handleKeyDown(event) {
40
+ const tabs = this.$tabs;
41
+ if (!tabs.length) return;
42
+ const activeElement = this.getRootNode().activeElement;
43
+ const currentIndex = tabs.findIndex((t)=>t === activeElement || t.focused);
44
+ let nextIndex = currentIndex !== -1 ? currentIndex : 0;
45
+ let handled = false;
46
+ switch(event.key){
47
+ case 'ArrowLeft':
48
+ nextIndex = currentIndex <= 0 ? tabs.length - 1 : currentIndex - 1;
49
+ handled = true;
50
+ break;
51
+ case 'ArrowRight':
52
+ nextIndex = currentIndex >= tabs.length - 1 ? 0 : currentIndex + 1;
53
+ handled = true;
54
+ break;
55
+ case 'Home':
56
+ nextIndex = 0;
57
+ handled = true;
58
+ break;
59
+ case 'End':
60
+ nextIndex = tabs.length - 1;
61
+ handled = true;
62
+ break;
63
+ case 'Enter':
64
+ case ' ':
65
+ if (currentIndex !== -1) {
66
+ this.selectTab(tabs[currentIndex]);
67
+ }
68
+ handled = true;
69
+ break;
70
+ }
71
+ if (handled) {
72
+ event.preventDefault();
73
+ event.stopPropagation();
74
+ if (event.key !== 'Enter' && event.key !== ' ') {
75
+ this.focusTab(tabs[nextIndex]);
76
+ if (this.switch === 'auto') this.selectTab(tabs[nextIndex]);
77
+ }
78
+ }
79
+ }
80
+ #handleClick(event) {
81
+ const target = event.target;
82
+ const tab = target.closest('md-tab');
83
+ if (tab && this.$tabs.includes(tab)) {
84
+ this.focusTab(tab);
85
+ this.selectTab(tab);
86
+ }
87
+ }
88
+ focusTab(tab) {
89
+ this.$tabs.forEach((t)=>{
90
+ t.blur();
91
+ t.tabIndex = -1;
92
+ });
93
+ tab.tabIndex = 0;
94
+ tab.focus();
95
+ }
96
+ selectTab(selectedTab) {
97
+ this.selected = selectedTab.value;
98
+ const parent = this.$parent;
99
+ const panels = Array.from(parent.querySelectorAll('[seele-base="tabpanel"]'));
100
+ this.$tabs.forEach((tab)=>{
101
+ tab.selected = tab === selectedTab;
102
+ tab.tabIndex = tab === selectedTab ? 0 : -1;
103
+ });
104
+ panels.forEach((panel)=>{
105
+ if (panel.value && selectedTab.value) {
106
+ panel.hidden = panel.value !== selectedTab.value;
107
+ }
108
+ });
109
+ this.dispatchEvent(new CustomEvent('tab-select', {
110
+ detail: {
111
+ tab: selectedTab,
112
+ value: this.selected
113
+ },
114
+ bubbles: true,
115
+ composed: true
116
+ }));
117
+ }
118
+ render() {
119
+ return html`
120
+ <div part="tablist" role="tablist">
121
+ <slot @slotchange=${this.#handleSlotChange}></slot>
122
+ </div>
123
+ `;
124
+ }
125
+ constructor(...args){
126
+ super(...args), this.switch = 'manual', this.selected = '', this._role = 'tablist';
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);
@@ -11,11 +11,7 @@ export class M3List extends List {
11
11
  ];
12
12
  }
13
13
  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';
14
+ super(...args), this.color = 'standard';
19
15
  }
20
16
  }
21
17
  _ts_decorate([
@@ -12,12 +12,7 @@ export class M3Menu extends Menu {
12
12
  ];
13
13
  }
14
14
  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 = {
15
+ super(...args), this._durations = {
21
16
  show: 300,
22
17
  hide: 150
23
18
  }, this.color = 'standard';
@@ -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);
@@ -1,3 +0,0 @@
1
- import { LitElement } from "lit";
2
- export class TabGroup extends LitElement {
3
- }