@u-elements/u-tabs 0.0.6 → 0.0.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/dist/u-tabs.cjs CHANGED
@@ -3,12 +3,20 @@
3
3
  // ../utils.ts
4
4
  var IS_BROWSER = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.navigator !== "undefined";
5
5
  var IS_ANDROID = IS_BROWSER && /android/i.test(navigator.userAgent);
6
+ var _a;
6
7
  IS_BROWSER && // @ts-expect-error Typescript has not implemented userAgentData yet https://stackoverflow.com/a/71392474
7
- /^Mac/i.test(navigator.userAgentData?.platform || navigator.platform);
8
+ /^Mac/i.test(((_a = navigator.userAgentData) == null ? void 0 : _a.platform) || navigator.platform);
8
9
  var SAFE_LABELLEDBY = `${IS_ANDROID ? "data" : "aria"}-labelledby`;
9
10
  var DISPLAY_BLOCK = ":host(:not([hidden])) { display: block }";
10
11
  var UHTMLElement = typeof HTMLElement === "undefined" ? class {
11
12
  } : HTMLElement;
13
+ function attr(el, name, value) {
14
+ var _a2;
15
+ if (value === void 0) return (_a2 = el.getAttribute(name)) != null ? _a2 : null;
16
+ if (value === null) el.removeAttribute(name);
17
+ else if (el.getAttribute(name) !== value) el.setAttribute(name, value);
18
+ return null;
19
+ }
12
20
  var events = (action, element, rest) => {
13
21
  for (const type of rest[0].split(",")) {
14
22
  rest[0] = type;
@@ -20,28 +28,53 @@ var off = (element, ...rest) => events("remove", element, rest);
20
28
  var attachStyle = (element, css) => element.attachShadow({ mode: "open" }).append(
21
29
  createElement("slot"),
22
30
  // Unnamed slot does automatically render all top element nodes
23
- createElement("style", { textContent: css })
31
+ createElement("style", css)
24
32
  );
33
+ var observers = /* @__PURE__ */ new WeakMap();
34
+ var mutationObserver = (element, options) => {
35
+ if (options === void 0) return observers.get(element);
36
+ try {
37
+ observers.get(element).disconnect();
38
+ observers.delete(element);
39
+ } catch (err) {
40
+ }
41
+ if (options) {
42
+ const observer = new MutationObserver(
43
+ (detail) => element.handleEvent({ type: "mutation", detail })
44
+ );
45
+ observer.observe(element, options);
46
+ observers.set(element, observer);
47
+ }
48
+ };
25
49
  var asButton = (event) => {
26
50
  const isClick = "key" in event && (event.key === " " || event.key === "Enter");
27
51
  if (isClick) event.preventDefault();
28
52
  if (isClick && event.target instanceof HTMLElement) event.target.click();
29
53
  return isClick;
30
54
  };
31
- var getRoot = (node) => node.getRootNode();
55
+ var getRoot = (node) => {
56
+ var _a2;
57
+ const root = ((_a2 = node.getRootNode) == null ? void 0 : _a2.call(node)) || node.ownerDocument;
58
+ return root instanceof Document || root instanceof ShadowRoot ? root : document;
59
+ };
32
60
  var id = 0;
33
61
  var useId = (el) => {
34
62
  if (!el) return "";
35
63
  if (!el.id) el.id = `:${el.nodeName.toLowerCase()}${(++id).toString(32)}`;
36
64
  return el.id;
37
65
  };
38
- var createElement = (tagName, props) => Object.assign(document.createElement(tagName), props);
66
+ var createElement = (tagName, text, attrs) => {
67
+ const el = document.createElement(tagName);
68
+ if (text) el.textContent = text;
69
+ return el;
70
+ };
39
71
  var customElements = {
40
72
  define: (name, instance) => !IS_BROWSER || window.customElements.get(name) || window.customElements.define(name, instance)
41
73
  };
42
74
 
43
75
  // u-tabs.ts
44
76
  var ARIA_CONTROLS = "aria-controls";
77
+ var ARIA_SELECTED = "aria-selected";
45
78
  var UHTMLTabsElement = class extends UHTMLElement {
46
79
  constructor() {
47
80
  super();
@@ -54,7 +87,7 @@ var UHTMLTabsElement = class extends UHTMLElement {
54
87
  return getSelectedIndex(this.tabs);
55
88
  }
56
89
  set selectedIndex(index) {
57
- if (this.tabs[index]) this.tabs[index].ariaSelected = "true";
90
+ setSelected(this.tabs[index]);
58
91
  }
59
92
  get tabs() {
60
93
  return queryWithoutNested("u-tab", this);
@@ -69,19 +102,26 @@ var UHTMLTabListElement = class extends UHTMLElement {
69
102
  attachStyle(this, DISPLAY_BLOCK);
70
103
  }
71
104
  connectedCallback() {
72
- this.role = "tablist";
105
+ attr(this, "role", "tablist");
73
106
  on(this, "click,keydown", this);
107
+ mutationObserver(this, { childList: true });
108
+ if (this.tabs.length) this.handleEvent();
74
109
  }
75
110
  disconnectedCallback() {
76
111
  off(this, "click,keydown", this);
112
+ mutationObserver(this, false);
77
113
  }
78
114
  handleEvent(event) {
115
+ if (!event || event.type === "mutation") {
116
+ const tab = this.tabs[Math.max(this.selectedIndex, 0)];
117
+ return tab == null ? void 0 : tab.setAttribute(ARIA_SELECTED, "true");
118
+ }
79
119
  const { key } = event;
80
- const tabs = [...this.getElementsByTagName("u-tab")];
120
+ const tabs = [...this.tabs];
81
121
  const prev = tabs.findIndex((tab) => tab.contains(event.target));
82
122
  let next = prev;
83
123
  if (event.defaultPrevented || prev === -1) return;
84
- if (event.type === "click") tabs[prev].selected = true;
124
+ if (event.type === "click") setSelected(tabs[prev]);
85
125
  if (event.type === "keydown" && !asButton(event)) {
86
126
  if (key === "ArrowDown" || key === "ArrowRight")
87
127
  next = (prev + 1) % tabs.length;
@@ -105,16 +145,21 @@ var UHTMLTabListElement = class extends UHTMLElement {
105
145
  return this.closest("u-tabs");
106
146
  }
107
147
  get tabs() {
108
- return queryWithoutNested("u-tab", this);
148
+ return this.querySelectorAll("u-tab");
109
149
  }
110
150
  get selectedIndex() {
111
151
  return getSelectedIndex(this.tabs);
112
152
  }
113
153
  set selectedIndex(index) {
114
- if (this.tabs[index]) this.tabs[index].ariaSelected = "true";
154
+ setSelected(this.tabs[index]);
115
155
  }
116
156
  };
157
+ var SKIP_ATTR_CHANGE = false;
117
158
  var UHTMLTabElement = class extends UHTMLElement {
159
+ // Using ES2015 syntax for backwards compatibility
160
+ static get observedAttributes() {
161
+ return ["id", ARIA_SELECTED, ARIA_CONTROLS];
162
+ }
118
163
  constructor() {
119
164
  super();
120
165
  attachStyle(
@@ -123,68 +168,65 @@ var UHTMLTabElement = class extends UHTMLElement {
123
168
  );
124
169
  }
125
170
  connectedCallback() {
126
- const selected = this.selected || ![...queryWithoutNested("u-tab", this.tabList || this)].some(isSelected);
127
- this.role = "tab";
128
- this.tabIndex = selected ? 0 : -1;
129
- this.ariaSelected = `${selected}`;
130
- if (!this.hasAttribute(ARIA_CONTROLS))
131
- this.setAttribute(ARIA_CONTROLS, useId(getPanel(this)));
132
- }
133
- attributeChangedCallback(name, prev) {
134
- if (!this.selected) return;
135
- const nextPanel = getPanel(this);
136
- const nextPanelId = useId(nextPanel);
137
- if (name === "aria-selected" && this.tabList)
138
- for (const tab of queryWithoutNested("u-tab", this.tabList)) {
139
- if (tab !== this && isSelected(tab)) {
140
- getPanel(tab)?.setAttribute("hidden", "");
141
- tab.ariaSelected = "false";
142
- tab.tabIndex = -1;
143
- }
144
- }
145
- if (name === ARIA_CONTROLS && prev)
146
- getPanel(this, prev)?.setAttribute("hidden", "");
147
- if (nextPanel && this.getAttribute(ARIA_CONTROLS) !== nextPanelId)
148
- this.setAttribute(ARIA_CONTROLS, nextPanelId);
149
- this.tabIndex = 0;
150
- nextPanel?.setAttribute(SAFE_LABELLEDBY, useId(this));
151
- nextPanel?.removeAttribute("hidden");
171
+ attr(this, "role", "tab");
172
+ this.tabIndex = this.selected ? 0 : -1;
173
+ }
174
+ attributeChangedCallback() {
175
+ if (!SKIP_ATTR_CHANGE && this.selected && this.tabList) {
176
+ SKIP_ATTR_CHANGE = true;
177
+ const tabs = [...this.tabList.querySelectorAll("u-tab")];
178
+ const panels = queryWithoutNested("u-tabpanel", this.tabsElement || this);
179
+ const nextPanel = getPanel(this, panels[tabs.indexOf(this)]);
180
+ if (nextPanel) attr(nextPanel, SAFE_LABELLEDBY, useId(this));
181
+ tabs.forEach((tab, index) => {
182
+ const panel = getPanel(tab, panels[index]);
183
+ tab.tabIndex = tab === this ? 0 : -1;
184
+ attr(tab, ARIA_SELECTED, `${tab === this}`);
185
+ if (panel) panel.hidden = panel !== nextPanel;
186
+ if (panel) attr(tab, ARIA_CONTROLS, panel.id);
187
+ });
188
+ SKIP_ATTR_CHANGE = false;
189
+ }
152
190
  }
153
191
  get tabsElement() {
154
192
  return this.closest("u-tabs");
155
193
  }
156
194
  get tabList() {
157
- return this.closest("u-tablist");
195
+ const tablist = this.parentElement;
196
+ return (tablist == null ? void 0 : tablist.nodeName) === "U-TABLIST" ? tablist : null;
158
197
  }
159
198
  get selected() {
160
- return isSelected(this);
199
+ return attr(this, ARIA_SELECTED) === "true";
161
200
  }
162
201
  set selected(value) {
163
- this.ariaSelected = `${!!value}`;
202
+ attr(this, ARIA_SELECTED, `${!!value}`);
164
203
  }
165
204
  /** Retrieves the ordinal position of an tab in a tablist. */
166
205
  get index() {
167
206
  const tabList = this.tabList;
168
- return tabList ? [...queryWithoutNested("u-tab", tabList)].indexOf(this) : 0;
207
+ return tabList ? [...tabList.querySelectorAll("u-tab")].indexOf(this) : 0;
169
208
  }
170
209
  get panel() {
171
210
  return getPanel(this);
172
211
  }
173
212
  };
174
- UHTMLTabElement.observedAttributes = ["id", "aria-selected", ARIA_CONTROLS];
175
213
  var UHTMLTabPanelElement = class extends UHTMLElement {
214
+ // Using ES2015 syntax for backwards compatibility
215
+ static get observedAttributes() {
216
+ return ["hidden"];
217
+ }
176
218
  constructor() {
177
219
  super();
178
220
  attachStyle(this, DISPLAY_BLOCK);
179
221
  }
180
222
  connectedCallback() {
223
+ attr(this, "role", "tabpanel");
181
224
  this.hidden = getSelectedIndex(this.tabs) === -1;
182
- this.role = "tabpanel";
183
225
  this.attributeChangedCallback();
184
226
  }
185
227
  attributeChangedCallback() {
186
- if (this.hidden || isFocusable(this.firstElementChild))
187
- this.removeAttribute("tabindex");
228
+ if (this.hidden || isFocusable(this.firstChild))
229
+ attr(this, "tabindex", null);
188
230
  else this.tabIndex = 0;
189
231
  }
190
232
  get tabsElement() {
@@ -192,25 +234,20 @@ var UHTMLTabPanelElement = class extends UHTMLElement {
192
234
  }
193
235
  get tabs() {
194
236
  const css = `u-tab[${ARIA_CONTROLS}="${this.id}"]`;
195
- const root = getRoot(this).querySelectorAll(css);
196
- return root.length ? root : document.querySelectorAll(css);
237
+ return getRoot(this).querySelectorAll(css);
197
238
  }
198
239
  };
199
- UHTMLTabPanelElement.observedAttributes = ["hidden"];
200
- var queryWithoutNested = (tag, self) => self.querySelectorAll(
201
- `${tag}:not(:scope ${self.nodeName}:not(:scope) ${tag})`
202
- );
203
- var isSelected = (tab) => tab.ariaSelected === "true";
204
- var getSelectedIndex = (tabs) => [...tabs].findIndex(isSelected);
205
- var isFocusable = (el) => el instanceof Element && el.matches(
206
- `:is([contenteditable],[controls],[href],[tabindex],input:not([type="hidden"]),select,textarea,button,summary,iframe):not(:disabled,[tabindex^="-"])`
207
- );
208
- var getPanel = (tab, id2) => {
209
- const panelId = id2 || tab.getAttribute(ARIA_CONTROLS);
210
- const panelSelector = `u-tabpanel[id="${panelId}"]`;
211
- const tabsElement = tab.closest("u-tabs");
212
- return panelId && getRoot(tab).querySelector(panelSelector) || panelId && getRoot(tab).querySelector(panelSelector) || tabsElement && queryWithoutNested("u-tabpanel", tabsElement)[[...queryWithoutNested("u-tab", tabsElement)].indexOf(tab)] || null;
240
+ var queryWithoutNested = (tag, self) => self.querySelectorAll(`${tag}:not(:scope u-tabpanel ${tag})`);
241
+ var getPanel = (tab, panel) => {
242
+ const id2 = attr(tab, ARIA_CONTROLS) || useId(panel);
243
+ const el = getRoot(tab).getElementById(id2);
244
+ return (el == null ? void 0 : el.nodeName) === "U-TABPANEL" ? el : null;
213
245
  };
246
+ var getSelectedIndex = (tabs) => [...tabs].findIndex((tab) => attr(tab, ARIA_SELECTED) === "true");
247
+ var setSelected = (tab) => tab && attr(tab, "aria-selected", "true");
248
+ var isFocusable = (el) => el instanceof Element && !el.matches(':disabled,[tabindex^="-"]') && el.matches(
249
+ `[contenteditable],[controls],[href],[tabindex],input:not([type="hidden"]),select,textarea,button,summary,iframe`
250
+ );
214
251
  customElements.define("u-tabs", UHTMLTabsElement);
215
252
  customElements.define("u-tablist", UHTMLTabListElement);
216
253
  customElements.define("u-tab", UHTMLTabElement);
package/dist/u-tabs.d.cts CHANGED
@@ -31,21 +31,17 @@ declare class UHTMLTabListElement extends UHTMLElement {
31
31
  constructor();
32
32
  connectedCallback(): void;
33
33
  disconnectedCallback(): void;
34
- handleEvent(event: Event): void;
34
+ handleEvent(event?: Event): void;
35
35
  get tabsElement(): UHTMLTabsElement | null;
36
36
  get tabs(): NodeListOf<UHTMLTabElement>;
37
37
  get selectedIndex(): number;
38
38
  set selectedIndex(index: number);
39
39
  }
40
- /**
41
- * The `<u-tab>` HTML element is an interactive element inside a `<u-tablist>` that, when activated, displays its associated `<u-tabpanel>`.
42
- * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tab_role)
43
- */
44
40
  declare class UHTMLTabElement extends UHTMLElement {
45
- static observedAttributes: string[];
41
+ static get observedAttributes(): string[];
46
42
  constructor();
47
43
  connectedCallback(): void;
48
- attributeChangedCallback(name: string, prev: string): void;
44
+ attributeChangedCallback(): void;
49
45
  get tabsElement(): UHTMLTabsElement | null;
50
46
  get tabList(): UHTMLTabListElement | null;
51
47
  get selected(): boolean;
@@ -59,7 +55,7 @@ declare class UHTMLTabElement extends UHTMLElement {
59
55
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tabpanel_role)
60
56
  */
61
57
  declare class UHTMLTabPanelElement extends UHTMLElement {
62
- static observedAttributes: string[];
58
+ static get observedAttributes(): string[];
63
59
  constructor();
64
60
  connectedCallback(): void;
65
61
  attributeChangedCallback(): void;
package/dist/u-tabs.d.ts CHANGED
@@ -31,21 +31,17 @@ declare class UHTMLTabListElement extends UHTMLElement {
31
31
  constructor();
32
32
  connectedCallback(): void;
33
33
  disconnectedCallback(): void;
34
- handleEvent(event: Event): void;
34
+ handleEvent(event?: Event): void;
35
35
  get tabsElement(): UHTMLTabsElement | null;
36
36
  get tabs(): NodeListOf<UHTMLTabElement>;
37
37
  get selectedIndex(): number;
38
38
  set selectedIndex(index: number);
39
39
  }
40
- /**
41
- * The `<u-tab>` HTML element is an interactive element inside a `<u-tablist>` that, when activated, displays its associated `<u-tabpanel>`.
42
- * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tab_role)
43
- */
44
40
  declare class UHTMLTabElement extends UHTMLElement {
45
- static observedAttributes: string[];
41
+ static get observedAttributes(): string[];
46
42
  constructor();
47
43
  connectedCallback(): void;
48
- attributeChangedCallback(name: string, prev: string): void;
44
+ attributeChangedCallback(): void;
49
45
  get tabsElement(): UHTMLTabsElement | null;
50
46
  get tabList(): UHTMLTabListElement | null;
51
47
  get selected(): boolean;
@@ -59,7 +55,7 @@ declare class UHTMLTabElement extends UHTMLElement {
59
55
  * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tabpanel_role)
60
56
  */
61
57
  declare class UHTMLTabPanelElement extends UHTMLElement {
62
- static observedAttributes: string[];
58
+ static get observedAttributes(): string[];
63
59
  constructor();
64
60
  connectedCallback(): void;
65
61
  attributeChangedCallback(): void;
package/dist/u-tabs.js CHANGED
@@ -1,12 +1,20 @@
1
1
  // ../utils.ts
2
2
  var IS_BROWSER = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.navigator !== "undefined";
3
3
  var IS_ANDROID = IS_BROWSER && /android/i.test(navigator.userAgent);
4
+ var _a;
4
5
  IS_BROWSER && // @ts-expect-error Typescript has not implemented userAgentData yet https://stackoverflow.com/a/71392474
5
- /^Mac/i.test(navigator.userAgentData?.platform || navigator.platform);
6
+ /^Mac/i.test(((_a = navigator.userAgentData) == null ? void 0 : _a.platform) || navigator.platform);
6
7
  var SAFE_LABELLEDBY = `${IS_ANDROID ? "data" : "aria"}-labelledby`;
7
8
  var DISPLAY_BLOCK = ":host(:not([hidden])) { display: block }";
8
9
  var UHTMLElement = typeof HTMLElement === "undefined" ? class {
9
10
  } : HTMLElement;
11
+ function attr(el, name, value) {
12
+ var _a2;
13
+ if (value === void 0) return (_a2 = el.getAttribute(name)) != null ? _a2 : null;
14
+ if (value === null) el.removeAttribute(name);
15
+ else if (el.getAttribute(name) !== value) el.setAttribute(name, value);
16
+ return null;
17
+ }
10
18
  var events = (action, element, rest) => {
11
19
  for (const type of rest[0].split(",")) {
12
20
  rest[0] = type;
@@ -18,28 +26,53 @@ var off = (element, ...rest) => events("remove", element, rest);
18
26
  var attachStyle = (element, css) => element.attachShadow({ mode: "open" }).append(
19
27
  createElement("slot"),
20
28
  // Unnamed slot does automatically render all top element nodes
21
- createElement("style", { textContent: css })
29
+ createElement("style", css)
22
30
  );
31
+ var observers = /* @__PURE__ */ new WeakMap();
32
+ var mutationObserver = (element, options) => {
33
+ if (options === void 0) return observers.get(element);
34
+ try {
35
+ observers.get(element).disconnect();
36
+ observers.delete(element);
37
+ } catch (err) {
38
+ }
39
+ if (options) {
40
+ const observer = new MutationObserver(
41
+ (detail) => element.handleEvent({ type: "mutation", detail })
42
+ );
43
+ observer.observe(element, options);
44
+ observers.set(element, observer);
45
+ }
46
+ };
23
47
  var asButton = (event) => {
24
48
  const isClick = "key" in event && (event.key === " " || event.key === "Enter");
25
49
  if (isClick) event.preventDefault();
26
50
  if (isClick && event.target instanceof HTMLElement) event.target.click();
27
51
  return isClick;
28
52
  };
29
- var getRoot = (node) => node.getRootNode();
53
+ var getRoot = (node) => {
54
+ var _a2;
55
+ const root = ((_a2 = node.getRootNode) == null ? void 0 : _a2.call(node)) || node.ownerDocument;
56
+ return root instanceof Document || root instanceof ShadowRoot ? root : document;
57
+ };
30
58
  var id = 0;
31
59
  var useId = (el) => {
32
60
  if (!el) return "";
33
61
  if (!el.id) el.id = `:${el.nodeName.toLowerCase()}${(++id).toString(32)}`;
34
62
  return el.id;
35
63
  };
36
- var createElement = (tagName, props) => Object.assign(document.createElement(tagName), props);
64
+ var createElement = (tagName, text, attrs) => {
65
+ const el = document.createElement(tagName);
66
+ if (text) el.textContent = text;
67
+ return el;
68
+ };
37
69
  var customElements = {
38
70
  define: (name, instance) => !IS_BROWSER || window.customElements.get(name) || window.customElements.define(name, instance)
39
71
  };
40
72
 
41
73
  // u-tabs.ts
42
74
  var ARIA_CONTROLS = "aria-controls";
75
+ var ARIA_SELECTED = "aria-selected";
43
76
  var UHTMLTabsElement = class extends UHTMLElement {
44
77
  constructor() {
45
78
  super();
@@ -52,7 +85,7 @@ var UHTMLTabsElement = class extends UHTMLElement {
52
85
  return getSelectedIndex(this.tabs);
53
86
  }
54
87
  set selectedIndex(index) {
55
- if (this.tabs[index]) this.tabs[index].ariaSelected = "true";
88
+ setSelected(this.tabs[index]);
56
89
  }
57
90
  get tabs() {
58
91
  return queryWithoutNested("u-tab", this);
@@ -67,19 +100,26 @@ var UHTMLTabListElement = class extends UHTMLElement {
67
100
  attachStyle(this, DISPLAY_BLOCK);
68
101
  }
69
102
  connectedCallback() {
70
- this.role = "tablist";
103
+ attr(this, "role", "tablist");
71
104
  on(this, "click,keydown", this);
105
+ mutationObserver(this, { childList: true });
106
+ if (this.tabs.length) this.handleEvent();
72
107
  }
73
108
  disconnectedCallback() {
74
109
  off(this, "click,keydown", this);
110
+ mutationObserver(this, false);
75
111
  }
76
112
  handleEvent(event) {
113
+ if (!event || event.type === "mutation") {
114
+ const tab = this.tabs[Math.max(this.selectedIndex, 0)];
115
+ return tab == null ? void 0 : tab.setAttribute(ARIA_SELECTED, "true");
116
+ }
77
117
  const { key } = event;
78
- const tabs = [...this.getElementsByTagName("u-tab")];
118
+ const tabs = [...this.tabs];
79
119
  const prev = tabs.findIndex((tab) => tab.contains(event.target));
80
120
  let next = prev;
81
121
  if (event.defaultPrevented || prev === -1) return;
82
- if (event.type === "click") tabs[prev].selected = true;
122
+ if (event.type === "click") setSelected(tabs[prev]);
83
123
  if (event.type === "keydown" && !asButton(event)) {
84
124
  if (key === "ArrowDown" || key === "ArrowRight")
85
125
  next = (prev + 1) % tabs.length;
@@ -103,16 +143,21 @@ var UHTMLTabListElement = class extends UHTMLElement {
103
143
  return this.closest("u-tabs");
104
144
  }
105
145
  get tabs() {
106
- return queryWithoutNested("u-tab", this);
146
+ return this.querySelectorAll("u-tab");
107
147
  }
108
148
  get selectedIndex() {
109
149
  return getSelectedIndex(this.tabs);
110
150
  }
111
151
  set selectedIndex(index) {
112
- if (this.tabs[index]) this.tabs[index].ariaSelected = "true";
152
+ setSelected(this.tabs[index]);
113
153
  }
114
154
  };
155
+ var SKIP_ATTR_CHANGE = false;
115
156
  var UHTMLTabElement = class extends UHTMLElement {
157
+ // Using ES2015 syntax for backwards compatibility
158
+ static get observedAttributes() {
159
+ return ["id", ARIA_SELECTED, ARIA_CONTROLS];
160
+ }
116
161
  constructor() {
117
162
  super();
118
163
  attachStyle(
@@ -121,68 +166,65 @@ var UHTMLTabElement = class extends UHTMLElement {
121
166
  );
122
167
  }
123
168
  connectedCallback() {
124
- const selected = this.selected || ![...queryWithoutNested("u-tab", this.tabList || this)].some(isSelected);
125
- this.role = "tab";
126
- this.tabIndex = selected ? 0 : -1;
127
- this.ariaSelected = `${selected}`;
128
- if (!this.hasAttribute(ARIA_CONTROLS))
129
- this.setAttribute(ARIA_CONTROLS, useId(getPanel(this)));
130
- }
131
- attributeChangedCallback(name, prev) {
132
- if (!this.selected) return;
133
- const nextPanel = getPanel(this);
134
- const nextPanelId = useId(nextPanel);
135
- if (name === "aria-selected" && this.tabList)
136
- for (const tab of queryWithoutNested("u-tab", this.tabList)) {
137
- if (tab !== this && isSelected(tab)) {
138
- getPanel(tab)?.setAttribute("hidden", "");
139
- tab.ariaSelected = "false";
140
- tab.tabIndex = -1;
141
- }
142
- }
143
- if (name === ARIA_CONTROLS && prev)
144
- getPanel(this, prev)?.setAttribute("hidden", "");
145
- if (nextPanel && this.getAttribute(ARIA_CONTROLS) !== nextPanelId)
146
- this.setAttribute(ARIA_CONTROLS, nextPanelId);
147
- this.tabIndex = 0;
148
- nextPanel?.setAttribute(SAFE_LABELLEDBY, useId(this));
149
- nextPanel?.removeAttribute("hidden");
169
+ attr(this, "role", "tab");
170
+ this.tabIndex = this.selected ? 0 : -1;
171
+ }
172
+ attributeChangedCallback() {
173
+ if (!SKIP_ATTR_CHANGE && this.selected && this.tabList) {
174
+ SKIP_ATTR_CHANGE = true;
175
+ const tabs = [...this.tabList.querySelectorAll("u-tab")];
176
+ const panels = queryWithoutNested("u-tabpanel", this.tabsElement || this);
177
+ const nextPanel = getPanel(this, panels[tabs.indexOf(this)]);
178
+ if (nextPanel) attr(nextPanel, SAFE_LABELLEDBY, useId(this));
179
+ tabs.forEach((tab, index) => {
180
+ const panel = getPanel(tab, panels[index]);
181
+ tab.tabIndex = tab === this ? 0 : -1;
182
+ attr(tab, ARIA_SELECTED, `${tab === this}`);
183
+ if (panel) panel.hidden = panel !== nextPanel;
184
+ if (panel) attr(tab, ARIA_CONTROLS, panel.id);
185
+ });
186
+ SKIP_ATTR_CHANGE = false;
187
+ }
150
188
  }
151
189
  get tabsElement() {
152
190
  return this.closest("u-tabs");
153
191
  }
154
192
  get tabList() {
155
- return this.closest("u-tablist");
193
+ const tablist = this.parentElement;
194
+ return (tablist == null ? void 0 : tablist.nodeName) === "U-TABLIST" ? tablist : null;
156
195
  }
157
196
  get selected() {
158
- return isSelected(this);
197
+ return attr(this, ARIA_SELECTED) === "true";
159
198
  }
160
199
  set selected(value) {
161
- this.ariaSelected = `${!!value}`;
200
+ attr(this, ARIA_SELECTED, `${!!value}`);
162
201
  }
163
202
  /** Retrieves the ordinal position of an tab in a tablist. */
164
203
  get index() {
165
204
  const tabList = this.tabList;
166
- return tabList ? [...queryWithoutNested("u-tab", tabList)].indexOf(this) : 0;
205
+ return tabList ? [...tabList.querySelectorAll("u-tab")].indexOf(this) : 0;
167
206
  }
168
207
  get panel() {
169
208
  return getPanel(this);
170
209
  }
171
210
  };
172
- UHTMLTabElement.observedAttributes = ["id", "aria-selected", ARIA_CONTROLS];
173
211
  var UHTMLTabPanelElement = class extends UHTMLElement {
212
+ // Using ES2015 syntax for backwards compatibility
213
+ static get observedAttributes() {
214
+ return ["hidden"];
215
+ }
174
216
  constructor() {
175
217
  super();
176
218
  attachStyle(this, DISPLAY_BLOCK);
177
219
  }
178
220
  connectedCallback() {
221
+ attr(this, "role", "tabpanel");
179
222
  this.hidden = getSelectedIndex(this.tabs) === -1;
180
- this.role = "tabpanel";
181
223
  this.attributeChangedCallback();
182
224
  }
183
225
  attributeChangedCallback() {
184
- if (this.hidden || isFocusable(this.firstElementChild))
185
- this.removeAttribute("tabindex");
226
+ if (this.hidden || isFocusable(this.firstChild))
227
+ attr(this, "tabindex", null);
186
228
  else this.tabIndex = 0;
187
229
  }
188
230
  get tabsElement() {
@@ -190,25 +232,20 @@ var UHTMLTabPanelElement = class extends UHTMLElement {
190
232
  }
191
233
  get tabs() {
192
234
  const css = `u-tab[${ARIA_CONTROLS}="${this.id}"]`;
193
- const root = getRoot(this).querySelectorAll(css);
194
- return root.length ? root : document.querySelectorAll(css);
235
+ return getRoot(this).querySelectorAll(css);
195
236
  }
196
237
  };
197
- UHTMLTabPanelElement.observedAttributes = ["hidden"];
198
- var queryWithoutNested = (tag, self) => self.querySelectorAll(
199
- `${tag}:not(:scope ${self.nodeName}:not(:scope) ${tag})`
200
- );
201
- var isSelected = (tab) => tab.ariaSelected === "true";
202
- var getSelectedIndex = (tabs) => [...tabs].findIndex(isSelected);
203
- var isFocusable = (el) => el instanceof Element && el.matches(
204
- `:is([contenteditable],[controls],[href],[tabindex],input:not([type="hidden"]),select,textarea,button,summary,iframe):not(:disabled,[tabindex^="-"])`
205
- );
206
- var getPanel = (tab, id2) => {
207
- const panelId = id2 || tab.getAttribute(ARIA_CONTROLS);
208
- const panelSelector = `u-tabpanel[id="${panelId}"]`;
209
- const tabsElement = tab.closest("u-tabs");
210
- return panelId && getRoot(tab).querySelector(panelSelector) || panelId && getRoot(tab).querySelector(panelSelector) || tabsElement && queryWithoutNested("u-tabpanel", tabsElement)[[...queryWithoutNested("u-tab", tabsElement)].indexOf(tab)] || null;
238
+ var queryWithoutNested = (tag, self) => self.querySelectorAll(`${tag}:not(:scope u-tabpanel ${tag})`);
239
+ var getPanel = (tab, panel) => {
240
+ const id2 = attr(tab, ARIA_CONTROLS) || useId(panel);
241
+ const el = getRoot(tab).getElementById(id2);
242
+ return (el == null ? void 0 : el.nodeName) === "U-TABPANEL" ? el : null;
211
243
  };
244
+ var getSelectedIndex = (tabs) => [...tabs].findIndex((tab) => attr(tab, ARIA_SELECTED) === "true");
245
+ var setSelected = (tab) => tab && attr(tab, "aria-selected", "true");
246
+ var isFocusable = (el) => el instanceof Element && !el.matches(':disabled,[tabindex^="-"]') && el.matches(
247
+ `[contenteditable],[controls],[href],[tabindex],input:not([type="hidden"]),select,textarea,button,summary,iframe`
248
+ );
212
249
  customElements.define("u-tabs", UHTMLTabsElement);
213
250
  customElements.define("u-tablist", UHTMLTabListElement);
214
251
  customElements.define("u-tab", UHTMLTabElement);
@@ -61,6 +61,7 @@
61
61
  "parameters": [
62
62
  {
63
63
  "name": "event",
64
+ "optional": true,
64
65
  "type": {
65
66
  "text": "Event"
66
67
  }
@@ -100,7 +101,7 @@
100
101
  },
101
102
  {
102
103
  "kind": "class",
103
- "description": "The `<u-tab>` HTML element is an interactive element inside a `<u-tablist>` that, when activated, displays its associated `<u-tabpanel>`.\n[MDN Reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tab_role)",
104
+ "description": "",
104
105
  "name": "UHTMLTabElement",
105
106
  "members": [
106
107
  {
@@ -147,9 +148,6 @@
147
148
  "attributes": [
148
149
  {
149
150
  "name": "id"
150
- },
151
- {
152
- "name": "aria-selected"
153
151
  }
154
152
  ],
155
153
  "superclass": {
@@ -16,11 +16,8 @@
16
16
  },
17
17
  {
18
18
  "name": "u-tab",
19
- "description": "The `<u-tab>` HTML element is an interactive element inside a `<u-tablist>` that, when activated, displays its associated `<u-tabpanel>`.\n[MDN Reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tab_role)\n---\n",
20
- "attributes": [
21
- { "name": "id", "values": [] },
22
- { "name": "aria-selected", "values": [] }
23
- ],
19
+ "description": "\n---\n",
20
+ "attributes": [{ "name": "id", "values": [] }],
24
21
  "references": []
25
22
  },
26
23
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@u-elements/u-tabs",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "license": "MIT",
5
5
  "description": "HTML tags, just truly accessible",
6
6
  "homepage": "https://u-elements.github.io/u-elements/",