power-focusable 2.1.3 → 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 +58 -66
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +58 -66
- package/package.json +1 -1
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
|
|
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
|
-
|
|
20
|
+
traverse2(shadow);
|
|
21
21
|
}
|
|
22
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
34
|
+
traverse2(children[i]);
|
|
34
35
|
}
|
|
35
36
|
};
|
|
36
|
-
|
|
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++) {
|
|
@@ -74,11 +75,9 @@ function isFocusable(element) {
|
|
|
74
75
|
if (element.hasAttribute("hidden") || element.hasAttribute("inert")) {
|
|
75
76
|
return false;
|
|
76
77
|
}
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
78
|
+
const index = element.getAttribute("tabindex");
|
|
79
|
+
if (index && Number(index) < 0) {
|
|
80
|
+
return false;
|
|
82
81
|
}
|
|
83
82
|
if (!element.matches(FOCUSABLE_SELECTOR)) {
|
|
84
83
|
return false;
|
|
@@ -95,7 +94,7 @@ function isFocusable(element) {
|
|
|
95
94
|
}
|
|
96
95
|
return true;
|
|
97
96
|
}
|
|
98
|
-
function getRelativeFocusable(container, offset
|
|
97
|
+
function getRelativeFocusable(container, offset, options) {
|
|
99
98
|
const { active: a = null, composed = false, wrap = false } = options;
|
|
100
99
|
const focusables = getFocusables(container, { composed });
|
|
101
100
|
const { length } = focusables;
|
|
@@ -117,29 +116,21 @@ function getRelativeFocusable(container, offset = 0, options) {
|
|
|
117
116
|
return focusables[(offsetIndex + length) % length] ?? null;
|
|
118
117
|
}
|
|
119
118
|
function containsDeep(container, element) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (node === container) {
|
|
119
|
+
let current = element;
|
|
120
|
+
while (current) {
|
|
121
|
+
if (current === container) {
|
|
125
122
|
return true;
|
|
126
123
|
}
|
|
127
|
-
|
|
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
|
|
126
|
+
return false;
|
|
133
127
|
}
|
|
134
128
|
function getActiveElement() {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
133
|
+
return current;
|
|
143
134
|
}
|
|
144
135
|
var tabIndexCache = /* @__PURE__ */ new WeakMap();
|
|
145
136
|
function getTabIndex(element) {
|
|
@@ -147,71 +138,72 @@ function getTabIndex(element) {
|
|
|
147
138
|
if (cached !== void 0) {
|
|
148
139
|
return cached;
|
|
149
140
|
}
|
|
150
|
-
const
|
|
151
|
-
tabIndexCache.set(element,
|
|
152
|
-
return
|
|
141
|
+
const index = Number(element.getAttribute("tabindex"));
|
|
142
|
+
tabIndexCache.set(element, index);
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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 (!(
|
|
166
|
-
|
|
158
|
+
if (!(current instanceof Element)) {
|
|
159
|
+
current = current.parentNode;
|
|
160
|
+
continue;
|
|
167
161
|
}
|
|
168
|
-
if (
|
|
162
|
+
if (current === element && isFormControl(current) && isDisabled(current)) {
|
|
169
163
|
return true;
|
|
170
164
|
}
|
|
171
|
-
if (
|
|
165
|
+
if (current.hasAttribute("inert")) {
|
|
172
166
|
return true;
|
|
173
167
|
}
|
|
174
|
-
if (isFormControl(element) &&
|
|
175
|
-
if (
|
|
176
|
-
return
|
|
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
|
-
|
|
173
|
+
current = current.parentNode;
|
|
181
174
|
}
|
|
182
|
-
return
|
|
175
|
+
return false;
|
|
183
176
|
}
|
|
184
177
|
function isFormControl(element) {
|
|
185
|
-
const
|
|
186
|
-
return
|
|
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
|
|
193
|
-
|
|
194
|
-
map = /* @__PURE__ */ new Map();
|
|
195
|
-
}
|
|
196
|
-
const key = `${element.form?.id ?? "no-form"}::${element.name}`;
|
|
197
|
-
const group = map.get(key) ?? map.set(key, []).get(key);
|
|
198
|
-
group[group.length] = element;
|
|
188
|
+
if (!isUngroupedRadio(element)) {
|
|
189
|
+
continue;
|
|
199
190
|
}
|
|
191
|
+
if (!map) {
|
|
192
|
+
map = /* @__PURE__ */ new Map();
|
|
193
|
+
}
|
|
194
|
+
const key = `${element.form?.id ?? "no-form"}::${element.name}`;
|
|
195
|
+
const group = map.get(key) ?? map.set(key, []).get(key);
|
|
196
|
+
group[group.length] = element;
|
|
200
197
|
}
|
|
201
198
|
if (!map) {
|
|
202
199
|
return elements;
|
|
203
200
|
}
|
|
204
201
|
const placeholder = /* @__PURE__ */ new Set();
|
|
205
202
|
for (const group of map.values()) {
|
|
206
|
-
|
|
207
|
-
const enabled = group.filter(isFocusable);
|
|
208
|
-
if (enabled.length) {
|
|
209
|
-
placeholder.add(enabled.find((radio) => radio.checked) ?? enabled[0]);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
203
|
+
placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
|
|
212
204
|
}
|
|
213
205
|
return elements.filter((element) => {
|
|
214
|
-
if (element
|
|
206
|
+
if (isUngroupedRadio(element)) {
|
|
215
207
|
return placeholder.has(element);
|
|
216
208
|
}
|
|
217
209
|
return true;
|
|
@@ -241,7 +233,7 @@ function sortByTabIndex(elements) {
|
|
|
241
233
|
* High-precision focus management utility with shadow DOM support.
|
|
242
234
|
* Handles complex focus rules including tabindex ordering, radio groups, etc.
|
|
243
235
|
*
|
|
244
|
-
* @version 2.1.
|
|
236
|
+
* @version 2.1.5
|
|
245
237
|
* @author Yusuke Kamiyamane
|
|
246
238
|
* @license MIT
|
|
247
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
18
|
+
traverse2(shadow);
|
|
19
19
|
}
|
|
20
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
32
|
+
traverse2(children[i]);
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
|
-
|
|
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++) {
|
|
@@ -72,11 +73,9 @@ function isFocusable(element) {
|
|
|
72
73
|
if (element.hasAttribute("hidden") || element.hasAttribute("inert")) {
|
|
73
74
|
return false;
|
|
74
75
|
}
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
76
|
+
const index = element.getAttribute("tabindex");
|
|
77
|
+
if (index && Number(index) < 0) {
|
|
78
|
+
return false;
|
|
80
79
|
}
|
|
81
80
|
if (!element.matches(FOCUSABLE_SELECTOR)) {
|
|
82
81
|
return false;
|
|
@@ -93,7 +92,7 @@ function isFocusable(element) {
|
|
|
93
92
|
}
|
|
94
93
|
return true;
|
|
95
94
|
}
|
|
96
|
-
function getRelativeFocusable(container, offset
|
|
95
|
+
function getRelativeFocusable(container, offset, options) {
|
|
97
96
|
const { active: a = null, composed = false, wrap = false } = options;
|
|
98
97
|
const focusables = getFocusables(container, { composed });
|
|
99
98
|
const { length } = focusables;
|
|
@@ -115,29 +114,21 @@ function getRelativeFocusable(container, offset = 0, options) {
|
|
|
115
114
|
return focusables[(offsetIndex + length) % length] ?? null;
|
|
116
115
|
}
|
|
117
116
|
function containsDeep(container, element) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
if (node === container) {
|
|
117
|
+
let current = element;
|
|
118
|
+
while (current) {
|
|
119
|
+
if (current === container) {
|
|
123
120
|
return true;
|
|
124
121
|
}
|
|
125
|
-
|
|
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
|
|
124
|
+
return false;
|
|
131
125
|
}
|
|
132
126
|
function getActiveElement() {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
131
|
+
return current;
|
|
141
132
|
}
|
|
142
133
|
var tabIndexCache = /* @__PURE__ */ new WeakMap();
|
|
143
134
|
function getTabIndex(element) {
|
|
@@ -145,71 +136,72 @@ function getTabIndex(element) {
|
|
|
145
136
|
if (cached !== void 0) {
|
|
146
137
|
return cached;
|
|
147
138
|
}
|
|
148
|
-
const
|
|
149
|
-
tabIndexCache.set(element,
|
|
150
|
-
return
|
|
139
|
+
const index = Number(element.getAttribute("tabindex"));
|
|
140
|
+
tabIndexCache.set(element, index);
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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 (!(
|
|
164
|
-
|
|
156
|
+
if (!(current instanceof Element)) {
|
|
157
|
+
current = current.parentNode;
|
|
158
|
+
continue;
|
|
165
159
|
}
|
|
166
|
-
if (
|
|
160
|
+
if (current === element && isFormControl(current) && isDisabled(current)) {
|
|
167
161
|
return true;
|
|
168
162
|
}
|
|
169
|
-
if (
|
|
163
|
+
if (current.hasAttribute("inert")) {
|
|
170
164
|
return true;
|
|
171
165
|
}
|
|
172
|
-
if (isFormControl(element) &&
|
|
173
|
-
if (
|
|
174
|
-
return
|
|
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
|
-
|
|
171
|
+
current = current.parentNode;
|
|
179
172
|
}
|
|
180
|
-
return
|
|
173
|
+
return false;
|
|
181
174
|
}
|
|
182
175
|
function isFormControl(element) {
|
|
183
|
-
const
|
|
184
|
-
return
|
|
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
|
|
191
|
-
|
|
192
|
-
map = /* @__PURE__ */ new Map();
|
|
193
|
-
}
|
|
194
|
-
const key = `${element.form?.id ?? "no-form"}::${element.name}`;
|
|
195
|
-
const group = map.get(key) ?? map.set(key, []).get(key);
|
|
196
|
-
group[group.length] = element;
|
|
186
|
+
if (!isUngroupedRadio(element)) {
|
|
187
|
+
continue;
|
|
197
188
|
}
|
|
189
|
+
if (!map) {
|
|
190
|
+
map = /* @__PURE__ */ new Map();
|
|
191
|
+
}
|
|
192
|
+
const key = `${element.form?.id ?? "no-form"}::${element.name}`;
|
|
193
|
+
const group = map.get(key) ?? map.set(key, []).get(key);
|
|
194
|
+
group[group.length] = element;
|
|
198
195
|
}
|
|
199
196
|
if (!map) {
|
|
200
197
|
return elements;
|
|
201
198
|
}
|
|
202
199
|
const placeholder = /* @__PURE__ */ new Set();
|
|
203
200
|
for (const group of map.values()) {
|
|
204
|
-
|
|
205
|
-
const enabled = group.filter(isFocusable);
|
|
206
|
-
if (enabled.length) {
|
|
207
|
-
placeholder.add(enabled.find((radio) => radio.checked) ?? enabled[0]);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
201
|
+
placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
|
|
210
202
|
}
|
|
211
203
|
return elements.filter((element) => {
|
|
212
|
-
if (element
|
|
204
|
+
if (isUngroupedRadio(element)) {
|
|
213
205
|
return placeholder.has(element);
|
|
214
206
|
}
|
|
215
207
|
return true;
|
|
@@ -239,7 +231,7 @@ function sortByTabIndex(elements) {
|
|
|
239
231
|
* High-precision focus management utility with shadow DOM support.
|
|
240
232
|
* Handles complex focus rules including tabindex ordering, radio groups, etc.
|
|
241
233
|
*
|
|
242
|
-
* @version 2.1.
|
|
234
|
+
* @version 2.1.5
|
|
243
235
|
* @author Yusuke Kamiyamane
|
|
244
236
|
* @license MIT
|
|
245
237
|
* @copyright Copyright (c) Yusuke Kamiyamane
|