@wordpress/interactivity
Version:
Package that provides a standard and simple way to handle the frontend interactivity of Gutenberg blocks.
8 lines (7 loc) • 13.2 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/vdom.ts"],
"sourcesContent": ["/**\n * External dependencies\n */\nimport { h, type ComponentChild, type JSX } from 'preact';\n/**\n * Internal dependencies\n */\nimport { warn } from './utils';\nimport { type DirectiveEntry } from './hooks';\n\nconst directivePrefix = `data-wp-`;\nconst namespaces: Array< string | null > = [];\nconst currentNamespace = () => namespaces[ namespaces.length - 1 ] ?? null;\nconst isObject = ( item: unknown ): item is Record< string, unknown > =>\n\tBoolean( item && typeof item === 'object' && item.constructor === Object );\nconst invalidCharsRegex = /[^a-z0-9-_]/i;\n\nfunction parseDirectiveName( directiveName: string ): {\n\tprefix: string;\n\tsuffix: string | null;\n\tuniqueId: string | null;\n} | null {\n\tconst name = directiveName.substring( 8 );\n\n\t// If the name contains invalid characters, it's not a valid directive name.\n\tif ( invalidCharsRegex.test( name ) ) {\n\t\treturn null;\n\t}\n\n\t// Finds the first \"--\" to separate the prefix.\n\tconst suffixIndex = name.indexOf( '--' );\n\n\t// If \"--\" is not found, everything is part of the prefix.\n\tif ( suffixIndex === -1 ) {\n\t\treturn { prefix: name, suffix: null, uniqueId: null };\n\t}\n\n\t// The prefix is the part before the first \"--\".\n\tconst prefix = name.substring( 0, suffixIndex );\n\t// The remaining is the part that starts from \"--\".\n\tconst remaining = name.substring( suffixIndex );\n\n\t// If the suffix starts with \"---\" (but not \"----\"), there is no suffix and\n\t// the remaining is the unique ID.\n\tif ( remaining.startsWith( '---' ) && remaining[ 3 ] !== '-' ) {\n\t\treturn {\n\t\t\tprefix,\n\t\t\tsuffix: null,\n\t\t\tuniqueId: remaining.substring( 3 ) || null,\n\t\t};\n\t}\n\n\t// Otherwise, the remaining is a potential suffix. The first two dashes are\n\t// removed.\n\tlet suffix: string | null = remaining.substring( 2 );\n\t// Search for \"---\" for a unique ID within the suffix.\n\tconst uniqueIdIndex = suffix.indexOf( '---' );\n\n\t// If \"---\" is found, split the suffix and the unique ID.\n\tif (\n\t\tuniqueIdIndex !== -1 &&\n\t\tsuffix.substring( uniqueIdIndex )[ 3 ] !== '-'\n\t) {\n\t\tconst uniqueId = suffix.substring( uniqueIdIndex + 3 ) || null;\n\t\tsuffix = suffix.substring( 0, uniqueIdIndex ) || null;\n\t\treturn { prefix, suffix, uniqueId };\n\t}\n\n\t// Otherwise, the rest is the entire suffix.\n\treturn { prefix, suffix: suffix || null, uniqueId: null };\n}\n\n// Regular expression for reference parsing. It can contain a namespace before\n// the reference, separated by `::`, like `some-namespace::state.somePath`.\n// Namespaces can contain any alphanumeric characters, hyphens, underscores or\n// forward slashes. References don't have any restrictions.\nconst nsPathRegExp = /^([\\w_\\/-]+)::(.+)$/;\n\nexport const hydratedIslands = new WeakSet();\n\n/**\n * Recursive function that transforms a DOM tree into vDOM.\n *\n * @param root The root element or node to start traversing on.\n * @return The resulting vDOM tree.\n */\nexport function toVdom( root: Node ): ComponentChild {\n\tconst nodesToRemove = new Set< Node >();\n\tconst nodesToReplace = new Set< Node >();\n\n\tconst treeWalker = document.createTreeWalker(\n\t\troot,\n\t\t205 // TEXT + CDATA_SECTION + COMMENT + PROCESSING_INSTRUCTION + ELEMENT\n\t);\n\n\tfunction walk( node: Node ): ComponentChild | null {\n\t\tconst { nodeType } = node;\n\n\t\t// TEXT_NODE (3)\n\t\tif ( nodeType === 3 ) {\n\t\t\treturn ( node as Text ).data;\n\t\t}\n\n\t\t// CDATA_SECTION_NODE (4)\n\t\tif ( nodeType === 4 ) {\n\t\t\tnodesToReplace.add( node );\n\t\t\treturn node.nodeValue;\n\t\t}\n\n\t\t// COMMENT_NODE (8) || PROCESSING_INSTRUCTION_NODE (7)\n\t\tif ( nodeType === 8 || nodeType === 7 ) {\n\t\t\tnodesToRemove.add( node );\n\t\t\treturn null;\n\t\t}\n\n\t\tconst elementNode = node as HTMLElement;\n\t\tconst { attributes } = elementNode;\n\t\tconst localName = elementNode.localName as keyof JSX.IntrinsicElements;\n\n\t\tconst props: Record< string, any > = {};\n\t\tconst children: Array< ComponentChild > = [];\n\t\tconst directives: Array<\n\t\t\t[ name: string, namespace: string | null, value: unknown ]\n\t\t> = [];\n\t\tlet ignore = false;\n\t\tlet island = false;\n\n\t\tfor ( let i = 0; i < attributes.length; i++ ) {\n\t\t\tconst attributeName = attributes[ i ].name;\n\t\t\tconst attributeValue = attributes[ i ].value;\n\t\t\tif (\n\t\t\t\tattributeName[ directivePrefix.length ] &&\n\t\t\t\tattributeName.slice( 0, directivePrefix.length ) ===\n\t\t\t\t\tdirectivePrefix\n\t\t\t) {\n\t\t\t\tif ( attributeName === 'data-wp-ignore' ) {\n\t\t\t\t\tignore = true;\n\t\t\t\t} else {\n\t\t\t\t\tconst regexResult = nsPathRegExp.exec( attributeValue );\n\t\t\t\t\tconst namespace = regexResult?.[ 1 ] ?? null;\n\t\t\t\t\tlet value: any = regexResult?.[ 2 ] ?? attributeValue;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst parsedValue = JSON.parse( value );\n\t\t\t\t\t\tvalue = isObject( parsedValue ) ? parsedValue : value;\n\t\t\t\t\t} catch {}\n\t\t\t\t\tif ( attributeName === 'data-wp-interactive' ) {\n\t\t\t\t\t\tisland = true;\n\t\t\t\t\t\tconst islandNamespace =\n\t\t\t\t\t\t\t// eslint-disable-next-line no-nested-ternary\n\t\t\t\t\t\t\ttypeof value === 'string'\n\t\t\t\t\t\t\t\t? value\n\t\t\t\t\t\t\t\t: typeof value?.namespace === 'string'\n\t\t\t\t\t\t\t\t? value.namespace\n\t\t\t\t\t\t\t\t: null;\n\t\t\t\t\t\tnamespaces.push( islandNamespace );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdirectives.push( [ attributeName, namespace, value ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if ( attributeName === 'ref' ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// For boolean attributes with empty string values, use `true`.\n\t\t\t// This prevents Preact from coercing \"\" to false. Camelcase\n\t\t\t// mismatches (readonly\u2192readOnly) are `undefined`, not `true`,\n\t\t\t// falling through to Preact's setAttribute path, which handles them\n\t\t\t// correctly: https://github.com/preactjs/preact/blob/bf7a195ac4b1706468e876e41b27428e3d8a08f3/src/diff/props.js#L114\n\t\t\tif (\n\t\t\t\tattributeValue === '' &&\n\t\t\t\telementNode[ attributeName ] === true\n\t\t\t) {\n\t\t\t\tprops[ attributeName ] = true;\n\t\t\t} else {\n\t\t\t\tprops[ attributeName ] = attributeValue;\n\t\t\t}\n\t\t}\n\n\t\tif ( ignore && ! island ) {\n\t\t\treturn [\n\t\t\t\th< any, any >( localName, {\n\t\t\t\t\t...props,\n\t\t\t\t\tinnerHTML: elementNode.innerHTML,\n\t\t\t\t\t__directives: { ignore: true },\n\t\t\t\t} ),\n\t\t\t];\n\t\t}\n\t\tif ( island ) {\n\t\t\thydratedIslands.add( elementNode );\n\t\t}\n\n\t\tif ( directives.length ) {\n\t\t\tprops.__directives = directives.reduce<\n\t\t\t\tRecord< string, Array< DirectiveEntry > >\n\t\t\t>( ( obj, [ name, ns, value ] ) => {\n\t\t\t\tconst directiveParsed = parseDirectiveName( name );\n\t\t\t\tif ( directiveParsed === null ) {\n\t\t\t\t\tif ( globalThis.SCRIPT_DEBUG ) {\n\t\t\t\t\t\twarn( `Found malformed directive name: ${ name }.` );\n\t\t\t\t\t}\n\t\t\t\t\treturn obj;\n\t\t\t\t}\n\t\t\t\tconst { prefix, suffix, uniqueId } = directiveParsed;\n\n\t\t\t\tobj[ prefix ] = obj[ prefix ] || [];\n\t\t\t\tobj[ prefix ].push( {\n\t\t\t\t\tnamespace: ns ?? currentNamespace()!,\n\t\t\t\t\tvalue: value as DirectiveEntry[ 'value' ],\n\t\t\t\t\tsuffix,\n\t\t\t\t\tuniqueId,\n\t\t\t\t} );\n\t\t\t\treturn obj;\n\t\t\t}, {} );\n\n\t\t\t// Sort directive arrays to ensure stable ordering across browsers.\n\t\t\t// Put nulls first, then sort by suffix and finally by uniqueIds.\n\t\t\tfor ( const prefix in props.__directives ) {\n\t\t\t\tprops.__directives[ prefix ].sort(\n\t\t\t\t\t( a: DirectiveEntry, b: DirectiveEntry ) => {\n\t\t\t\t\t\tconst aSuffix = a.suffix ?? '';\n\t\t\t\t\t\tconst bSuffix = b.suffix ?? '';\n\t\t\t\t\t\tif ( aSuffix !== bSuffix ) {\n\t\t\t\t\t\t\treturn aSuffix < bSuffix ? -1 : 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst aId = a.uniqueId ?? '';\n\t\t\t\t\t\tconst bId = b.uniqueId ?? '';\n\t\t\t\t\t\treturn +( aId > bId ) - +( aId < bId );\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif ( props.__directives?.[ 'each-child' ] ) {\n\t\t\tprops.dangerouslySetInnerHTML = {\n\t\t\t\t__html: elementNode.innerHTML,\n\t\t\t};\n\t\t} else if ( localName === 'template' ) {\n\t\t\tprops.content = [\n\t\t\t\t...( elementNode as HTMLTemplateElement ).content.childNodes,\n\t\t\t].map( ( childNode ) => toVdom( childNode ) );\n\t\t} else {\n\t\t\tlet child = treeWalker.firstChild();\n\t\t\tif ( child ) {\n\t\t\t\twhile ( child ) {\n\t\t\t\t\tconst vnode = walk( child );\n\t\t\t\t\tif ( vnode ) {\n\t\t\t\t\t\tchildren.push( vnode );\n\t\t\t\t\t}\n\t\t\t\t\tchild = treeWalker.nextSibling();\n\t\t\t\t}\n\t\t\t\ttreeWalker.parentNode();\n\t\t\t}\n\t\t}\n\n\t\t// Restore previous namespace.\n\t\tif ( island ) {\n\t\t\tnamespaces.pop();\n\t\t}\n\n\t\treturn h( localName, props, children );\n\t}\n\n\tconst vdom = walk( treeWalker.currentNode );\n\n\tnodesToRemove.forEach( ( node: Node ) =>\n\t\t( node as Comment | ProcessingInstruction ).remove()\n\t);\n\tnodesToReplace.forEach( ( node: Node ) =>\n\t\t( node as CDATASection ).replaceWith(\n\t\t\tnew window.Text( ( node as CDATASection ).nodeValue ?? '' )\n\t\t)\n\t);\n\n\treturn vdom;\n}\n"],
"mappings": "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",
"names": []
}