power-focusable 2.1.4 → 2.1.6
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 +50 -51
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +50 -51
- 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++) {
|
|
@@ -75,10 +76,8 @@ function isFocusable(element) {
|
|
|
75
76
|
return false;
|
|
76
77
|
}
|
|
77
78
|
const index = element.getAttribute("tabindex");
|
|
78
|
-
if (index) {
|
|
79
|
-
|
|
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;
|
|
@@ -96,13 +95,16 @@ function isFocusable(element) {
|
|
|
96
95
|
return true;
|
|
97
96
|
}
|
|
98
97
|
function getRelativeFocusable(container, offset, options) {
|
|
99
|
-
const {
|
|
98
|
+
const {
|
|
99
|
+
active = getActiveElement(),
|
|
100
|
+
composed = false,
|
|
101
|
+
wrap = false
|
|
102
|
+
} = options;
|
|
100
103
|
const focusables = getFocusables(container, { composed });
|
|
101
104
|
const { length } = focusables;
|
|
102
105
|
if (!length) {
|
|
103
106
|
return null;
|
|
104
107
|
}
|
|
105
|
-
const active = a ?? getActiveElement();
|
|
106
108
|
if (!active || !containsDeep(container, active)) {
|
|
107
109
|
return null;
|
|
108
110
|
}
|
|
@@ -117,29 +119,21 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
117
119
|
return focusables[(offsetIndex + length) % length] ?? null;
|
|
118
120
|
}
|
|
119
121
|
function containsDeep(container, element) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
if (node === container) {
|
|
122
|
+
let current = element;
|
|
123
|
+
while (current) {
|
|
124
|
+
if (current === container) {
|
|
125
125
|
return true;
|
|
126
126
|
}
|
|
127
|
-
|
|
128
|
-
return node.mode === "open" ? walk(node.host) : false;
|
|
129
|
-
}
|
|
130
|
-
return walk(node.parentNode);
|
|
127
|
+
current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
|
|
131
128
|
}
|
|
132
|
-
return
|
|
129
|
+
return false;
|
|
133
130
|
}
|
|
134
131
|
function getActiveElement() {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
const active = node.shadowRoot?.activeElement;
|
|
140
|
-
return active ? walk(active) : node;
|
|
132
|
+
let current = document.activeElement;
|
|
133
|
+
while (current?.shadowRoot?.activeElement) {
|
|
134
|
+
current = current.shadowRoot.activeElement;
|
|
141
135
|
}
|
|
142
|
-
return
|
|
136
|
+
return current;
|
|
143
137
|
}
|
|
144
138
|
var tabIndexCache = /* @__PURE__ */ new WeakMap();
|
|
145
139
|
function getTabIndex(element) {
|
|
@@ -152,44 +146,49 @@ function getTabIndex(element) {
|
|
|
152
146
|
return index;
|
|
153
147
|
}
|
|
154
148
|
function isDisabled(element) {
|
|
155
|
-
return "disabled" in element && element.disabled;
|
|
149
|
+
return "disabled" in element && !!element.disabled;
|
|
156
150
|
}
|
|
157
151
|
function isDisabledDeep(element) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
let current = element;
|
|
153
|
+
while (current) {
|
|
154
|
+
if (current instanceof ShadowRoot) {
|
|
155
|
+
if (current.mode !== "open") {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
current = current.host;
|
|
159
|
+
continue;
|
|
164
160
|
}
|
|
165
|
-
if (!(
|
|
166
|
-
|
|
161
|
+
if (!(current instanceof Element)) {
|
|
162
|
+
current = current.parentNode;
|
|
163
|
+
continue;
|
|
167
164
|
}
|
|
168
|
-
if (
|
|
165
|
+
if (current === element && isFormControl(current) && isDisabled(current)) {
|
|
169
166
|
return true;
|
|
170
167
|
}
|
|
171
|
-
if (
|
|
168
|
+
if (current.hasAttribute("inert")) {
|
|
172
169
|
return true;
|
|
173
170
|
}
|
|
174
|
-
if (isFormControl(element) &&
|
|
175
|
-
if (
|
|
176
|
-
return
|
|
171
|
+
if (isFormControl(element) && current.tagName === "FIELDSET" && isDisabled(current)) {
|
|
172
|
+
if (!current.querySelector(":scope > legend:first-of-type")?.contains(element)) {
|
|
173
|
+
return true;
|
|
177
174
|
}
|
|
178
|
-
return true;
|
|
179
175
|
}
|
|
180
|
-
|
|
176
|
+
current = current.parentNode;
|
|
181
177
|
}
|
|
182
|
-
return
|
|
178
|
+
return false;
|
|
183
179
|
}
|
|
184
180
|
function isFormControl(element) {
|
|
185
|
-
const
|
|
186
|
-
return
|
|
181
|
+
const name = element.tagName;
|
|
182
|
+
return name === "BUTTON" || name === "INPUT" || name === "SELECT" || name === "TEXTAREA";
|
|
183
|
+
}
|
|
184
|
+
function isUngroupedRadio(element) {
|
|
185
|
+
return element instanceof HTMLInputElement && element.type === "radio" && !!element.name;
|
|
187
186
|
}
|
|
188
187
|
function normalizeRadioGroup(elements) {
|
|
189
188
|
let map = null;
|
|
190
189
|
for (let i = 0, l = elements.length; i < l; i++) {
|
|
191
190
|
const element = elements[i];
|
|
192
|
-
if (!(element
|
|
191
|
+
if (!isUngroupedRadio(element)) {
|
|
193
192
|
continue;
|
|
194
193
|
}
|
|
195
194
|
if (!map) {
|
|
@@ -207,7 +206,7 @@ function normalizeRadioGroup(elements) {
|
|
|
207
206
|
placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
|
|
208
207
|
}
|
|
209
208
|
return elements.filter((element) => {
|
|
210
|
-
if (element
|
|
209
|
+
if (isUngroupedRadio(element)) {
|
|
211
210
|
return placeholder.has(element);
|
|
212
211
|
}
|
|
213
212
|
return true;
|
|
@@ -237,7 +236,7 @@ function sortByTabIndex(elements) {
|
|
|
237
236
|
* High-precision focus management utility with shadow DOM support.
|
|
238
237
|
* Handles complex focus rules including tabindex ordering, radio groups, etc.
|
|
239
238
|
*
|
|
240
|
-
* @version 2.1.
|
|
239
|
+
* @version 2.1.6
|
|
241
240
|
* @author Yusuke Kamiyamane
|
|
242
241
|
* @license MIT
|
|
243
242
|
* @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.6
|
|
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.6
|
|
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++) {
|
|
@@ -73,10 +74,8 @@ function isFocusable(element) {
|
|
|
73
74
|
return false;
|
|
74
75
|
}
|
|
75
76
|
const index = element.getAttribute("tabindex");
|
|
76
|
-
if (index) {
|
|
77
|
-
|
|
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;
|
|
@@ -94,13 +93,16 @@ function isFocusable(element) {
|
|
|
94
93
|
return true;
|
|
95
94
|
}
|
|
96
95
|
function getRelativeFocusable(container, offset, options) {
|
|
97
|
-
const {
|
|
96
|
+
const {
|
|
97
|
+
active = getActiveElement(),
|
|
98
|
+
composed = false,
|
|
99
|
+
wrap = false
|
|
100
|
+
} = options;
|
|
98
101
|
const focusables = getFocusables(container, { composed });
|
|
99
102
|
const { length } = focusables;
|
|
100
103
|
if (!length) {
|
|
101
104
|
return null;
|
|
102
105
|
}
|
|
103
|
-
const active = a ?? getActiveElement();
|
|
104
106
|
if (!active || !containsDeep(container, active)) {
|
|
105
107
|
return null;
|
|
106
108
|
}
|
|
@@ -115,29 +117,21 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
115
117
|
return focusables[(offsetIndex + length) % length] ?? null;
|
|
116
118
|
}
|
|
117
119
|
function containsDeep(container, element) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
if (node === container) {
|
|
120
|
+
let current = element;
|
|
121
|
+
while (current) {
|
|
122
|
+
if (current === container) {
|
|
123
123
|
return true;
|
|
124
124
|
}
|
|
125
|
-
|
|
126
|
-
return node.mode === "open" ? walk(node.host) : false;
|
|
127
|
-
}
|
|
128
|
-
return walk(node.parentNode);
|
|
125
|
+
current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
|
|
129
126
|
}
|
|
130
|
-
return
|
|
127
|
+
return false;
|
|
131
128
|
}
|
|
132
129
|
function getActiveElement() {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
const active = node.shadowRoot?.activeElement;
|
|
138
|
-
return active ? walk(active) : node;
|
|
130
|
+
let current = document.activeElement;
|
|
131
|
+
while (current?.shadowRoot?.activeElement) {
|
|
132
|
+
current = current.shadowRoot.activeElement;
|
|
139
133
|
}
|
|
140
|
-
return
|
|
134
|
+
return current;
|
|
141
135
|
}
|
|
142
136
|
var tabIndexCache = /* @__PURE__ */ new WeakMap();
|
|
143
137
|
function getTabIndex(element) {
|
|
@@ -150,44 +144,49 @@ function getTabIndex(element) {
|
|
|
150
144
|
return index;
|
|
151
145
|
}
|
|
152
146
|
function isDisabled(element) {
|
|
153
|
-
return "disabled" in element && element.disabled;
|
|
147
|
+
return "disabled" in element && !!element.disabled;
|
|
154
148
|
}
|
|
155
149
|
function isDisabledDeep(element) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
150
|
+
let current = element;
|
|
151
|
+
while (current) {
|
|
152
|
+
if (current instanceof ShadowRoot) {
|
|
153
|
+
if (current.mode !== "open") {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
current = current.host;
|
|
157
|
+
continue;
|
|
162
158
|
}
|
|
163
|
-
if (!(
|
|
164
|
-
|
|
159
|
+
if (!(current instanceof Element)) {
|
|
160
|
+
current = current.parentNode;
|
|
161
|
+
continue;
|
|
165
162
|
}
|
|
166
|
-
if (
|
|
163
|
+
if (current === element && isFormControl(current) && isDisabled(current)) {
|
|
167
164
|
return true;
|
|
168
165
|
}
|
|
169
|
-
if (
|
|
166
|
+
if (current.hasAttribute("inert")) {
|
|
170
167
|
return true;
|
|
171
168
|
}
|
|
172
|
-
if (isFormControl(element) &&
|
|
173
|
-
if (
|
|
174
|
-
return
|
|
169
|
+
if (isFormControl(element) && current.tagName === "FIELDSET" && isDisabled(current)) {
|
|
170
|
+
if (!current.querySelector(":scope > legend:first-of-type")?.contains(element)) {
|
|
171
|
+
return true;
|
|
175
172
|
}
|
|
176
|
-
return true;
|
|
177
173
|
}
|
|
178
|
-
|
|
174
|
+
current = current.parentNode;
|
|
179
175
|
}
|
|
180
|
-
return
|
|
176
|
+
return false;
|
|
181
177
|
}
|
|
182
178
|
function isFormControl(element) {
|
|
183
|
-
const
|
|
184
|
-
return
|
|
179
|
+
const name = element.tagName;
|
|
180
|
+
return name === "BUTTON" || name === "INPUT" || name === "SELECT" || name === "TEXTAREA";
|
|
181
|
+
}
|
|
182
|
+
function isUngroupedRadio(element) {
|
|
183
|
+
return element instanceof HTMLInputElement && element.type === "radio" && !!element.name;
|
|
185
184
|
}
|
|
186
185
|
function normalizeRadioGroup(elements) {
|
|
187
186
|
let map = null;
|
|
188
187
|
for (let i = 0, l = elements.length; i < l; i++) {
|
|
189
188
|
const element = elements[i];
|
|
190
|
-
if (!(element
|
|
189
|
+
if (!isUngroupedRadio(element)) {
|
|
191
190
|
continue;
|
|
192
191
|
}
|
|
193
192
|
if (!map) {
|
|
@@ -205,7 +204,7 @@ function normalizeRadioGroup(elements) {
|
|
|
205
204
|
placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
|
|
206
205
|
}
|
|
207
206
|
return elements.filter((element) => {
|
|
208
|
-
if (element
|
|
207
|
+
if (isUngroupedRadio(element)) {
|
|
209
208
|
return placeholder.has(element);
|
|
210
209
|
}
|
|
211
210
|
return true;
|
|
@@ -235,7 +234,7 @@ function sortByTabIndex(elements) {
|
|
|
235
234
|
* High-precision focus management utility with shadow DOM support.
|
|
236
235
|
* Handles complex focus rules including tabindex ordering, radio groups, etc.
|
|
237
236
|
*
|
|
238
|
-
* @version 2.1.
|
|
237
|
+
* @version 2.1.6
|
|
239
238
|
* @author Yusuke Kamiyamane
|
|
240
239
|
* @license MIT
|
|
241
240
|
* @copyright Copyright (c) Yusuke Kamiyamane
|