@primer/react 38.6.3-rc.16be9114c → 38.6.3-rc.9e02ccb41
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.
Potentially problematic release.
This version of @primer/react might be problematic. Click here for more details.
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
Split AutocompleteContext into separate contexts for static values, setters, and dynamic state.
|
|
12
12
|
Components now subscribe only to the context slices they need, reducing re-renders.
|
|
13
13
|
|
|
14
|
+
- [#7342](https://github.com/primer/react/pull/7342) [`a8b42b2`](https://github.com/primer/react/commit/a8b42b2e4cff9575eae8df897e64c1ca67ee180a) Thanks [@mattcosta7](https://github.com/mattcosta7)! - perf(hasInteractiveNodes): Optimize with combined selector and early attribute checks
|
|
15
|
+
|
|
16
|
+
- Use combined querySelectorAll selector instead of recursive traversal
|
|
17
|
+
- Check attribute-based states (disabled, hidden, inert) before getComputedStyle
|
|
18
|
+
- Only call getComputedStyle when CSS-based visibility check is needed
|
|
19
|
+
|
|
14
20
|
## 38.6.2
|
|
15
21
|
|
|
16
22
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hasInteractiveNodes.d.ts","sourceRoot":"","sources":["../../../src/internal/utils/hasInteractiveNodes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hasInteractiveNodes.d.ts","sourceRoot":"","sources":["../../../src/internal/utils/hasInteractiveNodes.ts"],"names":[],"mappings":"AA2BA;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,EAAE,WAAW,CAAC,EAAE,WAAW,EAAE,WAkBxF"}
|
|
@@ -7,6 +7,9 @@ const nonValidSelectors = {
|
|
|
7
7
|
const interactiveElementsSelectors = [`a[href]`, `button`, 'summary', 'select', 'input:not([type=hidden])', 'textarea', '[tabindex="0"]', `audio[controls]`, `video[controls]`, `[contenteditable]`];
|
|
8
8
|
const interactiveElements = interactiveElementsSelectors.map(selector => `${selector}:not(${Object.values(nonValidSelectors).join('):not(')})`);
|
|
9
9
|
|
|
10
|
+
// Combined selector for fast querySelector check
|
|
11
|
+
const interactiveSelector = interactiveElements.join(', ');
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* Finds interactive nodes within the passed node.
|
|
12
15
|
* If the node itself is interactive, or children within are, it will return true.
|
|
@@ -23,26 +26,29 @@ function hasInteractiveNodes(node, ignoreNodes) {
|
|
|
23
26
|
// If one does exist, we can abort early.
|
|
24
27
|
|
|
25
28
|
const nodesToIgnore = [node];
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
|
|
30
|
+
// Performance optimization: Use querySelectorAll with combined selector first
|
|
31
|
+
// This avoids recursive getComputedStyle calls for each node
|
|
32
|
+
const candidates = node.querySelectorAll(interactiveSelector);
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
if (!nodesToIgnore.includes(candidate) && !isNonValidInteractiveNode(candidate)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
28
39
|
}
|
|
40
|
+
|
|
41
|
+
// Note: Only call getComputedStyle when CSS-based checks are insufficient
|
|
29
42
|
function isNonValidInteractiveNode(node) {
|
|
30
|
-
|
|
43
|
+
// Fast path: Check attribute-based states first (no style recalc needed)
|
|
31
44
|
const isNonInteractive = node.matches('[disabled], [hidden], [inert]');
|
|
45
|
+
if (isNonInteractive) return true;
|
|
46
|
+
|
|
47
|
+
// Only call getComputedStyle if attribute checks passed
|
|
48
|
+
// This is necessary for display:none and visibility:hidden which aren't detectable via attributes
|
|
49
|
+
const nodeStyle = getComputedStyle(node);
|
|
32
50
|
const isHiddenVisually = nodeStyle.display === 'none' || nodeStyle.visibility === 'hidden';
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
function findInteractiveChildNodes(node, ignoreNodes) {
|
|
36
|
-
if (!node) return;
|
|
37
|
-
const ignoreSelector = ignoreNodes.find(elem => elem === node);
|
|
38
|
-
const isNotValidNode = isNonValidInteractiveNode(node);
|
|
39
|
-
if (node.matches(interactiveElements.join(', ')) && !ignoreSelector && !isNotValidNode) {
|
|
40
|
-
return node;
|
|
41
|
-
}
|
|
42
|
-
for (const child of node.children) {
|
|
43
|
-
const interactiveNode = findInteractiveChildNodes(child, ignoreNodes);
|
|
44
|
-
if (interactiveNode) return true;
|
|
45
|
-
}
|
|
51
|
+
return isHiddenVisually;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export { hasInteractiveNodes };
|
package/package.json
CHANGED