Skip to content

Commit

Permalink
dom: Add types to clean-node-list (#30412)
Browse files Browse the repository at this point in the history
* dom: Type dependencies of clean-node-list

* dom: Add types to clean-node-list

* Fix property descriptions
  • Loading branch information
sarayourfriend authored Apr 6, 2021
1 parent ea6035f commit 8be7a8d
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 15 deletions.
6 changes: 3 additions & 3 deletions packages/dom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ the latter.
_Parameters_
- _newNode_ `Element`: Node to be inserted.
- _referenceNode_ `Element`: Node after which to perform the insertion.
- _newNode_ `Node`: Node to be inserted.
- _referenceNode_ `Node`: Node after which to perform the insertion.
_Returns_
Expand Down Expand Up @@ -291,7 +291,7 @@ Given a DOM node, removes it from the DOM.
_Parameters_
- _node_ `Element`: Node to be removed.
- _node_ `Node`: Node to be removed.
_Returns_
Expand Down
34 changes: 27 additions & 7 deletions packages/dom/src/dom/clean-node-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,44 @@ import remove from './remove';
import unwrap from './unwrap';
import { isPhrasingContent } from '../phrasing-content';
import insertAfter from './insert-after';
import isElement from './is-element';

/* eslint-disable jsdoc/valid-types */
/**
* @typedef SchemaItem
* @property {string[]} [attributes] Attributes.
* @property {(string | RegExp)[]} [classes] Classnames or RegExp to test against.
* @property {'*' | { [tag: string]: SchemaItem }} [children] Child schemas.
* @property {string[]} [require] Selectors to test required children against. Leave empty or undefined if there are no requirements.
* @property {boolean} allowEmpty Whether to allow nodes without children.
* @property {(node: Node) => boolean} [isMatch] Function to test whether a node is a match. If left undefined any node will be assumed to match.
*/

/** @typedef {{ [tag: string]: SchemaItem }} Schema */
/* eslint-enable jsdoc/valid-types */

/**
* Given a schema, unwraps or removes nodes, attributes and classes on a node
* list.
*
* @param {NodeList} nodeList The nodeList to filter.
* @param {Document} doc The document of the nodeList.
* @param {Object} schema An array of functions that can mutate with the provided node.
* @param {Object} inline Whether to clean for inline mode.
* @param {Schema} schema An array of functions that can mutate with the provided node.
* @param {boolean} inline Whether to clean for inline mode.
*/
export default function cleanNodeList( nodeList, doc, schema, inline ) {
Array.from( nodeList ).forEach( ( node ) => {
Array.from( nodeList ).forEach( (
/** @type {Node & { nextElementSibling?: unknown }} */ node
) => {
const tag = node.nodeName.toLowerCase();

// It's a valid child, if the tag exists in the schema without an isMatch
// function, or with an isMatch function that matches the node.
if (
schema.hasOwnProperty( tag ) &&
( ! schema[ tag ].isMatch || schema[ tag ].isMatch( node ) )
( ! schema[ tag ].isMatch || schema[ tag ].isMatch?.( node ) )
) {
if ( node.nodeType === node.ELEMENT_NODE ) {
if ( isElement( node ) ) {
const {
attributes = [],
classes = [],
Expand Down Expand Up @@ -64,9 +81,11 @@ export default function cleanNodeList( nodeList, doc, schema, inline ) {
if ( node.classList && node.classList.length ) {
const mattchers = classes.map( ( item ) => {
if ( typeof item === 'string' ) {
return ( className ) => className === item;
return ( /** @type {string} */ className ) =>
className === item;
} else if ( item instanceof RegExp ) {
return ( className ) => item.test( className );
return ( /** @type {string} */ className ) =>
item.test( className );
}

return noop;
Expand Down Expand Up @@ -113,6 +132,7 @@ export default function cleanNodeList( nodeList, doc, schema, inline ) {
// contains children that are block content, unwrap
// the node because it is invalid.
} else if (
node.parentNode &&
node.parentNode.nodeName === 'BODY' &&
isPhrasingContent( node )
) {
Expand Down
10 changes: 8 additions & 2 deletions packages/dom/src/dom/insert-after.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
/**
* Internal dependencies
*/
import { assertIsDefined } from '../utils/assert-is-defined';

/**
* Given two DOM nodes, inserts the former in the DOM as the next sibling of
* the latter.
*
* @param {Element} newNode Node to be inserted.
* @param {Element} referenceNode Node after which to perform the insertion.
* @param {Node} newNode Node to be inserted.
* @param {Node} referenceNode Node after which to perform the insertion.
* @return {void}
*/
export default function insertAfter( newNode, referenceNode ) {
assertIsDefined( referenceNode.parentNode, 'referenceNode.parentNode' );
referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling );
}
9 changes: 9 additions & 0 deletions packages/dom/src/dom/is-element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable jsdoc/valid-types */
/**
* @param {Node | null | undefined} node
* @return {node is Element} True if node is an Element node
*/
export default function isElement( node ) {
/* eslint-enable jsdoc/valid-types */
return !! node && node.nodeType === node.ELEMENT_NODE;
}
6 changes: 4 additions & 2 deletions packages/dom/src/dom/is-empty.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ export default function isEmpty( element ) {
case element.TEXT_NODE:
// We cannot use \s since it includes special spaces which we want
// to preserve.
return /^[ \f\n\r\t\v\u00a0]*$/.test( element.nodeValue );
return /^[ \f\n\r\t\v\u00a0]*$/.test( element.nodeValue || '' );
case element.ELEMENT_NODE:
if ( element.hasAttributes() ) {
return false;
} else if ( ! element.hasChildNodes() ) {
return true;
}

return Array.from( element.childNodes ).every( isEmpty );
return /** @type {Element[]} */ ( Array.from(
element.childNodes
) ).every( isEmpty );
default:
return true;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/dom/src/dom/remove.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
/**
* Internal dependencies
*/
import { assertIsDefined } from '../utils/assert-is-defined';

/**
* Given a DOM node, removes it from the DOM.
*
* @param {Element} node Node to be removed.
* @param {Node} node Node to be removed.
* @return {void}
*/
export default function remove( node ) {
assertIsDefined( node.parentNode, 'node.parentNode' );
node.parentNode.removeChild( node );
}
7 changes: 7 additions & 0 deletions packages/dom/src/dom/unwrap.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* Internal dependencies
*/
import { assertIsDefined } from '../utils/assert-is-defined';

/**
* Unwrap the given node. This means any child nodes are moved to the parent.
*
Expand All @@ -8,6 +13,8 @@
export default function unwrap( node ) {
const parent = node.parentNode;

assertIsDefined( parent, 'node.parentNode' );

while ( node.firstChild ) {
parent.insertBefore( node.firstChild, node );
}
Expand Down
6 changes: 6 additions & 0 deletions packages/dom/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@
"include": [
"src/data-transfer.js",
"src/dom/caret-range-from-point.js",
"src/dom/clean-node-list.js",
"src/dom/compute-caret-rect.js",
"src/dom/get-rectangle-from-range.js",
"src/dom/is-empty.js",
"src/dom/is-element.js",
"src/dom/insert-after.js",
"src/dom/remove.js",
"src/dom/unwrap.js",
"src/utils/**/*",
"src/focusable.js",
"src/phrasing-content.js",
Expand Down

0 comments on commit 8be7a8d

Please sign in to comment.