power-focusable 2.1.4 → 2.1.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/dist/index.cjs CHANGED
@@ -10,30 +10,31 @@ function getFocusables(container = document.body, options = {}) {
10
10
  const { composed = false } = options;
11
11
  const elements = [];
12
12
  if (composed) {
13
- let walk2 = function(node) {
13
+ let traverse2 = function(node) {
14
14
  if (node instanceof HTMLElement) {
15
15
  if (isFocusable(node)) {
16
16
  elements[elements.length] = node;
17
17
  }
18
18
  const shadow = node.shadowRoot;
19
19
  if (shadow && shadow.mode === "open") {
20
- walk2(shadow);
20
+ traverse2(shadow);
21
21
  }
22
- } else if (node instanceof HTMLSlotElement) {
22
+ }
23
+ if (node instanceof HTMLSlotElement) {
23
24
  const assigned = node.assignedElements({ flatten: true });
24
25
  if (assigned.length) {
25
26
  for (let i = 0, l = assigned.length; i < l; i++) {
26
- walk2(assigned[i]);
27
+ traverse2(assigned[i]);
27
28
  }
28
29
  return;
29
30
  }
30
31
  }
31
32
  const children = node.childNodes;
32
33
  for (let i = 0, l = children.length; i < l; i++) {
33
- walk2(children[i]);
34
+ traverse2(children[i]);
34
35
  }
35
36
  };
36
- walk2(container);
37
+ traverse2(container);
37
38
  } else {
38
39
  const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
39
40
  for (let i = 0, l = candidates.length; i < l; i++) {
@@ -75,10 +76,8 @@ function isFocusable(element) {
75
76
  return false;
76
77
  }
77
78
  const index = element.getAttribute("tabindex");
78
- if (index) {
79
- if (Number(index) < 0) {
80
- return false;
81
- }
79
+ if (index && Number(index) < 0) {
80
+ return false;
82
81
  }
83
82
  if (!element.matches(FOCUSABLE_SELECTOR)) {
84
83
  return false;
@@ -117,29 +116,21 @@ function getRelativeFocusable(container, offset, options) {
117
116
  return focusables[(offsetIndex + length) % length] ?? null;
118
117
  }
119
118
  function containsDeep(container, element) {
120
- function walk(node) {
121
- if (!node) {
122
- return false;
123
- }
124
- if (node === container) {
119
+ let current = element;
120
+ while (current) {
121
+ if (current === container) {
125
122
  return true;
126
123
  }
127
- if (node instanceof ShadowRoot) {
128
- return node.mode === "open" ? walk(node.host) : false;
129
- }
130
- return walk(node.parentNode);
124
+ current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
131
125
  }
132
- return walk(element);
126
+ return false;
133
127
  }
134
128
  function getActiveElement() {
135
- function walk(node) {
136
- if (!node) {
137
- return null;
138
- }
139
- const active = node.shadowRoot?.activeElement;
140
- return active ? walk(active) : node;
129
+ let current = document.activeElement;
130
+ while (current?.shadowRoot?.activeElement) {
131
+ current = current.shadowRoot.activeElement;
141
132
  }
142
- return walk(document.activeElement);
133
+ return current;
143
134
  }
144
135
  var tabIndexCache = /* @__PURE__ */ new WeakMap();
145
136
  function getTabIndex(element) {
@@ -152,44 +143,49 @@ function getTabIndex(element) {
152
143
  return index;
153
144
  }
154
145
  function isDisabled(element) {
155
- return "disabled" in element && element.disabled;
146
+ return "disabled" in element && !!element.disabled;
156
147
  }
157
148
  function isDisabledDeep(element) {
158
- function walk(node) {
159
- if (!node) {
160
- return false;
161
- }
162
- if (node instanceof ShadowRoot) {
163
- return node.mode === "open" ? walk(node.host) : false;
149
+ let current = element;
150
+ while (current) {
151
+ if (current instanceof ShadowRoot) {
152
+ if (current.mode !== "open") {
153
+ return false;
154
+ }
155
+ current = current.host;
156
+ continue;
164
157
  }
165
- if (!(node instanceof Element)) {
166
- return walk(node.parentNode);
158
+ if (!(current instanceof Element)) {
159
+ current = current.parentNode;
160
+ continue;
167
161
  }
168
- if (node === element && isFormControl(node) && isDisabled(node)) {
162
+ if (current === element && isFormControl(current) && isDisabled(current)) {
169
163
  return true;
170
164
  }
171
- if (node.hasAttribute("inert")) {
165
+ if (current.hasAttribute("inert")) {
172
166
  return true;
173
167
  }
174
- if (isFormControl(element) && node.tagName === "FIELDSET" && isDisabled(node)) {
175
- if (node.querySelector(":scope > legend:first-of-type")?.contains(element)) {
176
- return walk(node.parentNode);
168
+ if (isFormControl(element) && current.tagName === "FIELDSET" && isDisabled(current)) {
169
+ if (!current.querySelector(":scope > legend:first-of-type")?.contains(element)) {
170
+ return true;
177
171
  }
178
- return true;
179
172
  }
180
- return walk(node.parentNode);
173
+ current = current.parentNode;
181
174
  }
182
- return walk(element);
175
+ return false;
183
176
  }
184
177
  function isFormControl(element) {
185
- const tagName = element.tagName;
186
- return tagName === "BUTTON" || tagName === "INPUT" || tagName === "SELECT" || tagName === "TEXTAREA";
178
+ const name = element.tagName;
179
+ return name === "BUTTON" || name === "INPUT" || name === "SELECT" || name === "TEXTAREA";
180
+ }
181
+ function isUngroupedRadio(element) {
182
+ return element instanceof HTMLInputElement && element.type === "radio" && !!element.name;
187
183
  }
188
184
  function normalizeRadioGroup(elements) {
189
185
  let map = null;
190
186
  for (let i = 0, l = elements.length; i < l; i++) {
191
187
  const element = elements[i];
192
- if (!(element instanceof HTMLInputElement) || element.type !== "radio" || !element.name) {
188
+ if (!isUngroupedRadio(element)) {
193
189
  continue;
194
190
  }
195
191
  if (!map) {
@@ -207,7 +203,7 @@ function normalizeRadioGroup(elements) {
207
203
  placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
208
204
  }
209
205
  return elements.filter((element) => {
210
- if (element instanceof HTMLInputElement && element.type === "radio" && element.name) {
206
+ if (isUngroupedRadio(element)) {
211
207
  return placeholder.has(element);
212
208
  }
213
209
  return true;
@@ -237,7 +233,7 @@ function sortByTabIndex(elements) {
237
233
  * High-precision focus management utility with shadow DOM support.
238
234
  * Handles complex focus rules including tabindex ordering, radio groups, etc.
239
235
  *
240
- * @version 2.1.4
236
+ * @version 2.1.5
241
237
  * @author Yusuke Kamiyamane
242
238
  * @license MIT
243
239
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@
3
3
  * High-precision focus management utility with shadow DOM support.
4
4
  * Handles complex focus rules including tabindex ordering, radio groups, etc.
5
5
  *
6
- * @version 2.1.4
6
+ * @version 2.1.5
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * High-precision focus management utility with shadow DOM support.
4
4
  * Handles complex focus rules including tabindex ordering, radio groups, etc.
5
5
  *
6
- * @version 2.1.4
6
+ * @version 2.1.5
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -8,30 +8,31 @@ function getFocusables(container = document.body, options = {}) {
8
8
  const { composed = false } = options;
9
9
  const elements = [];
10
10
  if (composed) {
11
- let walk2 = function(node) {
11
+ let traverse2 = function(node) {
12
12
  if (node instanceof HTMLElement) {
13
13
  if (isFocusable(node)) {
14
14
  elements[elements.length] = node;
15
15
  }
16
16
  const shadow = node.shadowRoot;
17
17
  if (shadow && shadow.mode === "open") {
18
- walk2(shadow);
18
+ traverse2(shadow);
19
19
  }
20
- } else if (node instanceof HTMLSlotElement) {
20
+ }
21
+ if (node instanceof HTMLSlotElement) {
21
22
  const assigned = node.assignedElements({ flatten: true });
22
23
  if (assigned.length) {
23
24
  for (let i = 0, l = assigned.length; i < l; i++) {
24
- walk2(assigned[i]);
25
+ traverse2(assigned[i]);
25
26
  }
26
27
  return;
27
28
  }
28
29
  }
29
30
  const children = node.childNodes;
30
31
  for (let i = 0, l = children.length; i < l; i++) {
31
- walk2(children[i]);
32
+ traverse2(children[i]);
32
33
  }
33
34
  };
34
- walk2(container);
35
+ traverse2(container);
35
36
  } else {
36
37
  const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
37
38
  for (let i = 0, l = candidates.length; i < l; i++) {
@@ -73,10 +74,8 @@ function isFocusable(element) {
73
74
  return false;
74
75
  }
75
76
  const index = element.getAttribute("tabindex");
76
- if (index) {
77
- if (Number(index) < 0) {
78
- return false;
79
- }
77
+ if (index && Number(index) < 0) {
78
+ return false;
80
79
  }
81
80
  if (!element.matches(FOCUSABLE_SELECTOR)) {
82
81
  return false;
@@ -115,29 +114,21 @@ function getRelativeFocusable(container, offset, options) {
115
114
  return focusables[(offsetIndex + length) % length] ?? null;
116
115
  }
117
116
  function containsDeep(container, element) {
118
- function walk(node) {
119
- if (!node) {
120
- return false;
121
- }
122
- if (node === container) {
117
+ let current = element;
118
+ while (current) {
119
+ if (current === container) {
123
120
  return true;
124
121
  }
125
- if (node instanceof ShadowRoot) {
126
- return node.mode === "open" ? walk(node.host) : false;
127
- }
128
- return walk(node.parentNode);
122
+ current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
129
123
  }
130
- return walk(element);
124
+ return false;
131
125
  }
132
126
  function getActiveElement() {
133
- function walk(node) {
134
- if (!node) {
135
- return null;
136
- }
137
- const active = node.shadowRoot?.activeElement;
138
- return active ? walk(active) : node;
127
+ let current = document.activeElement;
128
+ while (current?.shadowRoot?.activeElement) {
129
+ current = current.shadowRoot.activeElement;
139
130
  }
140
- return walk(document.activeElement);
131
+ return current;
141
132
  }
142
133
  var tabIndexCache = /* @__PURE__ */ new WeakMap();
143
134
  function getTabIndex(element) {
@@ -150,44 +141,49 @@ function getTabIndex(element) {
150
141
  return index;
151
142
  }
152
143
  function isDisabled(element) {
153
- return "disabled" in element && element.disabled;
144
+ return "disabled" in element && !!element.disabled;
154
145
  }
155
146
  function isDisabledDeep(element) {
156
- function walk(node) {
157
- if (!node) {
158
- return false;
159
- }
160
- if (node instanceof ShadowRoot) {
161
- return node.mode === "open" ? walk(node.host) : false;
147
+ let current = element;
148
+ while (current) {
149
+ if (current instanceof ShadowRoot) {
150
+ if (current.mode !== "open") {
151
+ return false;
152
+ }
153
+ current = current.host;
154
+ continue;
162
155
  }
163
- if (!(node instanceof Element)) {
164
- return walk(node.parentNode);
156
+ if (!(current instanceof Element)) {
157
+ current = current.parentNode;
158
+ continue;
165
159
  }
166
- if (node === element && isFormControl(node) && isDisabled(node)) {
160
+ if (current === element && isFormControl(current) && isDisabled(current)) {
167
161
  return true;
168
162
  }
169
- if (node.hasAttribute("inert")) {
163
+ if (current.hasAttribute("inert")) {
170
164
  return true;
171
165
  }
172
- if (isFormControl(element) && node.tagName === "FIELDSET" && isDisabled(node)) {
173
- if (node.querySelector(":scope > legend:first-of-type")?.contains(element)) {
174
- return walk(node.parentNode);
166
+ if (isFormControl(element) && current.tagName === "FIELDSET" && isDisabled(current)) {
167
+ if (!current.querySelector(":scope > legend:first-of-type")?.contains(element)) {
168
+ return true;
175
169
  }
176
- return true;
177
170
  }
178
- return walk(node.parentNode);
171
+ current = current.parentNode;
179
172
  }
180
- return walk(element);
173
+ return false;
181
174
  }
182
175
  function isFormControl(element) {
183
- const tagName = element.tagName;
184
- return tagName === "BUTTON" || tagName === "INPUT" || tagName === "SELECT" || tagName === "TEXTAREA";
176
+ const name = element.tagName;
177
+ return name === "BUTTON" || name === "INPUT" || name === "SELECT" || name === "TEXTAREA";
178
+ }
179
+ function isUngroupedRadio(element) {
180
+ return element instanceof HTMLInputElement && element.type === "radio" && !!element.name;
185
181
  }
186
182
  function normalizeRadioGroup(elements) {
187
183
  let map = null;
188
184
  for (let i = 0, l = elements.length; i < l; i++) {
189
185
  const element = elements[i];
190
- if (!(element instanceof HTMLInputElement) || element.type !== "radio" || !element.name) {
186
+ if (!isUngroupedRadio(element)) {
191
187
  continue;
192
188
  }
193
189
  if (!map) {
@@ -205,7 +201,7 @@ function normalizeRadioGroup(elements) {
205
201
  placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
206
202
  }
207
203
  return elements.filter((element) => {
208
- if (element instanceof HTMLInputElement && element.type === "radio" && element.name) {
204
+ if (isUngroupedRadio(element)) {
209
205
  return placeholder.has(element);
210
206
  }
211
207
  return true;
@@ -235,7 +231,7 @@ function sortByTabIndex(elements) {
235
231
  * High-precision focus management utility with shadow DOM support.
236
232
  * Handles complex focus rules including tabindex ordering, radio groups, etc.
237
233
  *
238
- * @version 2.1.4
234
+ * @version 2.1.5
239
235
  * @author Yusuke Kamiyamane
240
236
  * @license MIT
241
237
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "power-focusable",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
4
4
  "description": "High-precision focus management utility with shadow DOM support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",