@wordpress/dom 4.54.1-next.v.202609031004.0 → 4.55.0
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/CHANGELOG.md +6 -0
- package/build/focusable.cjs +12 -0
- package/build/focusable.cjs.map +2 -2
- package/build-module/focusable.mjs +12 -0
- package/build-module/focusable.mjs.map +2 -2
- package/build-types/focusable.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/dom/test/safe-html.jsdom.test.js +1 -0
- package/src/dom/test/strip-html.jsdom.test.js +1 -0
- package/src/focusable.js +16 -2
- package/src/test/dom.jsdom.test.js +1 -0
- package/src/test/focusable.browser.test.js +83 -0
- package/src/test/focusable.jsdom.test.js +5 -0
- package/src/test/tabbable.jsdom.test.js +1 -0
- package/src/test/utils/create-element.js +3 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 4.55.0 (2026-09-10)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- `focusable.find`: Exclude elements hidden by CSS `visibility` or `content-visibility` while preserving explicitly visible descendants ([#82574](https://github.com/WordPress/gutenberg/pull/82574)).
|
|
10
|
+
|
|
5
11
|
## 4.54.0 (2026-08-26)
|
|
6
12
|
|
|
7
13
|
### Bug Fixes
|
package/build/focusable.cjs
CHANGED
|
@@ -40,6 +40,18 @@ function buildSelector(sequential) {
|
|
|
40
40
|
].join(",");
|
|
41
41
|
}
|
|
42
42
|
function isVisible(element) {
|
|
43
|
+
if (typeof element.checkVisibility === "function") {
|
|
44
|
+
if (!element.checkVisibility({ visibilityProperty: true })) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
const visibility = element.ownerDocument.defaultView?.getComputedStyle(
|
|
49
|
+
element
|
|
50
|
+
).visibility;
|
|
51
|
+
if (visibility === "hidden" || visibility === "collapse") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
43
55
|
return element.offsetWidth > 0 || element.offsetHeight > 0 || element.getClientRects().length > 0;
|
|
44
56
|
}
|
|
45
57
|
function isValidFocusableArea(element) {
|
package/build/focusable.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/focusable.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * References:\n *\n * Focusable:\n * - https://www.w3.org/TR/html5/editing.html#focus-management\n *\n * Sequential focus navigation:\n * - https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute\n *\n * Disabled elements:\n * - https://www.w3.org/TR/html5/disabled-elements.html#disabled-elements\n *\n * getClientRects algorithm (requiring layout box):\n * - https://www.w3.org/TR/cssom-view-1/#extension-to-the-element-interface\n *\n * AREA elements associated with an IMG:\n * - https://w3c.github.io/html/editing.html#data-model\n */\n\n/**\n * Returns a CSS selector used to query for focusable elements.\n *\n * @param {boolean} sequential If set, only query elements that are sequentially\n * focusable. Non-interactive elements with a\n * negative `tabindex` are focusable but not\n * sequentially focusable.\n * https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute\n *\n * @return {string} CSS selector.\n */\nfunction buildSelector( sequential ) {\n\treturn [\n\t\tsequential ? '[tabindex]:not([tabindex^=\"-\"])' : '[tabindex]',\n\t\t'a[href]',\n\t\t'button:not([disabled])',\n\t\t'input:not([type=\"hidden\"]):not([disabled])',\n\t\t'select:not([disabled])',\n\t\t'textarea:not([disabled])',\n\t\t'iframe:not([tabindex^=\"-\"])',\n\t\t'object',\n\t\t'embed',\n\t\t'summary',\n\t\t'area[href]',\n\t\t'[contenteditable]:not([contenteditable=false])',\n\t].join( ',' );\n}\n\n/**\n * Returns true if the specified element
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BA,SAAS,cAAe,YAAa;AACpC,SAAO;AAAA,IACN,aAAa,oCAAoC;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,EAAE,KAAM,GAAI;AACb;AAUA,SAAS,UAAW,SAAU;AAC7B,SACC,QAAQ,cAAc,KACtB,QAAQ,eAAe,KACvB,QAAQ,eAAe,EAAE,SAAS;AAEpC;AAWA,SAAS,qBAAsB,SAAU;AAExC,QAAM,MAAM,QAAQ,QAAS,WAAY;AACzC,MAAK,CAAE,KAAM;AACZ,WAAO;AAAA,EACR;AAGA,QAAM,MAAM,QAAQ,cAAc;AAAA,IACjC,kBAAkB,IAAI,OAAO;AAAA,EAC9B;AACA,SAAO,CAAC,CAAE,OAAO,UAAW,GAAI;AACjC;AAgBO,SAAS,KAAM,SAAS,EAAE,aAAa,MAAM,IAAI,CAAC,GAAI;AAE5D,QAAM,WAAW,QAAQ,iBAAkB,cAAe,UAAW,CAAE;AAEvE,SAAO,MAAM,KAAM,QAAS,EAAE,OAAQ,CAAE,YAAa;AACpD,QAAK,CAAE,UAAW,OAAQ,GAAI;AAC7B,aAAO;AAAA,IACR;AAGA,QAAK,QAAQ,QAAS,SAAU,GAAI;AACnC,aAAO;AAAA,IACR;AAEA,UAAM,EAAE,SAAS,IAAI;AACrB,QAAK,WAAW,UAAW;AAC1B,aAAO;AAAA;AAAA,QAC2B;AAAA,MAClC;AAAA,IACD;AAEA,WAAO;AAAA,EACR,CAAE;AACH;",
|
|
4
|
+
"sourcesContent": ["/**\n * References:\n *\n * Focusable:\n * - https://www.w3.org/TR/html5/editing.html#focus-management\n *\n * Sequential focus navigation:\n * - https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute\n *\n * Disabled elements:\n * - https://www.w3.org/TR/html5/disabled-elements.html#disabled-elements\n *\n * getClientRects algorithm (requiring layout box):\n * - https://www.w3.org/TR/cssom-view-1/#extension-to-the-element-interface\n *\n * AREA elements associated with an IMG:\n * - https://w3c.github.io/html/editing.html#data-model\n */\n\n/**\n * Returns a CSS selector used to query for focusable elements.\n *\n * @param {boolean} sequential If set, only query elements that are sequentially\n * focusable. Non-interactive elements with a\n * negative `tabindex` are focusable but not\n * sequentially focusable.\n * https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute\n *\n * @return {string} CSS selector.\n */\nfunction buildSelector( sequential ) {\n\treturn [\n\t\tsequential ? '[tabindex]:not([tabindex^=\"-\"])' : '[tabindex]',\n\t\t'a[href]',\n\t\t'button:not([disabled])',\n\t\t'input:not([type=\"hidden\"]):not([disabled])',\n\t\t'select:not([disabled])',\n\t\t'textarea:not([disabled])',\n\t\t'iframe:not([tabindex^=\"-\"])',\n\t\t'object',\n\t\t'embed',\n\t\t'summary',\n\t\t'area[href]',\n\t\t'[contenteditable]:not([contenteditable=false])',\n\t].join( ',' );\n}\n\n/**\n * Returns true if the specified element has a layout box and is not hidden by\n * CSS visibility or content visibility.\n *\n * @param {HTMLElement} element DOM element to test.\n *\n * @return {boolean} Whether element is visible.\n */\nfunction isVisible( element ) {\n\tif ( typeof element.checkVisibility === 'function' ) {\n\t\tif ( ! element.checkVisibility( { visibilityProperty: true } ) ) {\n\t\t\treturn false;\n\t\t}\n\t} else {\n\t\tconst visibility =\n\t\t\telement.ownerDocument.defaultView?.getComputedStyle(\n\t\t\t\telement\n\t\t\t).visibility;\n\t\tif ( visibility === 'hidden' || visibility === 'collapse' ) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn (\n\t\telement.offsetWidth > 0 ||\n\t\telement.offsetHeight > 0 ||\n\t\telement.getClientRects().length > 0\n\t);\n}\n\n/**\n * Returns true if the specified area element is a valid focusable element, or\n * false otherwise. Area is only focusable if within a map where a named map\n * referenced by an image somewhere in the document.\n *\n * @param {HTMLAreaElement} element DOM area element to test.\n *\n * @return {boolean} Whether area element is valid for focus.\n */\nfunction isValidFocusableArea( element ) {\n\t/** @type {HTMLMapElement | null} */\n\tconst map = element.closest( 'map[name]' );\n\tif ( ! map ) {\n\t\treturn false;\n\t}\n\n\t/** @type {HTMLImageElement | null} */\n\tconst img = element.ownerDocument.querySelector(\n\t\t'img[usemap=\"#' + map.name + '\"]'\n\t);\n\treturn !! img && isVisible( img );\n}\n\n/**\n * Returns all focusable elements within a given context.\n *\n * @param {Element} context Element in which to search.\n * @param {Object} options\n * @param {boolean} [options.sequential] If set, only return elements that are\n * sequentially focusable.\n * Non-interactive elements with a\n * negative `tabindex` are focusable but\n * not sequentially focusable.\n * https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute\n *\n * @return {HTMLElement[]} Focusable elements.\n */\nexport function find( context, { sequential = false } = {} ) {\n\t/** @type {NodeListOf<HTMLElement>} */\n\tconst elements = context.querySelectorAll( buildSelector( sequential ) );\n\n\treturn Array.from( elements ).filter( ( element ) => {\n\t\tif ( ! isVisible( element ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Elements inside an inert subtree are not focusable.\n\t\tif ( element.closest( '[inert]' ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst { nodeName } = element;\n\t\tif ( 'AREA' === nodeName ) {\n\t\t\treturn isValidFocusableArea(\n\t\t\t\t/** @type {HTMLAreaElement} */ ( element )\n\t\t\t);\n\t\t}\n\n\t\treturn true;\n\t} );\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA8BA,SAAS,cAAe,YAAa;AACpC,SAAO;AAAA,IACN,aAAa,oCAAoC;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,EAAE,KAAM,GAAI;AACb;AAUA,SAAS,UAAW,SAAU;AAC7B,MAAK,OAAO,QAAQ,oBAAoB,YAAa;AACpD,QAAK,CAAE,QAAQ,gBAAiB,EAAE,oBAAoB,KAAK,CAAE,GAAI;AAChE,aAAO;AAAA,IACR;AAAA,EACD,OAAO;AACN,UAAM,aACL,QAAQ,cAAc,aAAa;AAAA,MAClC;AAAA,IACD,EAAE;AACH,QAAK,eAAe,YAAY,eAAe,YAAa;AAC3D,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SACC,QAAQ,cAAc,KACtB,QAAQ,eAAe,KACvB,QAAQ,eAAe,EAAE,SAAS;AAEpC;AAWA,SAAS,qBAAsB,SAAU;AAExC,QAAM,MAAM,QAAQ,QAAS,WAAY;AACzC,MAAK,CAAE,KAAM;AACZ,WAAO;AAAA,EACR;AAGA,QAAM,MAAM,QAAQ,cAAc;AAAA,IACjC,kBAAkB,IAAI,OAAO;AAAA,EAC9B;AACA,SAAO,CAAC,CAAE,OAAO,UAAW,GAAI;AACjC;AAgBO,SAAS,KAAM,SAAS,EAAE,aAAa,MAAM,IAAI,CAAC,GAAI;AAE5D,QAAM,WAAW,QAAQ,iBAAkB,cAAe,UAAW,CAAE;AAEvE,SAAO,MAAM,KAAM,QAAS,EAAE,OAAQ,CAAE,YAAa;AACpD,QAAK,CAAE,UAAW,OAAQ,GAAI;AAC7B,aAAO;AAAA,IACR;AAGA,QAAK,QAAQ,QAAS,SAAU,GAAI;AACnC,aAAO;AAAA,IACR;AAEA,UAAM,EAAE,SAAS,IAAI;AACrB,QAAK,WAAW,UAAW;AAC1B,aAAO;AAAA;AAAA,QAC2B;AAAA,MAClC;AAAA,IACD;AAEA,WAAO;AAAA,EACR,CAAE;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,6 +16,18 @@ function buildSelector(sequential) {
|
|
|
16
16
|
].join(",");
|
|
17
17
|
}
|
|
18
18
|
function isVisible(element) {
|
|
19
|
+
if (typeof element.checkVisibility === "function") {
|
|
20
|
+
if (!element.checkVisibility({ visibilityProperty: true })) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
const visibility = element.ownerDocument.defaultView?.getComputedStyle(
|
|
25
|
+
element
|
|
26
|
+
).visibility;
|
|
27
|
+
if (visibility === "hidden" || visibility === "collapse") {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
19
31
|
return element.offsetWidth > 0 || element.offsetHeight > 0 || element.getClientRects().length > 0;
|
|
20
32
|
}
|
|
21
33
|
function isValidFocusableArea(element) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/focusable.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * References:\n *\n * Focusable:\n * - https://www.w3.org/TR/html5/editing.html#focus-management\n *\n * Sequential focus navigation:\n * - https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute\n *\n * Disabled elements:\n * - https://www.w3.org/TR/html5/disabled-elements.html#disabled-elements\n *\n * getClientRects algorithm (requiring layout box):\n * - https://www.w3.org/TR/cssom-view-1/#extension-to-the-element-interface\n *\n * AREA elements associated with an IMG:\n * - https://w3c.github.io/html/editing.html#data-model\n */\n\n/**\n * Returns a CSS selector used to query for focusable elements.\n *\n * @param {boolean} sequential If set, only query elements that are sequentially\n * focusable. Non-interactive elements with a\n * negative `tabindex` are focusable but not\n * sequentially focusable.\n * https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute\n *\n * @return {string} CSS selector.\n */\nfunction buildSelector( sequential ) {\n\treturn [\n\t\tsequential ? '[tabindex]:not([tabindex^=\"-\"])' : '[tabindex]',\n\t\t'a[href]',\n\t\t'button:not([disabled])',\n\t\t'input:not([type=\"hidden\"]):not([disabled])',\n\t\t'select:not([disabled])',\n\t\t'textarea:not([disabled])',\n\t\t'iframe:not([tabindex^=\"-\"])',\n\t\t'object',\n\t\t'embed',\n\t\t'summary',\n\t\t'area[href]',\n\t\t'[contenteditable]:not([contenteditable=false])',\n\t].join( ',' );\n}\n\n/**\n * Returns true if the specified element
|
|
5
|
-
"mappings": ";AA8BA,SAAS,cAAe,YAAa;AACpC,SAAO;AAAA,IACN,aAAa,oCAAoC;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,EAAE,KAAM,GAAI;AACb;AAUA,SAAS,UAAW,SAAU;AAC7B,SACC,QAAQ,cAAc,KACtB,QAAQ,eAAe,KACvB,QAAQ,eAAe,EAAE,SAAS;AAEpC;AAWA,SAAS,qBAAsB,SAAU;AAExC,QAAM,MAAM,QAAQ,QAAS,WAAY;AACzC,MAAK,CAAE,KAAM;AACZ,WAAO;AAAA,EACR;AAGA,QAAM,MAAM,QAAQ,cAAc;AAAA,IACjC,kBAAkB,IAAI,OAAO;AAAA,EAC9B;AACA,SAAO,CAAC,CAAE,OAAO,UAAW,GAAI;AACjC;AAgBO,SAAS,KAAM,SAAS,EAAE,aAAa,MAAM,IAAI,CAAC,GAAI;AAE5D,QAAM,WAAW,QAAQ,iBAAkB,cAAe,UAAW,CAAE;AAEvE,SAAO,MAAM,KAAM,QAAS,EAAE,OAAQ,CAAE,YAAa;AACpD,QAAK,CAAE,UAAW,OAAQ,GAAI;AAC7B,aAAO;AAAA,IACR;AAGA,QAAK,QAAQ,QAAS,SAAU,GAAI;AACnC,aAAO;AAAA,IACR;AAEA,UAAM,EAAE,SAAS,IAAI;AACrB,QAAK,WAAW,UAAW;AAC1B,aAAO;AAAA;AAAA,QAC2B;AAAA,MAClC;AAAA,IACD;AAEA,WAAO;AAAA,EACR,CAAE;AACH;",
|
|
4
|
+
"sourcesContent": ["/**\n * References:\n *\n * Focusable:\n * - https://www.w3.org/TR/html5/editing.html#focus-management\n *\n * Sequential focus navigation:\n * - https://www.w3.org/TR/html5/editing.html#sequential-focus-navigation-and-the-tabindex-attribute\n *\n * Disabled elements:\n * - https://www.w3.org/TR/html5/disabled-elements.html#disabled-elements\n *\n * getClientRects algorithm (requiring layout box):\n * - https://www.w3.org/TR/cssom-view-1/#extension-to-the-element-interface\n *\n * AREA elements associated with an IMG:\n * - https://w3c.github.io/html/editing.html#data-model\n */\n\n/**\n * Returns a CSS selector used to query for focusable elements.\n *\n * @param {boolean} sequential If set, only query elements that are sequentially\n * focusable. Non-interactive elements with a\n * negative `tabindex` are focusable but not\n * sequentially focusable.\n * https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute\n *\n * @return {string} CSS selector.\n */\nfunction buildSelector( sequential ) {\n\treturn [\n\t\tsequential ? '[tabindex]:not([tabindex^=\"-\"])' : '[tabindex]',\n\t\t'a[href]',\n\t\t'button:not([disabled])',\n\t\t'input:not([type=\"hidden\"]):not([disabled])',\n\t\t'select:not([disabled])',\n\t\t'textarea:not([disabled])',\n\t\t'iframe:not([tabindex^=\"-\"])',\n\t\t'object',\n\t\t'embed',\n\t\t'summary',\n\t\t'area[href]',\n\t\t'[contenteditable]:not([contenteditable=false])',\n\t].join( ',' );\n}\n\n/**\n * Returns true if the specified element has a layout box and is not hidden by\n * CSS visibility or content visibility.\n *\n * @param {HTMLElement} element DOM element to test.\n *\n * @return {boolean} Whether element is visible.\n */\nfunction isVisible( element ) {\n\tif ( typeof element.checkVisibility === 'function' ) {\n\t\tif ( ! element.checkVisibility( { visibilityProperty: true } ) ) {\n\t\t\treturn false;\n\t\t}\n\t} else {\n\t\tconst visibility =\n\t\t\telement.ownerDocument.defaultView?.getComputedStyle(\n\t\t\t\telement\n\t\t\t).visibility;\n\t\tif ( visibility === 'hidden' || visibility === 'collapse' ) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn (\n\t\telement.offsetWidth > 0 ||\n\t\telement.offsetHeight > 0 ||\n\t\telement.getClientRects().length > 0\n\t);\n}\n\n/**\n * Returns true if the specified area element is a valid focusable element, or\n * false otherwise. Area is only focusable if within a map where a named map\n * referenced by an image somewhere in the document.\n *\n * @param {HTMLAreaElement} element DOM area element to test.\n *\n * @return {boolean} Whether area element is valid for focus.\n */\nfunction isValidFocusableArea( element ) {\n\t/** @type {HTMLMapElement | null} */\n\tconst map = element.closest( 'map[name]' );\n\tif ( ! map ) {\n\t\treturn false;\n\t}\n\n\t/** @type {HTMLImageElement | null} */\n\tconst img = element.ownerDocument.querySelector(\n\t\t'img[usemap=\"#' + map.name + '\"]'\n\t);\n\treturn !! img && isVisible( img );\n}\n\n/**\n * Returns all focusable elements within a given context.\n *\n * @param {Element} context Element in which to search.\n * @param {Object} options\n * @param {boolean} [options.sequential] If set, only return elements that are\n * sequentially focusable.\n * Non-interactive elements with a\n * negative `tabindex` are focusable but\n * not sequentially focusable.\n * https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute\n *\n * @return {HTMLElement[]} Focusable elements.\n */\nexport function find( context, { sequential = false } = {} ) {\n\t/** @type {NodeListOf<HTMLElement>} */\n\tconst elements = context.querySelectorAll( buildSelector( sequential ) );\n\n\treturn Array.from( elements ).filter( ( element ) => {\n\t\tif ( ! isVisible( element ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Elements inside an inert subtree are not focusable.\n\t\tif ( element.closest( '[inert]' ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst { nodeName } = element;\n\t\tif ( 'AREA' === nodeName ) {\n\t\t\treturn isValidFocusableArea(\n\t\t\t\t/** @type {HTMLAreaElement} */ ( element )\n\t\t\t);\n\t\t}\n\n\t\treturn true;\n\t} );\n}\n"],
|
|
5
|
+
"mappings": ";AA8BA,SAAS,cAAe,YAAa;AACpC,SAAO;AAAA,IACN,aAAa,oCAAoC;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,EAAE,KAAM,GAAI;AACb;AAUA,SAAS,UAAW,SAAU;AAC7B,MAAK,OAAO,QAAQ,oBAAoB,YAAa;AACpD,QAAK,CAAE,QAAQ,gBAAiB,EAAE,oBAAoB,KAAK,CAAE,GAAI;AAChE,aAAO;AAAA,IACR;AAAA,EACD,OAAO;AACN,UAAM,aACL,QAAQ,cAAc,aAAa;AAAA,MAClC;AAAA,IACD,EAAE;AACH,QAAK,eAAe,YAAY,eAAe,YAAa;AAC3D,aAAO;AAAA,IACR;AAAA,EACD;AAEA,SACC,QAAQ,cAAc,KACtB,QAAQ,eAAe,KACvB,QAAQ,eAAe,EAAE,SAAS;AAEpC;AAWA,SAAS,qBAAsB,SAAU;AAExC,QAAM,MAAM,QAAQ,QAAS,WAAY;AACzC,MAAK,CAAE,KAAM;AACZ,WAAO;AAAA,EACR;AAGA,QAAM,MAAM,QAAQ,cAAc;AAAA,IACjC,kBAAkB,IAAI,OAAO;AAAA,EAC9B;AACA,SAAO,CAAC,CAAE,OAAO,UAAW,GAAI;AACjC;AAgBO,SAAS,KAAM,SAAS,EAAE,aAAa,MAAM,IAAI,CAAC,GAAI;AAE5D,QAAM,WAAW,QAAQ,iBAAkB,cAAe,UAAW,CAAE;AAEvE,SAAO,MAAM,KAAM,QAAS,EAAE,OAAQ,CAAE,YAAa;AACpD,QAAK,CAAE,UAAW,OAAQ,GAAI;AAC7B,aAAO;AAAA,IACR;AAGA,QAAK,QAAQ,QAAS,SAAU,GAAI;AACnC,aAAO;AAAA,IACR;AAEA,UAAM,EAAE,SAAS,IAAI;AACrB,QAAK,WAAW,UAAW;AAC1B,aAAO;AAAA;AAAA,QAC2B;AAAA,MAClC;AAAA,IACD;AAEA,WAAO;AAAA,EACR,CAAE;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"focusable.d.ts","sourceRoot":"","sources":["../src/focusable.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;
|
|
1
|
+
{"version":3,"file":"focusable.d.ts","sourceRoot":"","sources":["../src/focusable.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAmFH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,IAAI,CAAE,OAAO,EAXlB,OAWkB,EAAE,EAAE,UAAkB,EAAE,GATlD;IAA0B,UAAU,AAApC,CAOA,EAPQ,OAAO,CAOf;CAEuD,GAF9C,WAAW,EAAE,CAyBxB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/dom",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.55.0",
|
|
4
4
|
"description": "DOM utilities module for WordPress.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -44,13 +44,13 @@
|
|
|
44
44
|
"types": "build-types",
|
|
45
45
|
"sideEffects": false,
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@wordpress/deprecated": "^4.
|
|
47
|
+
"@wordpress/deprecated": "^4.55.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"vitest": "^
|
|
50
|
+
"vitest": "^5.0.0"
|
|
51
51
|
},
|
|
52
52
|
"publishConfig": {
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "485f42ae8a1c58ceea18371a507fd4acfa86fbd8"
|
|
56
56
|
}
|
package/src/focusable.js
CHANGED
|
@@ -46,14 +46,28 @@ function buildSelector( sequential ) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
|
-
* Returns true if the specified element
|
|
50
|
-
*
|
|
49
|
+
* Returns true if the specified element has a layout box and is not hidden by
|
|
50
|
+
* CSS visibility or content visibility.
|
|
51
51
|
*
|
|
52
52
|
* @param {HTMLElement} element DOM element to test.
|
|
53
53
|
*
|
|
54
54
|
* @return {boolean} Whether element is visible.
|
|
55
55
|
*/
|
|
56
56
|
function isVisible( element ) {
|
|
57
|
+
if ( typeof element.checkVisibility === 'function' ) {
|
|
58
|
+
if ( ! element.checkVisibility( { visibilityProperty: true } ) ) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
const visibility =
|
|
63
|
+
element.ownerDocument.defaultView?.getComputedStyle(
|
|
64
|
+
element
|
|
65
|
+
).visibility;
|
|
66
|
+
if ( visibility === 'hidden' || visibility === 'collapse' ) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
57
71
|
return (
|
|
58
72
|
element.offsetWidth > 0 ||
|
|
59
73
|
element.offsetHeight > 0 ||
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { find } from '../focusable';
|
|
3
|
+
|
|
4
|
+
describe( 'focusable.find() CSS visibility', () => {
|
|
5
|
+
let node;
|
|
6
|
+
|
|
7
|
+
beforeEach( () => {
|
|
8
|
+
node = document.createElement( 'div' );
|
|
9
|
+
document.body.appendChild( node );
|
|
10
|
+
} );
|
|
11
|
+
|
|
12
|
+
afterEach( () => {
|
|
13
|
+
node.remove();
|
|
14
|
+
} );
|
|
15
|
+
|
|
16
|
+
it.each( [
|
|
17
|
+
[ 'visibility: hidden', '<input style="visibility: hidden">' ],
|
|
18
|
+
[
|
|
19
|
+
'inherited visibility: hidden',
|
|
20
|
+
'<div style="visibility: hidden"><input></div>',
|
|
21
|
+
],
|
|
22
|
+
[ 'visibility: collapse', '<input style="visibility: collapse">' ],
|
|
23
|
+
[
|
|
24
|
+
'content-visibility: hidden ancestor',
|
|
25
|
+
'<div style="content-visibility: hidden"><input></div>',
|
|
26
|
+
],
|
|
27
|
+
] )(
|
|
28
|
+
'excludes inputs with %s even when they have layout boxes',
|
|
29
|
+
( _, html ) => {
|
|
30
|
+
node.innerHTML = html;
|
|
31
|
+
const input = node.querySelector( 'input' );
|
|
32
|
+
const checkVisibility = vi.spyOn( input, 'checkVisibility' );
|
|
33
|
+
|
|
34
|
+
expect( input.offsetWidth ).toBeGreaterThan( 0 );
|
|
35
|
+
expect( input.offsetHeight ).toBeGreaterThan( 0 );
|
|
36
|
+
expect( input.getClientRects().length ).toBeGreaterThan( 0 );
|
|
37
|
+
input.focus();
|
|
38
|
+
expect( document.activeElement ).not.toBe( input );
|
|
39
|
+
expect( find( node ) ).toEqual( [] );
|
|
40
|
+
expect( checkVisibility ).toHaveBeenCalledWith( {
|
|
41
|
+
visibilityProperty: true,
|
|
42
|
+
} );
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
it( 'includes inputs that restore visibility inside a hidden ancestor', () => {
|
|
47
|
+
node.style.visibility = 'hidden';
|
|
48
|
+
node.innerHTML = '<input style="visibility: visible">';
|
|
49
|
+
const input = node.querySelector( 'input' );
|
|
50
|
+
|
|
51
|
+
input.focus();
|
|
52
|
+
expect( document.activeElement ).toBe( input );
|
|
53
|
+
expect( find( node ) ).toEqual( [ input ] );
|
|
54
|
+
} );
|
|
55
|
+
|
|
56
|
+
it( 'uses the owning window for the computed-style fallback', () => {
|
|
57
|
+
const iframe = document.createElement( 'iframe' );
|
|
58
|
+
node.appendChild( iframe );
|
|
59
|
+
const iframeDocument = iframe.contentDocument;
|
|
60
|
+
iframeDocument.body.innerHTML = `
|
|
61
|
+
<div style="visibility: hidden">
|
|
62
|
+
<input>
|
|
63
|
+
<input style="visibility: visible">
|
|
64
|
+
</div>
|
|
65
|
+
`;
|
|
66
|
+
const [ hiddenInput, visibleInput ] =
|
|
67
|
+
iframeDocument.querySelectorAll( 'input' );
|
|
68
|
+
Object.defineProperties( hiddenInput, {
|
|
69
|
+
checkVisibility: { value: undefined },
|
|
70
|
+
} );
|
|
71
|
+
Object.defineProperties( visibleInput, {
|
|
72
|
+
checkVisibility: { value: undefined },
|
|
73
|
+
} );
|
|
74
|
+
const getComputedStyle = vi.spyOn(
|
|
75
|
+
iframeDocument.defaultView,
|
|
76
|
+
'getComputedStyle'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect( find( iframeDocument.body ) ).toEqual( [ visibleInput ] );
|
|
80
|
+
expect( getComputedStyle ).toHaveBeenCalledWith( hiddenInput );
|
|
81
|
+
expect( getComputedStyle ).toHaveBeenCalledWith( visibleInput );
|
|
82
|
+
} );
|
|
83
|
+
} );
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
1
2
|
import createElement from './utils/create-element';
|
|
2
3
|
import { find } from '../focusable';
|
|
3
4
|
|
|
@@ -107,6 +108,8 @@ describe( 'focusable', () => {
|
|
|
107
108
|
const node = createElement( 'div' );
|
|
108
109
|
const input = createElement( 'input' );
|
|
109
110
|
node.appendChild( input );
|
|
111
|
+
// Keep the fixture connected so JSDOM invalidates computed styles.
|
|
112
|
+
document.body.appendChild( node );
|
|
110
113
|
|
|
111
114
|
input.style.visibility = 'hidden';
|
|
112
115
|
expect( find( node ) ).toEqual( [] );
|
|
@@ -125,6 +128,8 @@ describe( 'focusable', () => {
|
|
|
125
128
|
const node = createElement( 'div' );
|
|
126
129
|
const input = createElement( 'input' );
|
|
127
130
|
node.appendChild( input );
|
|
131
|
+
// Keep the fixture connected so JSDOM invalidates computed styles.
|
|
132
|
+
document.body.appendChild( node );
|
|
128
133
|
|
|
129
134
|
node.style.visibility = 'hidden';
|
|
130
135
|
expect( find( node ) ).toEqual( [] );
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Given an element type, returns an HTMLElement with an emulated layout,
|
|
3
|
-
* since JSDOM does have its own internal layout engine.
|
|
3
|
+
* since JSDOM does not have its own internal layout engine.
|
|
4
4
|
*
|
|
5
5
|
* @param {string} type Element type.
|
|
6
6
|
*
|
|
@@ -14,9 +14,8 @@ export default function createElement( type ) {
|
|
|
14
14
|
let isHidden = false;
|
|
15
15
|
let node = this;
|
|
16
16
|
do {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
node.style.visibility === 'hidden';
|
|
17
|
+
// CSS visibility does not remove an element's layout box.
|
|
18
|
+
isHidden = node.style.display === 'none';
|
|
20
19
|
|
|
21
20
|
node = node.parentNode;
|
|
22
21
|
} while (
|