@wordpress/dom 4.53.0 → 4.54.1-next.v.202608281122.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/dom/clean-node-list.cjs +5 -1
- package/build/dom/clean-node-list.cjs.map +3 -3
- package/build/dom/get-rectangle-from-range.cjs +15 -0
- package/build/dom/get-rectangle-from-range.cjs.map +2 -2
- package/build-module/dom/clean-node-list.mjs +5 -1
- package/build-module/dom/clean-node-list.mjs.map +2 -2
- package/build-module/dom/get-rectangle-from-range.mjs +15 -0
- package/build-module/dom/get-rectangle-from-range.mjs.map +2 -2
- package/build-types/dom/clean-node-list.d.ts.map +1 -1
- package/build-types/dom/get-rectangle-from-range.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/dom/clean-node-list.js +11 -2
- package/src/dom/get-rectangle-from-range.js +30 -0
- package/src/test/dom.js +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 4.54.0 (2026-08-26)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- `cleanNodeList`: Unwrap whitespace-only phrasing content elements instead of removing them, so pasting content like `a<em> </em>b` no longer loses the space between words ([#76696](https://github.com/WordPress/gutenberg/pull/76696)).
|
|
10
|
+
|
|
5
11
|
## 4.53.0 (2026-08-12)
|
|
6
12
|
|
|
7
13
|
|
|
@@ -55,7 +55,11 @@ function cleanNodeList(nodeList, doc, schema, inline) {
|
|
|
55
55
|
allowEmpty
|
|
56
56
|
} = schema[tag];
|
|
57
57
|
if (children && !allowEmpty && (0, import_is_empty.default)(node)) {
|
|
58
|
-
(0,
|
|
58
|
+
if ((0, import_phrasing_content.isPhrasingContent)(node) && node.hasChildNodes()) {
|
|
59
|
+
(0, import_unwrap.default)(node);
|
|
60
|
+
} else {
|
|
61
|
+
(0, import_remove.default)(node);
|
|
62
|
+
}
|
|
59
63
|
return;
|
|
60
64
|
}
|
|
61
65
|
if (node.hasAttributes()) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/dom/clean-node-list.js"],
|
|
4
|
-
"sourcesContent": ["import isEmpty from './is-empty';\nimport remove from './remove';\nimport unwrap from './unwrap';\nimport { isPhrasingContent } from '../phrasing-content';\nimport insertAfter from './insert-after';\nimport isElement from './is-element';\n\nconst noop = () => {};\n\n/**\n * @typedef SchemaItem\n * @property {string[]} [attributes] Attributes.\n * @property {(string | RegExp)[]} [classes] Classnames or RegExp to test against. Use '*' to keep all classes.\n * @property {'*' | { [tag: string]: SchemaItem }} [children] Child schemas.\n * @property {string[]} [require] Selectors to test required children against. Leave empty or undefined if there are no requirements.\n * @property {boolean} allowEmpty Whether to allow nodes without children.\n * @property {(node: Node) => boolean} [isMatch] Function to test whether a node is a match. If left undefined any node will be assumed to match.\n */\n\n/** @typedef {{ [tag: string]: SchemaItem }} Schema */\n\n/**\n * Given a schema, unwraps or removes nodes, attributes and classes on a node\n * list.\n *\n * @param {NodeList} nodeList The nodeList to filter.\n * @param {Document} doc The document of the nodeList.\n * @param {Schema} schema An array of functions that can mutate with the provided node.\n * @param {boolean} inline Whether to clean for inline mode.\n */\nexport default function cleanNodeList( nodeList, doc, schema, inline ) {\n\tArray.from( nodeList ).forEach(\n\t\t( /** @type {Node & { nextElementSibling?: unknown }} */ node ) => {\n\t\t\tconst tag = node.nodeName.toLowerCase();\n\n\t\t\t// It's a valid child, if the tag exists in the schema without an isMatch\n\t\t\t// function, or with an isMatch function that matches the node.\n\t\t\tif (\n\t\t\t\tschema.hasOwnProperty( tag ) &&\n\t\t\t\t( ! schema[ tag ].isMatch || schema[ tag ].isMatch?.( node ) )\n\t\t\t) {\n\t\t\t\tif ( isElement( node ) ) {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tattributes = [],\n\t\t\t\t\t\tclasses = [],\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\trequire = [],\n\t\t\t\t\t\tallowEmpty,\n\t\t\t\t\t} = schema[ tag ];\n\n\t\t\t\t\t// If the node is empty and it's supposed to have children,\n\t\t\t\t\t// remove the node.\n\t\t\t\t\tif ( children && ! allowEmpty && isEmpty( node ) ) {\n\t\t\t\t\t\tremove( node );\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( node.hasAttributes() ) {\n\t\t\t\t\t\t// Strip invalid attributes.\n\t\t\t\t\t\tArray.from( node.attributes ).forEach( ( { name } ) => {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tname !== 'class' &&\n\t\t\t\t\t\t\t\t! attributes.includes( name )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tnode.removeAttribute( name );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t// Strip invalid classes.\n\t\t\t\t\t\t// In jsdom-jscore, 'node.classList' can be undefined.\n\t\t\t\t\t\t// TODO: Explore patching this in jsdom-jscore.\n\t\t\t\t\t\tif ( node.classList && node.classList.length ) {\n\t\t\t\t\t\t\tconst mattchers = classes.map( ( item ) => {\n\t\t\t\t\t\t\t\tif ( item === '*' ) {\n\t\t\t\t\t\t\t\t\t// Keep all classes.\n\t\t\t\t\t\t\t\t\treturn () => true;\n\t\t\t\t\t\t\t\t} else if ( typeof item === 'string' ) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t/** @type {string} */ className\n\t\t\t\t\t\t\t\t\t) => className === item;\n\t\t\t\t\t\t\t\t} else if ( item instanceof RegExp ) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t/** @type {string} */ className\n\t\t\t\t\t\t\t\t\t) => item.test( className );\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn noop;\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\tArray.from( node.classList ).forEach( ( name ) => {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t! mattchers.some( ( isMatch ) =>\n\t\t\t\t\t\t\t\t\t\tisMatch( name )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tnode.classList.remove( name );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\tif ( ! node.classList.length ) {\n\t\t\t\t\t\t\t\tnode.removeAttribute( 'class' );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( node.hasChildNodes() ) {\n\t\t\t\t\t\t// Do not filter any content.\n\t\t\t\t\t\tif ( children === '*' ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Continue if the node is supposed to have children.\n\t\t\t\t\t\tif ( children ) {\n\t\t\t\t\t\t\t// If a parent requires certain children, but it does\n\t\t\t\t\t\t\t// not have them, drop the parent and continue.\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\trequire.length &&\n\t\t\t\t\t\t\t\t! node.querySelector( require.join( ',' ) )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t\t\t// If the node is at the top, phrasing content, and\n\t\t\t\t\t\t\t\t// contains children that are block content, unwrap\n\t\t\t\t\t\t\t\t// the node because it is invalid.\n\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\tnode.parentNode &&\n\t\t\t\t\t\t\t\tnode.parentNode.nodeName === 'BODY' &&\n\t\t\t\t\t\t\t\tisPhrasingContent( node )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tArray.from( node.childNodes ).some(\n\t\t\t\t\t\t\t\t\t\t( child ) =>\n\t\t\t\t\t\t\t\t\t\t\t! isPhrasingContent( child )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Remove children if the node is not supposed to have any.\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twhile ( node.firstChild ) {\n\t\t\t\t\t\t\t\tremove( node.firstChild );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Invalid child. Continue with schema at the same place and unwrap.\n\t\t\t} else {\n\t\t\t\tcleanNodeList( node.childNodes, doc, schema, inline );\n\n\t\t\t\t// For inline mode, insert a line break when unwrapping nodes that\n\t\t\t\t// are not phrasing content.\n\t\t\t\tif (\n\t\t\t\t\tinline &&\n\t\t\t\t\t! isPhrasingContent( node ) &&\n\t\t\t\t\tnode.nextElementSibling\n\t\t\t\t) {\n\t\t\t\t\tinsertAfter( doc.createElement( 'br' ), node );\n\t\t\t\t}\n\n\t\t\t\tunwrap( node );\n\t\t\t}\n\t\t}\n\t);\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAoB;AACpB,oBAAmB;AACnB,oBAAmB;AACnB,8BAAkC;AAClC,0BAAwB;AACxB,wBAAsB;AAEtB,IAAM,OAAO,MAAM;AAAC;AAuBL,SAAR,cAAgC,UAAU,KAAK,QAAQ,QAAS;AACtE,QAAM,KAAM,QAAS,EAAE;AAAA,IACtB,CAAyD,SAAU;AAClE,YAAM,MAAM,KAAK,SAAS,YAAY;AAItC,UACC,OAAO,eAAgB,GAAI,MACzB,CAAE,OAAQ,GAAI,EAAE,WAAW,OAAQ,GAAI,EAAE,UAAW,IAAK,IAC1D;AACD,gBAAK,kBAAAA,SAAW,IAAK,GAAI;AACxB,gBAAM;AAAA,YACL,aAAa,CAAC;AAAA,YACd,UAAU,CAAC;AAAA,YACX;AAAA,YACA,SAAAC,WAAU,CAAC;AAAA,YACX;AAAA,UACD,IAAI,OAAQ,GAAI;
|
|
6
|
-
"names": ["isElement", "require", "isEmpty", "
|
|
4
|
+
"sourcesContent": ["import isEmpty from './is-empty';\nimport remove from './remove';\nimport unwrap from './unwrap';\nimport { isPhrasingContent } from '../phrasing-content';\nimport insertAfter from './insert-after';\nimport isElement from './is-element';\n\nconst noop = () => {};\n\n/**\n * @typedef SchemaItem\n * @property {string[]} [attributes] Attributes.\n * @property {(string | RegExp)[]} [classes] Classnames or RegExp to test against. Use '*' to keep all classes.\n * @property {'*' | { [tag: string]: SchemaItem }} [children] Child schemas.\n * @property {string[]} [require] Selectors to test required children against. Leave empty or undefined if there are no requirements.\n * @property {boolean} allowEmpty Whether to allow nodes without children.\n * @property {(node: Node) => boolean} [isMatch] Function to test whether a node is a match. If left undefined any node will be assumed to match.\n */\n\n/** @typedef {{ [tag: string]: SchemaItem }} Schema */\n\n/**\n * Given a schema, unwraps or removes nodes, attributes and classes on a node\n * list.\n *\n * @param {NodeList} nodeList The nodeList to filter.\n * @param {Document} doc The document of the nodeList.\n * @param {Schema} schema An array of functions that can mutate with the provided node.\n * @param {boolean} inline Whether to clean for inline mode.\n */\nexport default function cleanNodeList( nodeList, doc, schema, inline ) {\n\tArray.from( nodeList ).forEach(\n\t\t( /** @type {Node & { nextElementSibling?: unknown }} */ node ) => {\n\t\t\tconst tag = node.nodeName.toLowerCase();\n\n\t\t\t// It's a valid child, if the tag exists in the schema without an isMatch\n\t\t\t// function, or with an isMatch function that matches the node.\n\t\t\tif (\n\t\t\t\tschema.hasOwnProperty( tag ) &&\n\t\t\t\t( ! schema[ tag ].isMatch || schema[ tag ].isMatch?.( node ) )\n\t\t\t) {\n\t\t\t\tif ( isElement( node ) ) {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tattributes = [],\n\t\t\t\t\t\tclasses = [],\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\trequire = [],\n\t\t\t\t\t\tallowEmpty,\n\t\t\t\t\t} = schema[ tag ];\n\n\t\t\t\t\t// If the node is empty and it's supposed to have children,\n\t\t\t\t\t// remove the node. For phrasing content nodes that contain\n\t\t\t\t\t// only whitespace, unwrap instead to preserve the text.\n\t\t\t\t\t// See https://github.com/WordPress/gutenberg/issues/50898\n\t\t\t\t\tif ( children && ! allowEmpty && isEmpty( node ) ) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tisPhrasingContent( node ) &&\n\t\t\t\t\t\t\tnode.hasChildNodes()\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tremove( node );\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( node.hasAttributes() ) {\n\t\t\t\t\t\t// Strip invalid attributes.\n\t\t\t\t\t\tArray.from( node.attributes ).forEach( ( { name } ) => {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tname !== 'class' &&\n\t\t\t\t\t\t\t\t! attributes.includes( name )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tnode.removeAttribute( name );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t// Strip invalid classes.\n\t\t\t\t\t\t// In jsdom-jscore, 'node.classList' can be undefined.\n\t\t\t\t\t\t// TODO: Explore patching this in jsdom-jscore.\n\t\t\t\t\t\tif ( node.classList && node.classList.length ) {\n\t\t\t\t\t\t\tconst mattchers = classes.map( ( item ) => {\n\t\t\t\t\t\t\t\tif ( item === '*' ) {\n\t\t\t\t\t\t\t\t\t// Keep all classes.\n\t\t\t\t\t\t\t\t\treturn () => true;\n\t\t\t\t\t\t\t\t} else if ( typeof item === 'string' ) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t/** @type {string} */ className\n\t\t\t\t\t\t\t\t\t) => className === item;\n\t\t\t\t\t\t\t\t} else if ( item instanceof RegExp ) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t/** @type {string} */ className\n\t\t\t\t\t\t\t\t\t) => item.test( className );\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn noop;\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\tArray.from( node.classList ).forEach( ( name ) => {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t! mattchers.some( ( isMatch ) =>\n\t\t\t\t\t\t\t\t\t\tisMatch( name )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tnode.classList.remove( name );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\tif ( ! node.classList.length ) {\n\t\t\t\t\t\t\t\tnode.removeAttribute( 'class' );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( node.hasChildNodes() ) {\n\t\t\t\t\t\t// Do not filter any content.\n\t\t\t\t\t\tif ( children === '*' ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Continue if the node is supposed to have children.\n\t\t\t\t\t\tif ( children ) {\n\t\t\t\t\t\t\t// If a parent requires certain children, but it does\n\t\t\t\t\t\t\t// not have them, drop the parent and continue.\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\trequire.length &&\n\t\t\t\t\t\t\t\t! node.querySelector( require.join( ',' ) )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t\t\t// If the node is at the top, phrasing content, and\n\t\t\t\t\t\t\t\t// contains children that are block content, unwrap\n\t\t\t\t\t\t\t\t// the node because it is invalid.\n\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\tnode.parentNode &&\n\t\t\t\t\t\t\t\tnode.parentNode.nodeName === 'BODY' &&\n\t\t\t\t\t\t\t\tisPhrasingContent( node )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tArray.from( node.childNodes ).some(\n\t\t\t\t\t\t\t\t\t\t( child ) =>\n\t\t\t\t\t\t\t\t\t\t\t! isPhrasingContent( child )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Remove children if the node is not supposed to have any.\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twhile ( node.firstChild ) {\n\t\t\t\t\t\t\t\tremove( node.firstChild );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Invalid child. Continue with schema at the same place and unwrap.\n\t\t\t} else {\n\t\t\t\tcleanNodeList( node.childNodes, doc, schema, inline );\n\n\t\t\t\t// For inline mode, insert a line break when unwrapping nodes that\n\t\t\t\t// are not phrasing content.\n\t\t\t\tif (\n\t\t\t\t\tinline &&\n\t\t\t\t\t! isPhrasingContent( node ) &&\n\t\t\t\t\tnode.nextElementSibling\n\t\t\t\t) {\n\t\t\t\t\tinsertAfter( doc.createElement( 'br' ), node );\n\t\t\t\t}\n\n\t\t\t\tunwrap( node );\n\t\t\t}\n\t\t}\n\t);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["isElement", "require", "isEmpty", "unwrap", "remove", "insertAfter"]
|
|
7
7
|
}
|
|
@@ -82,6 +82,21 @@ function getRectangleFromRange(range) {
|
|
|
82
82
|
if (rects.length > 1) {
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
|
+
if (rects.length === 1 && startContainer.nodeType === startContainer.TEXT_NODE && range.startOffset > 0 && range.startOffset < /** @type {Text} */
|
|
86
|
+
startContainer.length) {
|
|
87
|
+
(0, import_assert_is_defined.assertIsDefined)(ownerDocument, "ownerDocument");
|
|
88
|
+
const measure = (start, end) => {
|
|
89
|
+
const charRange = ownerDocument.createRange();
|
|
90
|
+
charRange.setStart(startContainer, start);
|
|
91
|
+
charRange.setEnd(startContainer, end);
|
|
92
|
+
return charRange.getBoundingClientRect();
|
|
93
|
+
};
|
|
94
|
+
const before = measure(range.startOffset - 1, range.startOffset);
|
|
95
|
+
const after = measure(range.startOffset, range.startOffset + 1);
|
|
96
|
+
if (before.bottom <= after.top) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
85
100
|
let rect = rects[0];
|
|
86
101
|
if (!rect || rect.height === 0) {
|
|
87
102
|
(0, import_assert_is_defined.assertIsDefined)(ownerDocument, "ownerDocument");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/dom/get-rectangle-from-range.js"],
|
|
4
|
-
"sourcesContent": ["import { assertIsDefined } from '../utils/assert-is-defined';\n\n/**\n * Get the rectangle of a given Range. Returns `null` if no suitable rectangle\n * can be found. Use instead of `Range.getBoundingClientRect()`, which is often\n * broken, especially for collapsed ranges.\n *\n * @param {Range} range The range.\n *\n * @return {DOMRect?} The rectangle.\n */\nexport default function getRectangleFromRange( range ) {\n\t// For uncollapsed ranges, get the rectangle that bounds the contents of the\n\t// range; this a rectangle enclosing the union of the bounding rectangles\n\t// for all the elements in the range.\n\tif ( ! range.collapsed ) {\n\t\tconst rects = Array.from( range.getClientRects() );\n\n\t\t// If there's just a single rect, return it.\n\t\tif ( rects.length === 1 ) {\n\t\t\treturn rects[ 0 ];\n\t\t}\n\n\t\t// Ignore tiny selection at the edge of a range.\n\t\tconst filteredRects = rects.filter( ( { width } ) => width > 1 );\n\n\t\t// If it's full of tiny selections, return browser default.\n\t\tif ( filteredRects.length === 0 ) {\n\t\t\treturn range.getBoundingClientRect();\n\t\t}\n\n\t\tif ( filteredRects.length === 1 ) {\n\t\t\treturn filteredRects[ 0 ];\n\t\t}\n\n\t\tlet {\n\t\t\ttop: furthestTop,\n\t\t\tbottom: furthestBottom,\n\t\t\tleft: furthestLeft,\n\t\t\tright: furthestRight,\n\t\t} = filteredRects[ 0 ];\n\n\t\tfor ( const { top, bottom, left, right } of filteredRects ) {\n\t\t\tif ( top < furthestTop ) {\n\t\t\t\tfurthestTop = top;\n\t\t\t}\n\t\t\tif ( bottom > furthestBottom ) {\n\t\t\t\tfurthestBottom = bottom;\n\t\t\t}\n\t\t\tif ( left < furthestLeft ) {\n\t\t\t\tfurthestLeft = left;\n\t\t\t}\n\t\t\tif ( right > furthestRight ) {\n\t\t\t\tfurthestRight = right;\n\t\t\t}\n\t\t}\n\n\t\treturn new window.DOMRect(\n\t\t\tfurthestLeft,\n\t\t\tfurthestTop,\n\t\t\tfurthestRight - furthestLeft,\n\t\t\tfurthestBottom - furthestTop\n\t\t);\n\t}\n\n\tconst { startContainer } = range;\n\tconst { ownerDocument } = startContainer;\n\n\t// Correct invalid \"BR\" ranges. The cannot contain any children.\n\tif ( startContainer.nodeName === 'BR' ) {\n\t\tconst { parentNode } = startContainer;\n\t\tassertIsDefined( parentNode, 'parentNode' );\n\t\tconst index = /** @type {Node[]} */ (\n\t\t\tArray.from( parentNode.childNodes )\n\t\t).indexOf( startContainer );\n\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\trange = ownerDocument.createRange();\n\t\trange.setStart( parentNode, index );\n\t\trange.setEnd( parentNode, index );\n\t}\n\n\tconst rects = range.getClientRects();\n\n\t// If we have multiple rectangles for a collapsed range, there's no way to\n\t// know which it is, so don't return anything.\n\tif ( rects.length > 1 ) {\n\t\treturn null;\n\t}\n\n\tlet rect = rects[ 0 ];\n\n\t// If the collapsed range starts (and therefore ends) at an element node,\n\t// `getClientRects` can be empty in some browsers. This can be resolved\n\t// by adding a temporary text node with zero-width space to the range.\n\t//\n\t// See: https://stackoverflow.com/a/6847328/995445\n\tif ( ! rect || rect.height === 0 ) {\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\tconst padNode = ownerDocument.createTextNode( '\\u200b' );\n\t\t// Do not modify the live range.\n\t\trange = range.cloneRange();\n\t\trange.insertNode( padNode );\n\t\trect = range.getClientRects()[ 0 ];\n\t\tassertIsDefined( padNode.parentNode, 'padNode.parentNode' );\n\t\tpadNode.parentNode.removeChild( padNode );\n\t}\n\n\treturn rect;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAAgC;AAWjB,SAAR,sBAAwC,OAAQ;AAItD,MAAK,CAAE,MAAM,WAAY;AACxB,UAAMA,SAAQ,MAAM,KAAM,MAAM,eAAe,CAAE;AAGjD,QAAKA,OAAM,WAAW,GAAI;AACzB,aAAOA,OAAO,CAAE;AAAA,IACjB;AAGA,UAAM,gBAAgBA,OAAM,OAAQ,CAAE,EAAE,MAAM,MAAO,QAAQ,CAAE;AAG/D,QAAK,cAAc,WAAW,GAAI;AACjC,aAAO,MAAM,sBAAsB;AAAA,IACpC;AAEA,QAAK,cAAc,WAAW,GAAI;AACjC,aAAO,cAAe,CAAE;AAAA,IACzB;AAEA,QAAI;AAAA,MACH,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,IACR,IAAI,cAAe,CAAE;AAErB,eAAY,EAAE,KAAK,QAAQ,MAAM,MAAM,KAAK,eAAgB;AAC3D,UAAK,MAAM,aAAc;AACxB,sBAAc;AAAA,MACf;AACA,UAAK,SAAS,gBAAiB;AAC9B,yBAAiB;AAAA,MAClB;AACA,UAAK,OAAO,cAAe;AAC1B,uBAAe;AAAA,MAChB;AACA,UAAK,QAAQ,eAAgB;AAC5B,wBAAgB;AAAA,MACjB;AAAA,IACD;AAEA,WAAO,IAAI,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IAClB;AAAA,EACD;AAEA,QAAM,EAAE,eAAe,IAAI;AAC3B,QAAM,EAAE,cAAc,IAAI;AAG1B,MAAK,eAAe,aAAa,MAAO;AACvC,UAAM,EAAE,WAAW,IAAI;AACvB,kDAAiB,YAAY,YAAa;AAC1C,UAAM;AAAA;AAAA,MACL,MAAM,KAAM,WAAW,UAAW,EACjC,QAAS,cAAe;AAAA;AAE1B,kDAAiB,eAAe,eAAgB;AAChD,YAAQ,cAAc,YAAY;AAClC,UAAM,SAAU,YAAY,KAAM;AAClC,UAAM,OAAQ,YAAY,KAAM;AAAA,EACjC;AAEA,QAAM,QAAQ,MAAM,eAAe;AAInC,MAAK,MAAM,SAAS,GAAI;AACvB,WAAO;AAAA,EACR;AAEA,MAAI,OAAO,MAAO,CAAE;AAOpB,MAAK,CAAE,QAAQ,KAAK,WAAW,GAAI;AAClC,kDAAiB,eAAe,eAAgB;AAChD,UAAM,UAAU,cAAc,eAAgB,GAAS;AAEvD,YAAQ,MAAM,WAAW;AACzB,UAAM,WAAY,OAAQ;AAC1B,WAAO,MAAM,eAAe,EAAG,CAAE;AACjC,kDAAiB,QAAQ,YAAY,oBAAqB;AAC1D,YAAQ,WAAW,YAAa,OAAQ;AAAA,EACzC;AAEA,SAAO;AACR;",
|
|
4
|
+
"sourcesContent": ["import { assertIsDefined } from '../utils/assert-is-defined';\n\n/**\n * Get the rectangle of a given Range. Returns `null` if no suitable rectangle\n * can be found. Use instead of `Range.getBoundingClientRect()`, which is often\n * broken, especially for collapsed ranges.\n *\n * @param {Range} range The range.\n *\n * @return {DOMRect?} The rectangle.\n */\nexport default function getRectangleFromRange( range ) {\n\t// For uncollapsed ranges, get the rectangle that bounds the contents of the\n\t// range; this a rectangle enclosing the union of the bounding rectangles\n\t// for all the elements in the range.\n\tif ( ! range.collapsed ) {\n\t\tconst rects = Array.from( range.getClientRects() );\n\n\t\t// If there's just a single rect, return it.\n\t\tif ( rects.length === 1 ) {\n\t\t\treturn rects[ 0 ];\n\t\t}\n\n\t\t// Ignore tiny selection at the edge of a range.\n\t\tconst filteredRects = rects.filter( ( { width } ) => width > 1 );\n\n\t\t// If it's full of tiny selections, return browser default.\n\t\tif ( filteredRects.length === 0 ) {\n\t\t\treturn range.getBoundingClientRect();\n\t\t}\n\n\t\tif ( filteredRects.length === 1 ) {\n\t\t\treturn filteredRects[ 0 ];\n\t\t}\n\n\t\tlet {\n\t\t\ttop: furthestTop,\n\t\t\tbottom: furthestBottom,\n\t\t\tleft: furthestLeft,\n\t\t\tright: furthestRight,\n\t\t} = filteredRects[ 0 ];\n\n\t\tfor ( const { top, bottom, left, right } of filteredRects ) {\n\t\t\tif ( top < furthestTop ) {\n\t\t\t\tfurthestTop = top;\n\t\t\t}\n\t\t\tif ( bottom > furthestBottom ) {\n\t\t\t\tfurthestBottom = bottom;\n\t\t\t}\n\t\t\tif ( left < furthestLeft ) {\n\t\t\t\tfurthestLeft = left;\n\t\t\t}\n\t\t\tif ( right > furthestRight ) {\n\t\t\t\tfurthestRight = right;\n\t\t\t}\n\t\t}\n\n\t\treturn new window.DOMRect(\n\t\t\tfurthestLeft,\n\t\t\tfurthestTop,\n\t\t\tfurthestRight - furthestLeft,\n\t\t\tfurthestBottom - furthestTop\n\t\t);\n\t}\n\n\tconst { startContainer } = range;\n\tconst { ownerDocument } = startContainer;\n\n\t// Correct invalid \"BR\" ranges. The cannot contain any children.\n\tif ( startContainer.nodeName === 'BR' ) {\n\t\tconst { parentNode } = startContainer;\n\t\tassertIsDefined( parentNode, 'parentNode' );\n\t\tconst index = /** @type {Node[]} */ (\n\t\t\tArray.from( parentNode.childNodes )\n\t\t).indexOf( startContainer );\n\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\trange = ownerDocument.createRange();\n\t\trange.setStart( parentNode, index );\n\t\trange.setEnd( parentNode, index );\n\t}\n\n\tconst rects = range.getClientRects();\n\n\t// If we have multiple rectangles for a collapsed range, there's no way to\n\t// know which it is, so don't return anything.\n\tif ( rects.length > 1 ) {\n\t\treturn null;\n\t}\n\n\t// A collapsed range at a soft line wrap is equally ambiguous: the same\n\t// position ends one line and starts the next. Some browsers (Gecko)\n\t// return a single rectangle for it, on the upper line, regardless of\n\t// where the caret is. Detect the boundary by measuring the characters\n\t// around the position: when they sit on different lines, there is no\n\t// way to know which line the caret is on, so don't return anything.\n\tif (\n\t\trects.length === 1 &&\n\t\tstartContainer.nodeType === startContainer.TEXT_NODE &&\n\t\trange.startOffset > 0 &&\n\t\trange.startOffset < /** @type {Text} */ ( startContainer ).length\n\t) {\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\tconst measure = (\n\t\t\t/** @type {number} */ start,\n\t\t\t/** @type {number} */ end\n\t\t) => {\n\t\t\tconst charRange = ownerDocument.createRange();\n\t\t\tcharRange.setStart( startContainer, start );\n\t\t\tcharRange.setEnd( startContainer, end );\n\t\t\treturn charRange.getBoundingClientRect();\n\t\t};\n\t\tconst before = measure( range.startOffset - 1, range.startOffset );\n\t\tconst after = measure( range.startOffset, range.startOffset + 1 );\n\n\t\tif ( before.bottom <= after.top ) {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tlet rect = rects[ 0 ];\n\n\t// If the collapsed range starts (and therefore ends) at an element node,\n\t// `getClientRects` can be empty in some browsers. This can be resolved\n\t// by adding a temporary text node with zero-width space to the range.\n\t//\n\t// See: https://stackoverflow.com/a/6847328/995445\n\tif ( ! rect || rect.height === 0 ) {\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\tconst padNode = ownerDocument.createTextNode( '\\u200b' );\n\t\t// Do not modify the live range.\n\t\trange = range.cloneRange();\n\t\trange.insertNode( padNode );\n\t\trect = range.getClientRects()[ 0 ];\n\t\tassertIsDefined( padNode.parentNode, 'padNode.parentNode' );\n\t\tpadNode.parentNode.removeChild( padNode );\n\t}\n\n\treturn rect;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAAgC;AAWjB,SAAR,sBAAwC,OAAQ;AAItD,MAAK,CAAE,MAAM,WAAY;AACxB,UAAMA,SAAQ,MAAM,KAAM,MAAM,eAAe,CAAE;AAGjD,QAAKA,OAAM,WAAW,GAAI;AACzB,aAAOA,OAAO,CAAE;AAAA,IACjB;AAGA,UAAM,gBAAgBA,OAAM,OAAQ,CAAE,EAAE,MAAM,MAAO,QAAQ,CAAE;AAG/D,QAAK,cAAc,WAAW,GAAI;AACjC,aAAO,MAAM,sBAAsB;AAAA,IACpC;AAEA,QAAK,cAAc,WAAW,GAAI;AACjC,aAAO,cAAe,CAAE;AAAA,IACzB;AAEA,QAAI;AAAA,MACH,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,IACR,IAAI,cAAe,CAAE;AAErB,eAAY,EAAE,KAAK,QAAQ,MAAM,MAAM,KAAK,eAAgB;AAC3D,UAAK,MAAM,aAAc;AACxB,sBAAc;AAAA,MACf;AACA,UAAK,SAAS,gBAAiB;AAC9B,yBAAiB;AAAA,MAClB;AACA,UAAK,OAAO,cAAe;AAC1B,uBAAe;AAAA,MAChB;AACA,UAAK,QAAQ,eAAgB;AAC5B,wBAAgB;AAAA,MACjB;AAAA,IACD;AAEA,WAAO,IAAI,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IAClB;AAAA,EACD;AAEA,QAAM,EAAE,eAAe,IAAI;AAC3B,QAAM,EAAE,cAAc,IAAI;AAG1B,MAAK,eAAe,aAAa,MAAO;AACvC,UAAM,EAAE,WAAW,IAAI;AACvB,kDAAiB,YAAY,YAAa;AAC1C,UAAM;AAAA;AAAA,MACL,MAAM,KAAM,WAAW,UAAW,EACjC,QAAS,cAAe;AAAA;AAE1B,kDAAiB,eAAe,eAAgB;AAChD,YAAQ,cAAc,YAAY;AAClC,UAAM,SAAU,YAAY,KAAM;AAClC,UAAM,OAAQ,YAAY,KAAM;AAAA,EACjC;AAEA,QAAM,QAAQ,MAAM,eAAe;AAInC,MAAK,MAAM,SAAS,GAAI;AACvB,WAAO;AAAA,EACR;AAQA,MACC,MAAM,WAAW,KACjB,eAAe,aAAa,eAAe,aAC3C,MAAM,cAAc,KACpB,MAAM;AAAA,EAAoC,eAAiB,QAC1D;AACD,kDAAiB,eAAe,eAAgB;AAChD,UAAM,UAAU,CACO,OACA,QAClB;AACJ,YAAM,YAAY,cAAc,YAAY;AAC5C,gBAAU,SAAU,gBAAgB,KAAM;AAC1C,gBAAU,OAAQ,gBAAgB,GAAI;AACtC,aAAO,UAAU,sBAAsB;AAAA,IACxC;AACA,UAAM,SAAS,QAAS,MAAM,cAAc,GAAG,MAAM,WAAY;AACjE,UAAM,QAAQ,QAAS,MAAM,aAAa,MAAM,cAAc,CAAE;AAEhE,QAAK,OAAO,UAAU,MAAM,KAAM;AACjC,aAAO;AAAA,IACR;AAAA,EACD;AAEA,MAAI,OAAO,MAAO,CAAE;AAOpB,MAAK,CAAE,QAAQ,KAAK,WAAW,GAAI;AAClC,kDAAiB,eAAe,eAAgB;AAChD,UAAM,UAAU,cAAc,eAAgB,GAAS;AAEvD,YAAQ,MAAM,WAAW;AACzB,UAAM,WAAY,OAAQ;AAC1B,WAAO,MAAM,eAAe,EAAG,CAAE;AACjC,kDAAiB,QAAQ,YAAY,oBAAqB;AAC1D,YAAQ,WAAW,YAAa,OAAQ;AAAA,EACzC;AAEA,SAAO;AACR;",
|
|
6
6
|
"names": ["rects"]
|
|
7
7
|
}
|
|
@@ -21,7 +21,11 @@ function cleanNodeList(nodeList, doc, schema, inline) {
|
|
|
21
21
|
allowEmpty
|
|
22
22
|
} = schema[tag];
|
|
23
23
|
if (children && !allowEmpty && isEmpty(node)) {
|
|
24
|
-
|
|
24
|
+
if (isPhrasingContent(node) && node.hasChildNodes()) {
|
|
25
|
+
unwrap(node);
|
|
26
|
+
} else {
|
|
27
|
+
remove(node);
|
|
28
|
+
}
|
|
25
29
|
return;
|
|
26
30
|
}
|
|
27
31
|
if (node.hasAttributes()) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/dom/clean-node-list.js"],
|
|
4
|
-
"sourcesContent": ["import isEmpty from './is-empty';\nimport remove from './remove';\nimport unwrap from './unwrap';\nimport { isPhrasingContent } from '../phrasing-content';\nimport insertAfter from './insert-after';\nimport isElement from './is-element';\n\nconst noop = () => {};\n\n/**\n * @typedef SchemaItem\n * @property {string[]} [attributes] Attributes.\n * @property {(string | RegExp)[]} [classes] Classnames or RegExp to test against. Use '*' to keep all classes.\n * @property {'*' | { [tag: string]: SchemaItem }} [children] Child schemas.\n * @property {string[]} [require] Selectors to test required children against. Leave empty or undefined if there are no requirements.\n * @property {boolean} allowEmpty Whether to allow nodes without children.\n * @property {(node: Node) => boolean} [isMatch] Function to test whether a node is a match. If left undefined any node will be assumed to match.\n */\n\n/** @typedef {{ [tag: string]: SchemaItem }} Schema */\n\n/**\n * Given a schema, unwraps or removes nodes, attributes and classes on a node\n * list.\n *\n * @param {NodeList} nodeList The nodeList to filter.\n * @param {Document} doc The document of the nodeList.\n * @param {Schema} schema An array of functions that can mutate with the provided node.\n * @param {boolean} inline Whether to clean for inline mode.\n */\nexport default function cleanNodeList( nodeList, doc, schema, inline ) {\n\tArray.from( nodeList ).forEach(\n\t\t( /** @type {Node & { nextElementSibling?: unknown }} */ node ) => {\n\t\t\tconst tag = node.nodeName.toLowerCase();\n\n\t\t\t// It's a valid child, if the tag exists in the schema without an isMatch\n\t\t\t// function, or with an isMatch function that matches the node.\n\t\t\tif (\n\t\t\t\tschema.hasOwnProperty( tag ) &&\n\t\t\t\t( ! schema[ tag ].isMatch || schema[ tag ].isMatch?.( node ) )\n\t\t\t) {\n\t\t\t\tif ( isElement( node ) ) {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tattributes = [],\n\t\t\t\t\t\tclasses = [],\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\trequire = [],\n\t\t\t\t\t\tallowEmpty,\n\t\t\t\t\t} = schema[ tag ];\n\n\t\t\t\t\t// If the node is empty and it's supposed to have children,\n\t\t\t\t\t// remove the node.\n\t\t\t\t\tif ( children && ! allowEmpty && isEmpty( node ) ) {\n\t\t\t\t\t\tremove( node );\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( node.hasAttributes() ) {\n\t\t\t\t\t\t// Strip invalid attributes.\n\t\t\t\t\t\tArray.from( node.attributes ).forEach( ( { name } ) => {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tname !== 'class' &&\n\t\t\t\t\t\t\t\t! attributes.includes( name )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tnode.removeAttribute( name );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t// Strip invalid classes.\n\t\t\t\t\t\t// In jsdom-jscore, 'node.classList' can be undefined.\n\t\t\t\t\t\t// TODO: Explore patching this in jsdom-jscore.\n\t\t\t\t\t\tif ( node.classList && node.classList.length ) {\n\t\t\t\t\t\t\tconst mattchers = classes.map( ( item ) => {\n\t\t\t\t\t\t\t\tif ( item === '*' ) {\n\t\t\t\t\t\t\t\t\t// Keep all classes.\n\t\t\t\t\t\t\t\t\treturn () => true;\n\t\t\t\t\t\t\t\t} else if ( typeof item === 'string' ) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t/** @type {string} */ className\n\t\t\t\t\t\t\t\t\t) => className === item;\n\t\t\t\t\t\t\t\t} else if ( item instanceof RegExp ) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t/** @type {string} */ className\n\t\t\t\t\t\t\t\t\t) => item.test( className );\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn noop;\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\tArray.from( node.classList ).forEach( ( name ) => {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t! mattchers.some( ( isMatch ) =>\n\t\t\t\t\t\t\t\t\t\tisMatch( name )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tnode.classList.remove( name );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\tif ( ! node.classList.length ) {\n\t\t\t\t\t\t\t\tnode.removeAttribute( 'class' );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( node.hasChildNodes() ) {\n\t\t\t\t\t\t// Do not filter any content.\n\t\t\t\t\t\tif ( children === '*' ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Continue if the node is supposed to have children.\n\t\t\t\t\t\tif ( children ) {\n\t\t\t\t\t\t\t// If a parent requires certain children, but it does\n\t\t\t\t\t\t\t// not have them, drop the parent and continue.\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\trequire.length &&\n\t\t\t\t\t\t\t\t! node.querySelector( require.join( ',' ) )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t\t\t// If the node is at the top, phrasing content, and\n\t\t\t\t\t\t\t\t// contains children that are block content, unwrap\n\t\t\t\t\t\t\t\t// the node because it is invalid.\n\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\tnode.parentNode &&\n\t\t\t\t\t\t\t\tnode.parentNode.nodeName === 'BODY' &&\n\t\t\t\t\t\t\t\tisPhrasingContent( node )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tArray.from( node.childNodes ).some(\n\t\t\t\t\t\t\t\t\t\t( child ) =>\n\t\t\t\t\t\t\t\t\t\t\t! isPhrasingContent( child )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Remove children if the node is not supposed to have any.\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twhile ( node.firstChild ) {\n\t\t\t\t\t\t\t\tremove( node.firstChild );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Invalid child. Continue with schema at the same place and unwrap.\n\t\t\t} else {\n\t\t\t\tcleanNodeList( node.childNodes, doc, schema, inline );\n\n\t\t\t\t// For inline mode, insert a line break when unwrapping nodes that\n\t\t\t\t// are not phrasing content.\n\t\t\t\tif (\n\t\t\t\t\tinline &&\n\t\t\t\t\t! isPhrasingContent( node ) &&\n\t\t\t\t\tnode.nextElementSibling\n\t\t\t\t) {\n\t\t\t\t\tinsertAfter( doc.createElement( 'br' ), node );\n\t\t\t\t}\n\n\t\t\t\tunwrap( node );\n\t\t\t}\n\t\t}\n\t);\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,OAAO,aAAa;AACpB,OAAO,YAAY;AACnB,OAAO,YAAY;AACnB,SAAS,yBAAyB;AAClC,OAAO,iBAAiB;AACxB,OAAO,eAAe;AAEtB,IAAM,OAAO,MAAM;AAAC;AAuBL,SAAR,cAAgC,UAAU,KAAK,QAAQ,QAAS;AACtE,QAAM,KAAM,QAAS,EAAE;AAAA,IACtB,CAAyD,SAAU;AAClE,YAAM,MAAM,KAAK,SAAS,YAAY;AAItC,UACC,OAAO,eAAgB,GAAI,MACzB,CAAE,OAAQ,GAAI,EAAE,WAAW,OAAQ,GAAI,EAAE,UAAW,IAAK,IAC1D;AACD,YAAK,UAAW,IAAK,GAAI;AACxB,gBAAM;AAAA,YACL,aAAa,CAAC;AAAA,YACd,UAAU,CAAC;AAAA,YACX;AAAA,YACA,SAAAA,WAAU,CAAC;AAAA,YACX;AAAA,UACD,IAAI,OAAQ,GAAI;
|
|
4
|
+
"sourcesContent": ["import isEmpty from './is-empty';\nimport remove from './remove';\nimport unwrap from './unwrap';\nimport { isPhrasingContent } from '../phrasing-content';\nimport insertAfter from './insert-after';\nimport isElement from './is-element';\n\nconst noop = () => {};\n\n/**\n * @typedef SchemaItem\n * @property {string[]} [attributes] Attributes.\n * @property {(string | RegExp)[]} [classes] Classnames or RegExp to test against. Use '*' to keep all classes.\n * @property {'*' | { [tag: string]: SchemaItem }} [children] Child schemas.\n * @property {string[]} [require] Selectors to test required children against. Leave empty or undefined if there are no requirements.\n * @property {boolean} allowEmpty Whether to allow nodes without children.\n * @property {(node: Node) => boolean} [isMatch] Function to test whether a node is a match. If left undefined any node will be assumed to match.\n */\n\n/** @typedef {{ [tag: string]: SchemaItem }} Schema */\n\n/**\n * Given a schema, unwraps or removes nodes, attributes and classes on a node\n * list.\n *\n * @param {NodeList} nodeList The nodeList to filter.\n * @param {Document} doc The document of the nodeList.\n * @param {Schema} schema An array of functions that can mutate with the provided node.\n * @param {boolean} inline Whether to clean for inline mode.\n */\nexport default function cleanNodeList( nodeList, doc, schema, inline ) {\n\tArray.from( nodeList ).forEach(\n\t\t( /** @type {Node & { nextElementSibling?: unknown }} */ node ) => {\n\t\t\tconst tag = node.nodeName.toLowerCase();\n\n\t\t\t// It's a valid child, if the tag exists in the schema without an isMatch\n\t\t\t// function, or with an isMatch function that matches the node.\n\t\t\tif (\n\t\t\t\tschema.hasOwnProperty( tag ) &&\n\t\t\t\t( ! schema[ tag ].isMatch || schema[ tag ].isMatch?.( node ) )\n\t\t\t) {\n\t\t\t\tif ( isElement( node ) ) {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tattributes = [],\n\t\t\t\t\t\tclasses = [],\n\t\t\t\t\t\tchildren,\n\t\t\t\t\t\trequire = [],\n\t\t\t\t\t\tallowEmpty,\n\t\t\t\t\t} = schema[ tag ];\n\n\t\t\t\t\t// If the node is empty and it's supposed to have children,\n\t\t\t\t\t// remove the node. For phrasing content nodes that contain\n\t\t\t\t\t// only whitespace, unwrap instead to preserve the text.\n\t\t\t\t\t// See https://github.com/WordPress/gutenberg/issues/50898\n\t\t\t\t\tif ( children && ! allowEmpty && isEmpty( node ) ) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tisPhrasingContent( node ) &&\n\t\t\t\t\t\t\tnode.hasChildNodes()\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tremove( node );\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( node.hasAttributes() ) {\n\t\t\t\t\t\t// Strip invalid attributes.\n\t\t\t\t\t\tArray.from( node.attributes ).forEach( ( { name } ) => {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tname !== 'class' &&\n\t\t\t\t\t\t\t\t! attributes.includes( name )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tnode.removeAttribute( name );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t// Strip invalid classes.\n\t\t\t\t\t\t// In jsdom-jscore, 'node.classList' can be undefined.\n\t\t\t\t\t\t// TODO: Explore patching this in jsdom-jscore.\n\t\t\t\t\t\tif ( node.classList && node.classList.length ) {\n\t\t\t\t\t\t\tconst mattchers = classes.map( ( item ) => {\n\t\t\t\t\t\t\t\tif ( item === '*' ) {\n\t\t\t\t\t\t\t\t\t// Keep all classes.\n\t\t\t\t\t\t\t\t\treturn () => true;\n\t\t\t\t\t\t\t\t} else if ( typeof item === 'string' ) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t/** @type {string} */ className\n\t\t\t\t\t\t\t\t\t) => className === item;\n\t\t\t\t\t\t\t\t} else if ( item instanceof RegExp ) {\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t/** @type {string} */ className\n\t\t\t\t\t\t\t\t\t) => item.test( className );\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn noop;\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\tArray.from( node.classList ).forEach( ( name ) => {\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t! mattchers.some( ( isMatch ) =>\n\t\t\t\t\t\t\t\t\t\tisMatch( name )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tnode.classList.remove( name );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\tif ( ! node.classList.length ) {\n\t\t\t\t\t\t\t\tnode.removeAttribute( 'class' );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif ( node.hasChildNodes() ) {\n\t\t\t\t\t\t// Do not filter any content.\n\t\t\t\t\t\tif ( children === '*' ) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Continue if the node is supposed to have children.\n\t\t\t\t\t\tif ( children ) {\n\t\t\t\t\t\t\t// If a parent requires certain children, but it does\n\t\t\t\t\t\t\t// not have them, drop the parent and continue.\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\trequire.length &&\n\t\t\t\t\t\t\t\t! node.querySelector( require.join( ',' ) )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t\t\t// If the node is at the top, phrasing content, and\n\t\t\t\t\t\t\t\t// contains children that are block content, unwrap\n\t\t\t\t\t\t\t\t// the node because it is invalid.\n\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\tnode.parentNode &&\n\t\t\t\t\t\t\t\tnode.parentNode.nodeName === 'BODY' &&\n\t\t\t\t\t\t\t\tisPhrasingContent( node )\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tschema,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tArray.from( node.childNodes ).some(\n\t\t\t\t\t\t\t\t\t\t( child ) =>\n\t\t\t\t\t\t\t\t\t\t\t! isPhrasingContent( child )\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tunwrap( node );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcleanNodeList(\n\t\t\t\t\t\t\t\t\tnode.childNodes,\n\t\t\t\t\t\t\t\t\tdoc,\n\t\t\t\t\t\t\t\t\tchildren,\n\t\t\t\t\t\t\t\t\tinline\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Remove children if the node is not supposed to have any.\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twhile ( node.firstChild ) {\n\t\t\t\t\t\t\t\tremove( node.firstChild );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Invalid child. Continue with schema at the same place and unwrap.\n\t\t\t} else {\n\t\t\t\tcleanNodeList( node.childNodes, doc, schema, inline );\n\n\t\t\t\t// For inline mode, insert a line break when unwrapping nodes that\n\t\t\t\t// are not phrasing content.\n\t\t\t\tif (\n\t\t\t\t\tinline &&\n\t\t\t\t\t! isPhrasingContent( node ) &&\n\t\t\t\t\tnode.nextElementSibling\n\t\t\t\t) {\n\t\t\t\t\tinsertAfter( doc.createElement( 'br' ), node );\n\t\t\t\t}\n\n\t\t\t\tunwrap( node );\n\t\t\t}\n\t\t}\n\t);\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,OAAO,aAAa;AACpB,OAAO,YAAY;AACnB,OAAO,YAAY;AACnB,SAAS,yBAAyB;AAClC,OAAO,iBAAiB;AACxB,OAAO,eAAe;AAEtB,IAAM,OAAO,MAAM;AAAC;AAuBL,SAAR,cAAgC,UAAU,KAAK,QAAQ,QAAS;AACtE,QAAM,KAAM,QAAS,EAAE;AAAA,IACtB,CAAyD,SAAU;AAClE,YAAM,MAAM,KAAK,SAAS,YAAY;AAItC,UACC,OAAO,eAAgB,GAAI,MACzB,CAAE,OAAQ,GAAI,EAAE,WAAW,OAAQ,GAAI,EAAE,UAAW,IAAK,IAC1D;AACD,YAAK,UAAW,IAAK,GAAI;AACxB,gBAAM;AAAA,YACL,aAAa,CAAC;AAAA,YACd,UAAU,CAAC;AAAA,YACX;AAAA,YACA,SAAAA,WAAU,CAAC;AAAA,YACX;AAAA,UACD,IAAI,OAAQ,GAAI;AAMhB,cAAK,YAAY,CAAE,cAAc,QAAS,IAAK,GAAI;AAClD,gBACC,kBAAmB,IAAK,KACxB,KAAK,cAAc,GAClB;AACD,qBAAQ,IAAK;AAAA,YACd,OAAO;AACN,qBAAQ,IAAK;AAAA,YACd;AACA;AAAA,UACD;AAEA,cAAK,KAAK,cAAc,GAAI;AAE3B,kBAAM,KAAM,KAAK,UAAW,EAAE,QAAS,CAAE,EAAE,KAAK,MAAO;AACtD,kBACC,SAAS,WACT,CAAE,WAAW,SAAU,IAAK,GAC3B;AACD,qBAAK,gBAAiB,IAAK;AAAA,cAC5B;AAAA,YACD,CAAE;AAKF,gBAAK,KAAK,aAAa,KAAK,UAAU,QAAS;AAC9C,oBAAM,YAAY,QAAQ,IAAK,CAAE,SAAU;AAC1C,oBAAK,SAAS,KAAM;AAEnB,yBAAO,MAAM;AAAA,gBACd,WAAY,OAAO,SAAS,UAAW;AACtC,yBAAO,CACgB,cAClB,cAAc;AAAA,gBACpB,WAAY,gBAAgB,QAAS;AACpC,yBAAO,CACgB,cAClB,KAAK,KAAM,SAAU;AAAA,gBAC3B;AAEA,uBAAO;AAAA,cACR,CAAE;AAEF,oBAAM,KAAM,KAAK,SAAU,EAAE,QAAS,CAAE,SAAU;AACjD,oBACC,CAAE,UAAU;AAAA,kBAAM,CAAE,YACnB,QAAS,IAAK;AAAA,gBACf,GACC;AACD,uBAAK,UAAU,OAAQ,IAAK;AAAA,gBAC7B;AAAA,cACD,CAAE;AAEF,kBAAK,CAAE,KAAK,UAAU,QAAS;AAC9B,qBAAK,gBAAiB,OAAQ;AAAA,cAC/B;AAAA,YACD;AAAA,UACD;AAEA,cAAK,KAAK,cAAc,GAAI;AAE3B,gBAAK,aAAa,KAAM;AACvB;AAAA,YACD;AAGA,gBAAK,UAAW;AAGf,kBACCA,SAAQ,UACR,CAAE,KAAK,cAAeA,SAAQ,KAAM,GAAI,CAAE,GACzC;AACD;AAAA,kBACC,KAAK;AAAA,kBACL;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AACA,uBAAQ,IAAK;AAAA,cAId,WACC,KAAK,cACL,KAAK,WAAW,aAAa,UAC7B,kBAAmB,IAAK,GACvB;AACD;AAAA,kBACC,KAAK;AAAA,kBACL;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAEA,oBACC,MAAM,KAAM,KAAK,UAAW,EAAE;AAAA,kBAC7B,CAAE,UACD,CAAE,kBAAmB,KAAM;AAAA,gBAC7B,GACC;AACD,yBAAQ,IAAK;AAAA,gBACd;AAAA,cACD,OAAO;AACN;AAAA,kBACC,KAAK;AAAA,kBACL;AAAA,kBACA;AAAA,kBACA;AAAA,gBACD;AAAA,cACD;AAAA,YAED,OAAO;AACN,qBAAQ,KAAK,YAAa;AACzB,uBAAQ,KAAK,UAAW;AAAA,cACzB;AAAA,YACD;AAAA,UACD;AAAA,QACD;AAAA,MAED,OAAO;AACN,sBAAe,KAAK,YAAY,KAAK,QAAQ,MAAO;AAIpD,YACC,UACA,CAAE,kBAAmB,IAAK,KAC1B,KAAK,oBACJ;AACD,sBAAa,IAAI,cAAe,IAAK,GAAG,IAAK;AAAA,QAC9C;AAEA,eAAQ,IAAK;AAAA,MACd;AAAA,IACD;AAAA,EACD;AACD;",
|
|
6
6
|
"names": ["require"]
|
|
7
7
|
}
|
|
@@ -58,6 +58,21 @@ function getRectangleFromRange(range) {
|
|
|
58
58
|
if (rects.length > 1) {
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
|
+
if (rects.length === 1 && startContainer.nodeType === startContainer.TEXT_NODE && range.startOffset > 0 && range.startOffset < /** @type {Text} */
|
|
62
|
+
startContainer.length) {
|
|
63
|
+
assertIsDefined(ownerDocument, "ownerDocument");
|
|
64
|
+
const measure = (start, end) => {
|
|
65
|
+
const charRange = ownerDocument.createRange();
|
|
66
|
+
charRange.setStart(startContainer, start);
|
|
67
|
+
charRange.setEnd(startContainer, end);
|
|
68
|
+
return charRange.getBoundingClientRect();
|
|
69
|
+
};
|
|
70
|
+
const before = measure(range.startOffset - 1, range.startOffset);
|
|
71
|
+
const after = measure(range.startOffset, range.startOffset + 1);
|
|
72
|
+
if (before.bottom <= after.top) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
61
76
|
let rect = rects[0];
|
|
62
77
|
if (!rect || rect.height === 0) {
|
|
63
78
|
assertIsDefined(ownerDocument, "ownerDocument");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/dom/get-rectangle-from-range.js"],
|
|
4
|
-
"sourcesContent": ["import { assertIsDefined } from '../utils/assert-is-defined';\n\n/**\n * Get the rectangle of a given Range. Returns `null` if no suitable rectangle\n * can be found. Use instead of `Range.getBoundingClientRect()`, which is often\n * broken, especially for collapsed ranges.\n *\n * @param {Range} range The range.\n *\n * @return {DOMRect?} The rectangle.\n */\nexport default function getRectangleFromRange( range ) {\n\t// For uncollapsed ranges, get the rectangle that bounds the contents of the\n\t// range; this a rectangle enclosing the union of the bounding rectangles\n\t// for all the elements in the range.\n\tif ( ! range.collapsed ) {\n\t\tconst rects = Array.from( range.getClientRects() );\n\n\t\t// If there's just a single rect, return it.\n\t\tif ( rects.length === 1 ) {\n\t\t\treturn rects[ 0 ];\n\t\t}\n\n\t\t// Ignore tiny selection at the edge of a range.\n\t\tconst filteredRects = rects.filter( ( { width } ) => width > 1 );\n\n\t\t// If it's full of tiny selections, return browser default.\n\t\tif ( filteredRects.length === 0 ) {\n\t\t\treturn range.getBoundingClientRect();\n\t\t}\n\n\t\tif ( filteredRects.length === 1 ) {\n\t\t\treturn filteredRects[ 0 ];\n\t\t}\n\n\t\tlet {\n\t\t\ttop: furthestTop,\n\t\t\tbottom: furthestBottom,\n\t\t\tleft: furthestLeft,\n\t\t\tright: furthestRight,\n\t\t} = filteredRects[ 0 ];\n\n\t\tfor ( const { top, bottom, left, right } of filteredRects ) {\n\t\t\tif ( top < furthestTop ) {\n\t\t\t\tfurthestTop = top;\n\t\t\t}\n\t\t\tif ( bottom > furthestBottom ) {\n\t\t\t\tfurthestBottom = bottom;\n\t\t\t}\n\t\t\tif ( left < furthestLeft ) {\n\t\t\t\tfurthestLeft = left;\n\t\t\t}\n\t\t\tif ( right > furthestRight ) {\n\t\t\t\tfurthestRight = right;\n\t\t\t}\n\t\t}\n\n\t\treturn new window.DOMRect(\n\t\t\tfurthestLeft,\n\t\t\tfurthestTop,\n\t\t\tfurthestRight - furthestLeft,\n\t\t\tfurthestBottom - furthestTop\n\t\t);\n\t}\n\n\tconst { startContainer } = range;\n\tconst { ownerDocument } = startContainer;\n\n\t// Correct invalid \"BR\" ranges. The cannot contain any children.\n\tif ( startContainer.nodeName === 'BR' ) {\n\t\tconst { parentNode } = startContainer;\n\t\tassertIsDefined( parentNode, 'parentNode' );\n\t\tconst index = /** @type {Node[]} */ (\n\t\t\tArray.from( parentNode.childNodes )\n\t\t).indexOf( startContainer );\n\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\trange = ownerDocument.createRange();\n\t\trange.setStart( parentNode, index );\n\t\trange.setEnd( parentNode, index );\n\t}\n\n\tconst rects = range.getClientRects();\n\n\t// If we have multiple rectangles for a collapsed range, there's no way to\n\t// know which it is, so don't return anything.\n\tif ( rects.length > 1 ) {\n\t\treturn null;\n\t}\n\n\tlet rect = rects[ 0 ];\n\n\t// If the collapsed range starts (and therefore ends) at an element node,\n\t// `getClientRects` can be empty in some browsers. This can be resolved\n\t// by adding a temporary text node with zero-width space to the range.\n\t//\n\t// See: https://stackoverflow.com/a/6847328/995445\n\tif ( ! rect || rect.height === 0 ) {\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\tconst padNode = ownerDocument.createTextNode( '\\u200b' );\n\t\t// Do not modify the live range.\n\t\trange = range.cloneRange();\n\t\trange.insertNode( padNode );\n\t\trect = range.getClientRects()[ 0 ];\n\t\tassertIsDefined( padNode.parentNode, 'padNode.parentNode' );\n\t\tpadNode.parentNode.removeChild( padNode );\n\t}\n\n\treturn rect;\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,SAAS,uBAAuB;AAWjB,SAAR,sBAAwC,OAAQ;AAItD,MAAK,CAAE,MAAM,WAAY;AACxB,UAAMA,SAAQ,MAAM,KAAM,MAAM,eAAe,CAAE;AAGjD,QAAKA,OAAM,WAAW,GAAI;AACzB,aAAOA,OAAO,CAAE;AAAA,IACjB;AAGA,UAAM,gBAAgBA,OAAM,OAAQ,CAAE,EAAE,MAAM,MAAO,QAAQ,CAAE;AAG/D,QAAK,cAAc,WAAW,GAAI;AACjC,aAAO,MAAM,sBAAsB;AAAA,IACpC;AAEA,QAAK,cAAc,WAAW,GAAI;AACjC,aAAO,cAAe,CAAE;AAAA,IACzB;AAEA,QAAI;AAAA,MACH,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,IACR,IAAI,cAAe,CAAE;AAErB,eAAY,EAAE,KAAK,QAAQ,MAAM,MAAM,KAAK,eAAgB;AAC3D,UAAK,MAAM,aAAc;AACxB,sBAAc;AAAA,MACf;AACA,UAAK,SAAS,gBAAiB;AAC9B,yBAAiB;AAAA,MAClB;AACA,UAAK,OAAO,cAAe;AAC1B,uBAAe;AAAA,MAChB;AACA,UAAK,QAAQ,eAAgB;AAC5B,wBAAgB;AAAA,MACjB;AAAA,IACD;AAEA,WAAO,IAAI,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IAClB;AAAA,EACD;AAEA,QAAM,EAAE,eAAe,IAAI;AAC3B,QAAM,EAAE,cAAc,IAAI;AAG1B,MAAK,eAAe,aAAa,MAAO;AACvC,UAAM,EAAE,WAAW,IAAI;AACvB,oBAAiB,YAAY,YAAa;AAC1C,UAAM;AAAA;AAAA,MACL,MAAM,KAAM,WAAW,UAAW,EACjC,QAAS,cAAe;AAAA;AAE1B,oBAAiB,eAAe,eAAgB;AAChD,YAAQ,cAAc,YAAY;AAClC,UAAM,SAAU,YAAY,KAAM;AAClC,UAAM,OAAQ,YAAY,KAAM;AAAA,EACjC;AAEA,QAAM,QAAQ,MAAM,eAAe;AAInC,MAAK,MAAM,SAAS,GAAI;AACvB,WAAO;AAAA,EACR;AAEA,MAAI,OAAO,MAAO,CAAE;AAOpB,MAAK,CAAE,QAAQ,KAAK,WAAW,GAAI;AAClC,oBAAiB,eAAe,eAAgB;AAChD,UAAM,UAAU,cAAc,eAAgB,GAAS;AAEvD,YAAQ,MAAM,WAAW;AACzB,UAAM,WAAY,OAAQ;AAC1B,WAAO,MAAM,eAAe,EAAG,CAAE;AACjC,oBAAiB,QAAQ,YAAY,oBAAqB;AAC1D,YAAQ,WAAW,YAAa,OAAQ;AAAA,EACzC;AAEA,SAAO;AACR;",
|
|
4
|
+
"sourcesContent": ["import { assertIsDefined } from '../utils/assert-is-defined';\n\n/**\n * Get the rectangle of a given Range. Returns `null` if no suitable rectangle\n * can be found. Use instead of `Range.getBoundingClientRect()`, which is often\n * broken, especially for collapsed ranges.\n *\n * @param {Range} range The range.\n *\n * @return {DOMRect?} The rectangle.\n */\nexport default function getRectangleFromRange( range ) {\n\t// For uncollapsed ranges, get the rectangle that bounds the contents of the\n\t// range; this a rectangle enclosing the union of the bounding rectangles\n\t// for all the elements in the range.\n\tif ( ! range.collapsed ) {\n\t\tconst rects = Array.from( range.getClientRects() );\n\n\t\t// If there's just a single rect, return it.\n\t\tif ( rects.length === 1 ) {\n\t\t\treturn rects[ 0 ];\n\t\t}\n\n\t\t// Ignore tiny selection at the edge of a range.\n\t\tconst filteredRects = rects.filter( ( { width } ) => width > 1 );\n\n\t\t// If it's full of tiny selections, return browser default.\n\t\tif ( filteredRects.length === 0 ) {\n\t\t\treturn range.getBoundingClientRect();\n\t\t}\n\n\t\tif ( filteredRects.length === 1 ) {\n\t\t\treturn filteredRects[ 0 ];\n\t\t}\n\n\t\tlet {\n\t\t\ttop: furthestTop,\n\t\t\tbottom: furthestBottom,\n\t\t\tleft: furthestLeft,\n\t\t\tright: furthestRight,\n\t\t} = filteredRects[ 0 ];\n\n\t\tfor ( const { top, bottom, left, right } of filteredRects ) {\n\t\t\tif ( top < furthestTop ) {\n\t\t\t\tfurthestTop = top;\n\t\t\t}\n\t\t\tif ( bottom > furthestBottom ) {\n\t\t\t\tfurthestBottom = bottom;\n\t\t\t}\n\t\t\tif ( left < furthestLeft ) {\n\t\t\t\tfurthestLeft = left;\n\t\t\t}\n\t\t\tif ( right > furthestRight ) {\n\t\t\t\tfurthestRight = right;\n\t\t\t}\n\t\t}\n\n\t\treturn new window.DOMRect(\n\t\t\tfurthestLeft,\n\t\t\tfurthestTop,\n\t\t\tfurthestRight - furthestLeft,\n\t\t\tfurthestBottom - furthestTop\n\t\t);\n\t}\n\n\tconst { startContainer } = range;\n\tconst { ownerDocument } = startContainer;\n\n\t// Correct invalid \"BR\" ranges. The cannot contain any children.\n\tif ( startContainer.nodeName === 'BR' ) {\n\t\tconst { parentNode } = startContainer;\n\t\tassertIsDefined( parentNode, 'parentNode' );\n\t\tconst index = /** @type {Node[]} */ (\n\t\t\tArray.from( parentNode.childNodes )\n\t\t).indexOf( startContainer );\n\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\trange = ownerDocument.createRange();\n\t\trange.setStart( parentNode, index );\n\t\trange.setEnd( parentNode, index );\n\t}\n\n\tconst rects = range.getClientRects();\n\n\t// If we have multiple rectangles for a collapsed range, there's no way to\n\t// know which it is, so don't return anything.\n\tif ( rects.length > 1 ) {\n\t\treturn null;\n\t}\n\n\t// A collapsed range at a soft line wrap is equally ambiguous: the same\n\t// position ends one line and starts the next. Some browsers (Gecko)\n\t// return a single rectangle for it, on the upper line, regardless of\n\t// where the caret is. Detect the boundary by measuring the characters\n\t// around the position: when they sit on different lines, there is no\n\t// way to know which line the caret is on, so don't return anything.\n\tif (\n\t\trects.length === 1 &&\n\t\tstartContainer.nodeType === startContainer.TEXT_NODE &&\n\t\trange.startOffset > 0 &&\n\t\trange.startOffset < /** @type {Text} */ ( startContainer ).length\n\t) {\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\tconst measure = (\n\t\t\t/** @type {number} */ start,\n\t\t\t/** @type {number} */ end\n\t\t) => {\n\t\t\tconst charRange = ownerDocument.createRange();\n\t\t\tcharRange.setStart( startContainer, start );\n\t\t\tcharRange.setEnd( startContainer, end );\n\t\t\treturn charRange.getBoundingClientRect();\n\t\t};\n\t\tconst before = measure( range.startOffset - 1, range.startOffset );\n\t\tconst after = measure( range.startOffset, range.startOffset + 1 );\n\n\t\tif ( before.bottom <= after.top ) {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tlet rect = rects[ 0 ];\n\n\t// If the collapsed range starts (and therefore ends) at an element node,\n\t// `getClientRects` can be empty in some browsers. This can be resolved\n\t// by adding a temporary text node with zero-width space to the range.\n\t//\n\t// See: https://stackoverflow.com/a/6847328/995445\n\tif ( ! rect || rect.height === 0 ) {\n\t\tassertIsDefined( ownerDocument, 'ownerDocument' );\n\t\tconst padNode = ownerDocument.createTextNode( '\\u200b' );\n\t\t// Do not modify the live range.\n\t\trange = range.cloneRange();\n\t\trange.insertNode( padNode );\n\t\trect = range.getClientRects()[ 0 ];\n\t\tassertIsDefined( padNode.parentNode, 'padNode.parentNode' );\n\t\tpadNode.parentNode.removeChild( padNode );\n\t}\n\n\treturn rect;\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,SAAS,uBAAuB;AAWjB,SAAR,sBAAwC,OAAQ;AAItD,MAAK,CAAE,MAAM,WAAY;AACxB,UAAMA,SAAQ,MAAM,KAAM,MAAM,eAAe,CAAE;AAGjD,QAAKA,OAAM,WAAW,GAAI;AACzB,aAAOA,OAAO,CAAE;AAAA,IACjB;AAGA,UAAM,gBAAgBA,OAAM,OAAQ,CAAE,EAAE,MAAM,MAAO,QAAQ,CAAE;AAG/D,QAAK,cAAc,WAAW,GAAI;AACjC,aAAO,MAAM,sBAAsB;AAAA,IACpC;AAEA,QAAK,cAAc,WAAW,GAAI;AACjC,aAAO,cAAe,CAAE;AAAA,IACzB;AAEA,QAAI;AAAA,MACH,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,IACR,IAAI,cAAe,CAAE;AAErB,eAAY,EAAE,KAAK,QAAQ,MAAM,MAAM,KAAK,eAAgB;AAC3D,UAAK,MAAM,aAAc;AACxB,sBAAc;AAAA,MACf;AACA,UAAK,SAAS,gBAAiB;AAC9B,yBAAiB;AAAA,MAClB;AACA,UAAK,OAAO,cAAe;AAC1B,uBAAe;AAAA,MAChB;AACA,UAAK,QAAQ,eAAgB;AAC5B,wBAAgB;AAAA,MACjB;AAAA,IACD;AAEA,WAAO,IAAI,OAAO;AAAA,MACjB;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,IAClB;AAAA,EACD;AAEA,QAAM,EAAE,eAAe,IAAI;AAC3B,QAAM,EAAE,cAAc,IAAI;AAG1B,MAAK,eAAe,aAAa,MAAO;AACvC,UAAM,EAAE,WAAW,IAAI;AACvB,oBAAiB,YAAY,YAAa;AAC1C,UAAM;AAAA;AAAA,MACL,MAAM,KAAM,WAAW,UAAW,EACjC,QAAS,cAAe;AAAA;AAE1B,oBAAiB,eAAe,eAAgB;AAChD,YAAQ,cAAc,YAAY;AAClC,UAAM,SAAU,YAAY,KAAM;AAClC,UAAM,OAAQ,YAAY,KAAM;AAAA,EACjC;AAEA,QAAM,QAAQ,MAAM,eAAe;AAInC,MAAK,MAAM,SAAS,GAAI;AACvB,WAAO;AAAA,EACR;AAQA,MACC,MAAM,WAAW,KACjB,eAAe,aAAa,eAAe,aAC3C,MAAM,cAAc,KACpB,MAAM;AAAA,EAAoC,eAAiB,QAC1D;AACD,oBAAiB,eAAe,eAAgB;AAChD,UAAM,UAAU,CACO,OACA,QAClB;AACJ,YAAM,YAAY,cAAc,YAAY;AAC5C,gBAAU,SAAU,gBAAgB,KAAM;AAC1C,gBAAU,OAAQ,gBAAgB,GAAI;AACtC,aAAO,UAAU,sBAAsB;AAAA,IACxC;AACA,UAAM,SAAS,QAAS,MAAM,cAAc,GAAG,MAAM,WAAY;AACjE,UAAM,QAAQ,QAAS,MAAM,aAAa,MAAM,cAAc,CAAE;AAEhE,QAAK,OAAO,UAAU,MAAM,KAAM;AACjC,aAAO;AAAA,IACR;AAAA,EACD;AAEA,MAAI,OAAO,MAAO,CAAE;AAOpB,MAAK,CAAE,QAAQ,KAAK,WAAW,GAAI;AAClC,oBAAiB,eAAe,eAAgB;AAChD,UAAM,UAAU,cAAc,eAAgB,GAAS;AAEvD,YAAQ,MAAM,WAAW;AACzB,UAAM,WAAY,OAAQ;AAC1B,WAAO,MAAM,eAAe,EAAG,CAAE;AACjC,oBAAiB,QAAQ,YAAY,oBAAqB;AAC1D,YAAQ,WAAW,YAAa,OAAQ;AAAA,EACzC;AAEA,SAAO;AACR;",
|
|
6
6
|
"names": ["rects"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clean-node-list.d.ts","sourceRoot":"","sources":["../../src/dom/clean-node-list.js"],"names":[],"mappings":"AAUG,YAAS,UAAU,GACnB;;;;IAAiD,UAAU,AAA3D,CACA,EADW,MAAM,EAAE,CACnB;;;;IAAiD,OAAO,AAAxD,CACA,EADW,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAC9B;;;;IAAiD,QAAQ,AAAzD,CACA,EADW,GAAG,GAAG;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAC9C;;;;IAAiD,OAAO,AAAxD,CACA,EADW,MAAM,EAAE,CACnB;;;;IAAgD,UAAU,EAA/C,OAAO,CAClB;;;;IAAiD,OAAO,AAAxD,CACF,EADa,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CACpC;CAAA,CAAA;AAEG,YAAyC,MAAM,GAArC;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE,CAAQ;AAVnD;;;;;;;;GAQG;AAEH,sDAAsD;AAEtD;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CAAE,QAAQ,EALpC,QAKoC,EAAE,GAAG,EAJzC,QAIyC,EAAE,MAAM,EAHjD,MAGiD,EAAE,MAAM,EAFzD,OAEyD,
|
|
1
|
+
{"version":3,"file":"clean-node-list.d.ts","sourceRoot":"","sources":["../../src/dom/clean-node-list.js"],"names":[],"mappings":"AAUG,YAAS,UAAU,GACnB;;;;IAAiD,UAAU,AAA3D,CACA,EADW,MAAM,EAAE,CACnB;;;;IAAiD,OAAO,AAAxD,CACA,EADW,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAC9B;;;;IAAiD,QAAQ,AAAzD,CACA,EADW,GAAG,GAAG;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAC9C;;;;IAAiD,OAAO,AAAxD,CACA,EADW,MAAM,EAAE,CACnB;;;;IAAgD,UAAU,EAA/C,OAAO,CAClB;;;;IAAiD,OAAO,AAAxD,CACF,EADa,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CACpC;CAAA,CAAA;AAEG,YAAyC,MAAM,GAArC;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE,CAAQ;AAVnD;;;;;;;;GAQG;AAEH,sDAAsD;AAEtD;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CAAE,QAAQ,EALpC,QAKoC,EAAE,GAAG,EAJzC,QAIyC,EAAE,MAAM,EAHjD,MAGiD,EAAE,MAAM,EAFzD,OAEyD,QAkKnE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-rectangle-from-range.d.ts","sourceRoot":"","sources":["../../src/dom/get-rectangle-from-range.js"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAAE,KAAK,EAJzC,KAIyC,GAFxC,OAAO,OAAC,
|
|
1
|
+
{"version":3,"file":"get-rectangle-from-range.d.ts","sourceRoot":"","sources":["../../src/dom/get-rectangle-from-range.js"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,qBAAqB,CAAE,KAAK,EAJzC,KAIyC,GAFxC,OAAO,OAAC,CAkInB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/dom",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.54.1-next.v.202608281122.0",
|
|
4
4
|
"description": "DOM utilities module for WordPress.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"types": "build-types",
|
|
45
45
|
"sideEffects": false,
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@wordpress/deprecated": "^4.
|
|
47
|
+
"@wordpress/deprecated": "^4.54.1-next.v.202608281122.0"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "de479d415171765f7b573c81aeec5334ff506a12"
|
|
53
53
|
}
|
|
@@ -49,9 +49,18 @@ export default function cleanNodeList( nodeList, doc, schema, inline ) {
|
|
|
49
49
|
} = schema[ tag ];
|
|
50
50
|
|
|
51
51
|
// If the node is empty and it's supposed to have children,
|
|
52
|
-
// remove the node.
|
|
52
|
+
// remove the node. For phrasing content nodes that contain
|
|
53
|
+
// only whitespace, unwrap instead to preserve the text.
|
|
54
|
+
// See https://github.com/WordPress/gutenberg/issues/50898
|
|
53
55
|
if ( children && ! allowEmpty && isEmpty( node ) ) {
|
|
54
|
-
|
|
56
|
+
if (
|
|
57
|
+
isPhrasingContent( node ) &&
|
|
58
|
+
node.hasChildNodes()
|
|
59
|
+
) {
|
|
60
|
+
unwrap( node );
|
|
61
|
+
} else {
|
|
62
|
+
remove( node );
|
|
63
|
+
}
|
|
55
64
|
return;
|
|
56
65
|
}
|
|
57
66
|
|
|
@@ -88,6 +88,36 @@ export default function getRectangleFromRange( range ) {
|
|
|
88
88
|
return null;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// A collapsed range at a soft line wrap is equally ambiguous: the same
|
|
92
|
+
// position ends one line and starts the next. Some browsers (Gecko)
|
|
93
|
+
// return a single rectangle for it, on the upper line, regardless of
|
|
94
|
+
// where the caret is. Detect the boundary by measuring the characters
|
|
95
|
+
// around the position: when they sit on different lines, there is no
|
|
96
|
+
// way to know which line the caret is on, so don't return anything.
|
|
97
|
+
if (
|
|
98
|
+
rects.length === 1 &&
|
|
99
|
+
startContainer.nodeType === startContainer.TEXT_NODE &&
|
|
100
|
+
range.startOffset > 0 &&
|
|
101
|
+
range.startOffset < /** @type {Text} */ ( startContainer ).length
|
|
102
|
+
) {
|
|
103
|
+
assertIsDefined( ownerDocument, 'ownerDocument' );
|
|
104
|
+
const measure = (
|
|
105
|
+
/** @type {number} */ start,
|
|
106
|
+
/** @type {number} */ end
|
|
107
|
+
) => {
|
|
108
|
+
const charRange = ownerDocument.createRange();
|
|
109
|
+
charRange.setStart( startContainer, start );
|
|
110
|
+
charRange.setEnd( startContainer, end );
|
|
111
|
+
return charRange.getBoundingClientRect();
|
|
112
|
+
};
|
|
113
|
+
const before = measure( range.startOffset - 1, range.startOffset );
|
|
114
|
+
const after = measure( range.startOffset, range.startOffset + 1 );
|
|
115
|
+
|
|
116
|
+
if ( before.bottom <= after.top ) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
91
121
|
let rect = rects[ 0 ];
|
|
92
122
|
|
|
93
123
|
// If the collapsed range starts (and therefore ends) at an element node,
|
package/src/test/dom.js
CHANGED
|
@@ -282,6 +282,24 @@ describe( 'removeInvalidHTML', () => {
|
|
|
282
282
|
const output = '<p>test</p>';
|
|
283
283
|
expect( removeInvalidHTML( input, schema ) ).toEqual( output );
|
|
284
284
|
} );
|
|
285
|
+
|
|
286
|
+
it( 'should preserve whitespace from inline elements containing only whitespace', () => {
|
|
287
|
+
const input = 'The following space:<em> </em>should not be removed.';
|
|
288
|
+
const output = 'The following space: should not be removed.';
|
|
289
|
+
expect( removeInvalidHTML( input, schema ) ).toBe( output );
|
|
290
|
+
} );
|
|
291
|
+
|
|
292
|
+
it( 'should still remove truly empty inline elements', () => {
|
|
293
|
+
const input = 'no space:<em></em>here';
|
|
294
|
+
const output = 'no space:here';
|
|
295
|
+
expect( removeInvalidHTML( input, schema ) ).toBe( output );
|
|
296
|
+
} );
|
|
297
|
+
|
|
298
|
+
it( 'should still remove empty block-level elements that should have children', () => {
|
|
299
|
+
const input = '<figure> </figure>';
|
|
300
|
+
const output = '';
|
|
301
|
+
expect( removeInvalidHTML( input, schema ) ).toBe( output );
|
|
302
|
+
} );
|
|
285
303
|
} );
|
|
286
304
|
|
|
287
305
|
describe( 'isEmpty', () => {
|