@wordpress/element 6.42.0 → 6.44.1-next.v.202604091042.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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 6.44.0-next.0 (2026-04-09)
6
+
7
+ ### Enhancement
8
+
9
+ - `createInterpolateElement` now infers tag names from `sprintf` return values, since `sprintf` returns `TransformedText<T>`. ([76974](https://github.com/WordPress/gutenberg/pull/76974))
10
+
11
+ ## 6.43.0 (2026-04-01)
12
+
5
13
  ## 6.42.0 (2026-03-18)
6
14
 
7
15
  ## 6.41.0 (2026-03-04)
package/README.md CHANGED
@@ -109,8 +109,8 @@ You would have something like this as the conversionMap value:
109
109
 
110
110
  _Parameters_
111
111
 
112
- - _interpolatedString_ `string`: The interpolation string to be parsed.
113
- - _conversionMap_ `Record< string, ReactElement >`: The map used to convert the string to a react element.
112
+ - _interpolatedString_ `Input`: The interpolation string to be parsed.
113
+ - _conversionMap_ `ConversionMap< InterpolationString< Input > >`: The map used to convert the string to a react element.
114
114
 
115
115
  _Returns_
116
116
 
@@ -39,7 +39,7 @@ function createFrame(element, tokenStart, tokenLength, prevOffset, leadingTextSt
39
39
  children: []
40
40
  };
41
41
  }
42
- var createInterpolateElement = (interpolatedString, conversionMap) => {
42
+ function createInterpolateElement(interpolatedString, conversionMap) {
43
43
  indoc = interpolatedString;
44
44
  offset = 0;
45
45
  output = [];
@@ -53,7 +53,7 @@ var createInterpolateElement = (interpolatedString, conversionMap) => {
53
53
  do {
54
54
  } while (proceed(conversionMap));
55
55
  return (0, import_react.createElement)(import_react.Fragment, null, ...output);
56
- };
56
+ }
57
57
  var isValidConversionMap = (conversionMap) => {
58
58
  const isObject = typeof conversionMap === "object" && conversionMap !== null;
59
59
  const values = isObject && Object.values(conversionMap);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/create-interpolate-element.ts"],
4
- "sourcesContent": ["/**\n * Internal dependencies\n */\nimport {\n\tcreateElement,\n\tcloneElement,\n\tFragment,\n\tisValidElement,\n\ttype Element as ReactElement,\n} from './react';\n\nlet indoc: string;\nlet offset: number;\nlet output: ( string | ReactElement )[];\nlet stack: Frame[];\n\n/**\n * Matches tags in the localized string\n *\n * This is used for extracting the tag pattern groups for parsing the localized\n * string and along with the map converting it to a react element.\n *\n * There are four references extracted using this tokenizer:\n *\n * match: Full match of the tag (i.e. <strong>, </strong>, <br/>)\n * isClosing: The closing slash, if it exists.\n * name: The name portion of the tag (strong, br) (if )\n * isSelfClosed: The slash on a self closing tag, if it exists.\n */\nconst tokenizer = /<(\\/)?(\\w+)\\s*(\\/)?>/g;\n\ninterface Frame {\n\t/**\n\t * A parent element which may still have nested children not yet parsed.\n\t */\n\telement: ReactElement;\n\n\t/**\n\t * Offset at which parent element first appears.\n\t */\n\ttokenStart: number;\n\n\t/**\n\t * Length of string marking start of parent element.\n\t */\n\ttokenLength: number;\n\n\t/**\n\t * Running offset at which parsing should continue.\n\t */\n\tprevOffset?: number;\n\n\t/**\n\t * Offset at which last closing element finished, used for finding text between elements.\n\t */\n\tleadingTextStart?: number | null;\n\n\t/**\n\t * Children.\n\t */\n\tchildren: ( string | ReactElement )[];\n}\n\n/**\n * Tracks recursive-descent parse state.\n *\n * This is a Stack frame holding parent elements until all children have been\n * parsed.\n *\n * @private\n * @param element A parent element which may still have\n * nested children not yet parsed.\n * @param tokenStart Offset at which parent element first\n * appears.\n * @param tokenLength Length of string marking start of parent\n * element.\n * @param prevOffset Running offset at which parsing should\n * continue.\n * @param leadingTextStart Offset at which last closing element\n * finished, used for finding text between\n * elements.\n *\n * @return The stack frame tracking parse progress.\n */\nfunction createFrame(\n\telement: ReactElement,\n\ttokenStart: number,\n\ttokenLength: number,\n\tprevOffset?: number,\n\tleadingTextStart?: number | null\n): Frame {\n\treturn {\n\t\telement,\n\t\ttokenStart,\n\t\ttokenLength,\n\t\tprevOffset,\n\t\tleadingTextStart,\n\t\tchildren: [],\n\t};\n}\n\n/**\n * This function creates an interpolated element from a passed in string with\n * specific tags matching how the string should be converted to an element via\n * the conversion map value.\n *\n * @example\n * For example, for the given string:\n *\n * \"This is a <span>string</span> with <a>a link</a> and a self-closing\n * <CustomComponentB/> tag\"\n *\n * You would have something like this as the conversionMap value:\n *\n * ```js\n * {\n * span: <span />,\n * a: <a href={ 'https://github.com' } />,\n * CustomComponentB: <CustomComponent />,\n * }\n * ```\n *\n * @param interpolatedString The interpolation string to be parsed.\n * @param conversionMap The map used to convert the string to\n * a react element.\n * @throws {TypeError}\n * @return A wp element.\n */\nconst createInterpolateElement = (\n\tinterpolatedString: string,\n\tconversionMap: Record< string, ReactElement >\n): ReactElement => {\n\tindoc = interpolatedString;\n\toffset = 0;\n\toutput = [];\n\tstack = [];\n\ttokenizer.lastIndex = 0;\n\n\tif ( ! isValidConversionMap( conversionMap ) ) {\n\t\tthrow new TypeError(\n\t\t\t'The conversionMap provided is not valid. It must be an object with values that are React Elements'\n\t\t);\n\t}\n\n\tdo {\n\t\t// twiddle our thumbs\n\t} while ( proceed( conversionMap ) );\n\treturn createElement( Fragment, null, ...output );\n};\n\n/**\n * Validate conversion map.\n *\n * A map is considered valid if it's an object and every value in the object\n * is a React Element\n *\n * @private\n *\n * @param conversionMap The map being validated.\n *\n * @return True means the map is valid.\n */\nconst isValidConversionMap = (\n\tconversionMap: Record< string, ReactElement >\n): boolean => {\n\tconst isObject =\n\t\ttypeof conversionMap === 'object' && conversionMap !== null;\n\tconst values = isObject && Object.values( conversionMap );\n\treturn (\n\t\tisObject &&\n\t\tvalues.length > 0 &&\n\t\tvalues.every( ( element ) => isValidElement( element ) )\n\t);\n};\n\ntype TokenType = 'no-more-tokens' | 'self-closed' | 'opener' | 'closer';\ntype TokenResult =\n\t| [ TokenType & 'no-more-tokens' ]\n\t| [\n\t\t\tTokenType & ( 'self-closed' | 'opener' | 'closer' ),\n\t\t\tstring,\n\t\t\tnumber,\n\t\t\tnumber,\n\t ];\n\n/**\n * This is the iterator over the matches in the string.\n *\n * @private\n *\n * @param conversionMap The conversion map for the string.\n *\n * @return true for continuing to iterate, false for finished.\n */\nfunction proceed( conversionMap: Record< string, ReactElement > ): boolean {\n\tconst next = nextToken();\n\tconst [ tokenType, name, startOffset, tokenLength ] = next;\n\tconst stackDepth = stack.length;\n\tconst leadingTextStart = startOffset > offset ? offset : null;\n\tif ( name && ! conversionMap[ name ] ) {\n\t\taddText();\n\t\treturn false;\n\t}\n\tswitch ( tokenType ) {\n\t\tcase 'no-more-tokens':\n\t\t\tif ( stackDepth !== 0 ) {\n\t\t\t\tconst { leadingTextStart: stackLeadingText, tokenStart } =\n\t\t\t\t\tstack.pop();\n\t\t\t\toutput.push( indoc.substr( stackLeadingText, tokenStart ) );\n\t\t\t}\n\t\t\taddText();\n\t\t\treturn false;\n\n\t\tcase 'self-closed':\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\tif ( null !== leadingTextStart ) {\n\t\t\t\t\toutput.push(\n\t\t\t\t\t\tindoc.substr(\n\t\t\t\t\t\t\tleadingTextStart,\n\t\t\t\t\t\t\tstartOffset - leadingTextStart\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\toutput.push( conversionMap[ name ] );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we found an inner element.\n\t\t\taddChild(\n\t\t\t\tcreateFrame( conversionMap[ name ], startOffset, tokenLength )\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'opener':\n\t\t\tstack.push(\n\t\t\t\tcreateFrame(\n\t\t\t\t\tconversionMap[ name ],\n\t\t\t\t\tstartOffset,\n\t\t\t\t\ttokenLength,\n\t\t\t\t\tstartOffset + tokenLength,\n\t\t\t\t\tleadingTextStart\n\t\t\t\t)\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'closer':\n\t\t\t// If we're not nesting then this is easy - close the block.\n\t\t\tif ( 1 === stackDepth ) {\n\t\t\t\tcloseOuterElement( startOffset );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we're nested and we have to close out the current\n\t\t\t// block and add it as a innerBlock to the parent.\n\t\t\tconst stackTop = stack.pop();\n\t\t\tconst text = indoc.substr(\n\t\t\t\tstackTop.prevOffset,\n\t\t\t\tstartOffset - stackTop.prevOffset\n\t\t\t);\n\t\t\tstackTop.children.push( text );\n\t\t\tstackTop.prevOffset = startOffset + tokenLength;\n\t\t\tconst frame = createFrame(\n\t\t\t\tstackTop.element,\n\t\t\t\tstackTop.tokenStart,\n\t\t\t\tstackTop.tokenLength,\n\t\t\t\tstartOffset + tokenLength\n\t\t\t);\n\t\t\tframe.children = stackTop.children;\n\t\t\taddChild( frame );\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tdefault:\n\t\t\taddText();\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Grabs the next token match in the string and returns it's details.\n *\n * @private\n *\n * @return An array of details for the token matched.\n */\nfunction nextToken(): TokenResult {\n\tconst matches = tokenizer.exec( indoc );\n\t// We have no more tokens.\n\tif ( null === matches ) {\n\t\treturn [ 'no-more-tokens' ];\n\t}\n\tconst startedAt = matches.index;\n\tconst [ match, isClosing, name, isSelfClosed ] = matches;\n\tconst length = match.length;\n\tif ( isSelfClosed ) {\n\t\treturn [ 'self-closed', name, startedAt, length ];\n\t}\n\tif ( isClosing ) {\n\t\treturn [ 'closer', name, startedAt, length ];\n\t}\n\treturn [ 'opener', name, startedAt, length ];\n}\n\n/**\n * Pushes text extracted from the indoc string to the output stack given the\n * current rawLength value and offset (if rawLength is provided ) or the\n * indoc.length and offset.\n *\n * @private\n */\nfunction addText(): void {\n\tconst length = indoc.length - offset;\n\tif ( 0 === length ) {\n\t\treturn;\n\t}\n\toutput.push( indoc.substr( offset, length ) );\n}\n\n/**\n * Pushes a child element to the associated parent element's children for the\n * parent currently active in the stack.\n *\n * @private\n *\n * @param {Frame} frame The Frame containing the child element and it's\n * token information.\n */\nfunction addChild( frame: Frame ): void {\n\tconst { element, tokenStart, tokenLength, prevOffset, children } = frame;\n\tconst parent = stack[ stack.length - 1 ];\n\tconst text = indoc.substr(\n\t\tparent.prevOffset,\n\t\ttokenStart - parent.prevOffset\n\t);\n\n\tif ( text ) {\n\t\tparent.children.push( text );\n\t}\n\n\tparent.children.push( cloneElement( element, null, ...children ) );\n\tparent.prevOffset = prevOffset ? prevOffset : tokenStart + tokenLength;\n}\n\n/**\n * This is called for closing tags. It creates the element currently active in\n * the stack.\n *\n * @private\n *\n * @param {number} endOffset Offset at which the closing tag for the element\n * begins in the string. If this is greater than the\n * prevOffset attached to the element, then this\n * helps capture any remaining nested text nodes in\n * the element.\n */\nfunction closeOuterElement( endOffset: number ): void {\n\tconst { element, leadingTextStart, prevOffset, tokenStart, children } =\n\t\tstack.pop();\n\n\tconst text = endOffset\n\t\t? indoc.substr( prevOffset, endOffset - prevOffset )\n\t\t: indoc.substr( prevOffset );\n\n\tif ( text ) {\n\t\tchildren.push( text );\n\t}\n\n\tif ( null !== leadingTextStart ) {\n\t\toutput.push(\n\t\t\tindoc.substr( leadingTextStart, tokenStart - leadingTextStart )\n\t\t);\n\t}\n\n\toutput.push( cloneElement( element, null, ...children ) );\n}\n\nexport default createInterpolateElement;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Internal dependencies\n */\nimport {\n\tcreateElement,\n\tcloneElement,\n\tFragment,\n\tisValidElement,\n\ttype Element as ReactElement,\n} from './react';\nimport type {\n\tConversionMap,\n\tInterpolationInput,\n\tInterpolationString,\n} from './types';\n\nlet indoc: string;\nlet offset: number;\nlet output: ( string | ReactElement )[];\nlet stack: Frame[];\n\n/**\n * Matches tags in the localized string\n *\n * This is used for extracting the tag pattern groups for parsing the localized\n * string and along with the map converting it to a react element.\n *\n * There are four references extracted using this tokenizer:\n *\n * match: Full match of the tag (i.e. <strong>, </strong>, <br/>)\n * isClosing: The closing slash, if it exists.\n * name: The name portion of the tag (strong, br) (if )\n * isSelfClosed: The slash on a self closing tag, if it exists.\n */\nconst tokenizer = /<(\\/)?(\\w+)\\s*(\\/)?>/g;\n\ninterface Frame {\n\t/**\n\t * A parent element which may still have nested children not yet parsed.\n\t */\n\telement: ReactElement;\n\n\t/**\n\t * Offset at which parent element first appears.\n\t */\n\ttokenStart: number;\n\n\t/**\n\t * Length of string marking start of parent element.\n\t */\n\ttokenLength: number;\n\n\t/**\n\t * Running offset at which parsing should continue.\n\t */\n\tprevOffset?: number;\n\n\t/**\n\t * Offset at which last closing element finished, used for finding text between elements.\n\t */\n\tleadingTextStart?: number | null;\n\n\t/**\n\t * Children.\n\t */\n\tchildren: ( string | ReactElement )[];\n}\n\n/**\n * Tracks recursive-descent parse state.\n *\n * This is a Stack frame holding parent elements until all children have been\n * parsed.\n *\n * @private\n * @param element A parent element which may still have\n * nested children not yet parsed.\n * @param tokenStart Offset at which parent element first\n * appears.\n * @param tokenLength Length of string marking start of parent\n * element.\n * @param prevOffset Running offset at which parsing should\n * continue.\n * @param leadingTextStart Offset at which last closing element\n * finished, used for finding text between\n * elements.\n *\n * @return The stack frame tracking parse progress.\n */\nfunction createFrame(\n\telement: ReactElement,\n\ttokenStart: number,\n\ttokenLength: number,\n\tprevOffset?: number,\n\tleadingTextStart?: number | null\n): Frame {\n\treturn {\n\t\telement,\n\t\ttokenStart,\n\t\ttokenLength,\n\t\tprevOffset,\n\t\tleadingTextStart,\n\t\tchildren: [],\n\t};\n}\n\n/**\n * This function creates an interpolated element from a passed in string with\n * specific tags matching how the string should be converted to an element via\n * the conversion map value.\n *\n * @example\n * For example, for the given string:\n *\n * \"This is a <span>string</span> with <a>a link</a> and a self-closing\n * <CustomComponentB/> tag\"\n *\n * You would have something like this as the conversionMap value:\n *\n * ```js\n * {\n * span: <span />,\n * a: <a href={ 'https://github.com' } />,\n * CustomComponentB: <CustomComponent />,\n * }\n * ```\n *\n * @param interpolatedString The interpolation string to be parsed.\n * @param conversionMap The map used to convert the string to\n * a react element.\n * @throws {TypeError}\n * @return A wp element.\n */\nfunction createInterpolateElement< Input extends InterpolationInput >(\n\tinterpolatedString: Input,\n\tconversionMap: ConversionMap< InterpolationString< Input > >\n): ReactElement {\n\tindoc = interpolatedString;\n\toffset = 0;\n\toutput = [];\n\tstack = [];\n\ttokenizer.lastIndex = 0;\n\n\tif ( ! isValidConversionMap( conversionMap ) ) {\n\t\tthrow new TypeError(\n\t\t\t'The conversionMap provided is not valid. It must be an object with values that are React Elements'\n\t\t);\n\t}\n\n\tdo {\n\t\t// twiddle our thumbs\n\t} while ( proceed( conversionMap ) );\n\treturn createElement( Fragment, null, ...output );\n}\n\n/**\n * Validate conversion map.\n *\n * A map is considered valid if it's an object and every value in the object\n * is a React Element\n *\n * @private\n *\n * @param conversionMap The map being validated.\n *\n * @return True means the map is valid.\n */\nconst isValidConversionMap = (\n\tconversionMap: Record< string, ReactElement >\n): boolean => {\n\tconst isObject =\n\t\ttypeof conversionMap === 'object' && conversionMap !== null;\n\tconst values = isObject && Object.values( conversionMap );\n\treturn (\n\t\tisObject &&\n\t\tvalues.length > 0 &&\n\t\tvalues.every( ( element ) => isValidElement( element ) )\n\t);\n};\n\ntype TokenType = 'no-more-tokens' | 'self-closed' | 'opener' | 'closer';\ntype TokenResult =\n\t| [ TokenType & 'no-more-tokens' ]\n\t| [\n\t\t\tTokenType & ( 'self-closed' | 'opener' | 'closer' ),\n\t\t\tstring,\n\t\t\tnumber,\n\t\t\tnumber,\n\t ];\n\n/**\n * This is the iterator over the matches in the string.\n *\n * @private\n *\n * @param conversionMap The conversion map for the string.\n *\n * @return true for continuing to iterate, false for finished.\n */\nfunction proceed( conversionMap: Record< string, ReactElement > ): boolean {\n\tconst next = nextToken();\n\tconst [ tokenType, name, startOffset, tokenLength ] = next;\n\tconst stackDepth = stack.length;\n\tconst leadingTextStart = startOffset > offset ? offset : null;\n\tif ( name && ! conversionMap[ name ] ) {\n\t\taddText();\n\t\treturn false;\n\t}\n\tswitch ( tokenType ) {\n\t\tcase 'no-more-tokens':\n\t\t\tif ( stackDepth !== 0 ) {\n\t\t\t\tconst { leadingTextStart: stackLeadingText, tokenStart } =\n\t\t\t\t\tstack.pop();\n\t\t\t\toutput.push( indoc.substr( stackLeadingText, tokenStart ) );\n\t\t\t}\n\t\t\taddText();\n\t\t\treturn false;\n\n\t\tcase 'self-closed':\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\tif ( null !== leadingTextStart ) {\n\t\t\t\t\toutput.push(\n\t\t\t\t\t\tindoc.substr(\n\t\t\t\t\t\t\tleadingTextStart,\n\t\t\t\t\t\t\tstartOffset - leadingTextStart\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\toutput.push( conversionMap[ name ] );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we found an inner element.\n\t\t\taddChild(\n\t\t\t\tcreateFrame( conversionMap[ name ], startOffset, tokenLength )\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'opener':\n\t\t\tstack.push(\n\t\t\t\tcreateFrame(\n\t\t\t\t\tconversionMap[ name ],\n\t\t\t\t\tstartOffset,\n\t\t\t\t\ttokenLength,\n\t\t\t\t\tstartOffset + tokenLength,\n\t\t\t\t\tleadingTextStart\n\t\t\t\t)\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'closer':\n\t\t\t// If we're not nesting then this is easy - close the block.\n\t\t\tif ( 1 === stackDepth ) {\n\t\t\t\tcloseOuterElement( startOffset );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we're nested and we have to close out the current\n\t\t\t// block and add it as a innerBlock to the parent.\n\t\t\tconst stackTop = stack.pop();\n\t\t\tconst text = indoc.substr(\n\t\t\t\tstackTop.prevOffset,\n\t\t\t\tstartOffset - stackTop.prevOffset\n\t\t\t);\n\t\t\tstackTop.children.push( text );\n\t\t\tstackTop.prevOffset = startOffset + tokenLength;\n\t\t\tconst frame = createFrame(\n\t\t\t\tstackTop.element,\n\t\t\t\tstackTop.tokenStart,\n\t\t\t\tstackTop.tokenLength,\n\t\t\t\tstartOffset + tokenLength\n\t\t\t);\n\t\t\tframe.children = stackTop.children;\n\t\t\taddChild( frame );\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tdefault:\n\t\t\taddText();\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Grabs the next token match in the string and returns it's details.\n *\n * @private\n *\n * @return An array of details for the token matched.\n */\nfunction nextToken(): TokenResult {\n\tconst matches = tokenizer.exec( indoc );\n\t// We have no more tokens.\n\tif ( null === matches ) {\n\t\treturn [ 'no-more-tokens' ];\n\t}\n\tconst startedAt = matches.index;\n\tconst [ match, isClosing, name, isSelfClosed ] = matches;\n\tconst length = match.length;\n\tif ( isSelfClosed ) {\n\t\treturn [ 'self-closed', name, startedAt, length ];\n\t}\n\tif ( isClosing ) {\n\t\treturn [ 'closer', name, startedAt, length ];\n\t}\n\treturn [ 'opener', name, startedAt, length ];\n}\n\n/**\n * Pushes text extracted from the indoc string to the output stack given the\n * current rawLength value and offset (if rawLength is provided ) or the\n * indoc.length and offset.\n *\n * @private\n */\nfunction addText(): void {\n\tconst length = indoc.length - offset;\n\tif ( 0 === length ) {\n\t\treturn;\n\t}\n\toutput.push( indoc.substr( offset, length ) );\n}\n\n/**\n * Pushes a child element to the associated parent element's children for the\n * parent currently active in the stack.\n *\n * @private\n *\n * @param {Frame} frame The Frame containing the child element and it's\n * token information.\n */\nfunction addChild( frame: Frame ): void {\n\tconst { element, tokenStart, tokenLength, prevOffset, children } = frame;\n\tconst parent = stack[ stack.length - 1 ];\n\tconst text = indoc.substr(\n\t\tparent.prevOffset,\n\t\ttokenStart - parent.prevOffset\n\t);\n\n\tif ( text ) {\n\t\tparent.children.push( text );\n\t}\n\n\tparent.children.push( cloneElement( element, null, ...children ) );\n\tparent.prevOffset = prevOffset ? prevOffset : tokenStart + tokenLength;\n}\n\n/**\n * This is called for closing tags. It creates the element currently active in\n * the stack.\n *\n * @private\n *\n * @param {number} endOffset Offset at which the closing tag for the element\n * begins in the string. If this is greater than the\n * prevOffset attached to the element, then this\n * helps capture any remaining nested text nodes in\n * the element.\n */\nfunction closeOuterElement( endOffset: number ): void {\n\tconst { element, leadingTextStart, prevOffset, tokenStart, children } =\n\t\tstack.pop();\n\n\tconst text = endOffset\n\t\t? indoc.substr( prevOffset, endOffset - prevOffset )\n\t\t: indoc.substr( prevOffset );\n\n\tif ( text ) {\n\t\tchildren.push( text );\n\t}\n\n\tif ( null !== leadingTextStart ) {\n\t\toutput.push(\n\t\t\tindoc.substr( leadingTextStart, tokenStart - leadingTextStart )\n\t\t);\n\t}\n\n\toutput.push( cloneElement( element, null, ...children ) );\n}\n\nexport default createInterpolateElement;\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // packages/element/src/types.ts
17
+ var types_exports = {};
18
+ module.exports = __toCommonJS(types_exports);
19
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/types.ts"],
4
+ "sourcesContent": ["import type { ReactElement } from 'react';\n\n/**\n * Mirrors `TransformedText` from @wordpress/i18n.\n * We don't import directly to avoid a circular dependency.\n */\ntype TransformedText< T extends string > = string & {\n\treadonly __transformedText: T;\n};\n\n/**\n * The input that can be passed to `createInterpolateElement`.\n */\nexport type InterpolationInput = string | TransformedText< string >;\n\n/**\n * The literal string extracted from the input.\n */\nexport type InterpolationString< Input > = Input extends TransformedText<\n\tinfer Text\n>\n\t? Text\n\t: Input;\n\n/**\n * Recursively trims trailing spaces from a string type.\n * Matches the runtime tokenizer's `\\s*` before the closing `>` or `/>`.\n */\ntype TrimTrailingSpaces< S extends string > = S extends `${ infer Rest } `\n\t? TrimTrailingSpaces< Rest >\n\t: S;\n\n/**\n * Helper type to extract tag name and handle closing/self-closing indicators.\n * Filters out tags with spaces as they won't be parsed by the tokenizer.\n */\ntype ExtractTagName< T extends string > =\n\t// Skip closing tags like \"/div\"\n\tT extends `/${ string }`\n\t\t? never\n\t\t: TrimTrailingSpaces< T > extends infer Name extends string\n\t\t? Name extends ''\n\t\t\t? never // Empty tag name\n\t\t\t: Name extends `${ string } ${ string }`\n\t\t\t? never // Skip tags with inner spaces like \"spaced token\"\n\t\t\t: Name extends `${ infer Base }/`\n\t\t\t? Base // Self-closing tags like \"br/\"\n\t\t\t: Name // Regular opening tags like \"div\"\n\t\t: never;\n\n/**\n * Utility type to extract all tag names from a template literal string.\n * Only handles simple tags without attributes, matching the runtime tokenizer.\n */\nexport type ExtractTags< T extends string > =\n\tT extends `${ string }<${ infer Tag }>${ infer After }`\n\t\t? ExtractTagName< Tag > | ExtractTags< After >\n\t\t: never;\n\n/**\n * Utility type to create a conversion map that:\n * - Makes extracted tag keys optional\n * - Only allows properties for tags found in the template literal\n */\nexport type ConversionMap< T extends string > = Partial<\n\tRecord< ExtractTags< T >, ReactElement >\n>;\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;",
6
+ "names": []
7
+ }
@@ -20,7 +20,7 @@ function createFrame(element, tokenStart, tokenLength, prevOffset, leadingTextSt
20
20
  children: []
21
21
  };
22
22
  }
23
- var createInterpolateElement = (interpolatedString, conversionMap) => {
23
+ function createInterpolateElement(interpolatedString, conversionMap) {
24
24
  indoc = interpolatedString;
25
25
  offset = 0;
26
26
  output = [];
@@ -34,7 +34,7 @@ var createInterpolateElement = (interpolatedString, conversionMap) => {
34
34
  do {
35
35
  } while (proceed(conversionMap));
36
36
  return createElement(Fragment, null, ...output);
37
- };
37
+ }
38
38
  var isValidConversionMap = (conversionMap) => {
39
39
  const isObject = typeof conversionMap === "object" && conversionMap !== null;
40
40
  const values = isObject && Object.values(conversionMap);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/create-interpolate-element.ts"],
4
- "sourcesContent": ["/**\n * Internal dependencies\n */\nimport {\n\tcreateElement,\n\tcloneElement,\n\tFragment,\n\tisValidElement,\n\ttype Element as ReactElement,\n} from './react';\n\nlet indoc: string;\nlet offset: number;\nlet output: ( string | ReactElement )[];\nlet stack: Frame[];\n\n/**\n * Matches tags in the localized string\n *\n * This is used for extracting the tag pattern groups for parsing the localized\n * string and along with the map converting it to a react element.\n *\n * There are four references extracted using this tokenizer:\n *\n * match: Full match of the tag (i.e. <strong>, </strong>, <br/>)\n * isClosing: The closing slash, if it exists.\n * name: The name portion of the tag (strong, br) (if )\n * isSelfClosed: The slash on a self closing tag, if it exists.\n */\nconst tokenizer = /<(\\/)?(\\w+)\\s*(\\/)?>/g;\n\ninterface Frame {\n\t/**\n\t * A parent element which may still have nested children not yet parsed.\n\t */\n\telement: ReactElement;\n\n\t/**\n\t * Offset at which parent element first appears.\n\t */\n\ttokenStart: number;\n\n\t/**\n\t * Length of string marking start of parent element.\n\t */\n\ttokenLength: number;\n\n\t/**\n\t * Running offset at which parsing should continue.\n\t */\n\tprevOffset?: number;\n\n\t/**\n\t * Offset at which last closing element finished, used for finding text between elements.\n\t */\n\tleadingTextStart?: number | null;\n\n\t/**\n\t * Children.\n\t */\n\tchildren: ( string | ReactElement )[];\n}\n\n/**\n * Tracks recursive-descent parse state.\n *\n * This is a Stack frame holding parent elements until all children have been\n * parsed.\n *\n * @private\n * @param element A parent element which may still have\n * nested children not yet parsed.\n * @param tokenStart Offset at which parent element first\n * appears.\n * @param tokenLength Length of string marking start of parent\n * element.\n * @param prevOffset Running offset at which parsing should\n * continue.\n * @param leadingTextStart Offset at which last closing element\n * finished, used for finding text between\n * elements.\n *\n * @return The stack frame tracking parse progress.\n */\nfunction createFrame(\n\telement: ReactElement,\n\ttokenStart: number,\n\ttokenLength: number,\n\tprevOffset?: number,\n\tleadingTextStart?: number | null\n): Frame {\n\treturn {\n\t\telement,\n\t\ttokenStart,\n\t\ttokenLength,\n\t\tprevOffset,\n\t\tleadingTextStart,\n\t\tchildren: [],\n\t};\n}\n\n/**\n * This function creates an interpolated element from a passed in string with\n * specific tags matching how the string should be converted to an element via\n * the conversion map value.\n *\n * @example\n * For example, for the given string:\n *\n * \"This is a <span>string</span> with <a>a link</a> and a self-closing\n * <CustomComponentB/> tag\"\n *\n * You would have something like this as the conversionMap value:\n *\n * ```js\n * {\n * span: <span />,\n * a: <a href={ 'https://github.com' } />,\n * CustomComponentB: <CustomComponent />,\n * }\n * ```\n *\n * @param interpolatedString The interpolation string to be parsed.\n * @param conversionMap The map used to convert the string to\n * a react element.\n * @throws {TypeError}\n * @return A wp element.\n */\nconst createInterpolateElement = (\n\tinterpolatedString: string,\n\tconversionMap: Record< string, ReactElement >\n): ReactElement => {\n\tindoc = interpolatedString;\n\toffset = 0;\n\toutput = [];\n\tstack = [];\n\ttokenizer.lastIndex = 0;\n\n\tif ( ! isValidConversionMap( conversionMap ) ) {\n\t\tthrow new TypeError(\n\t\t\t'The conversionMap provided is not valid. It must be an object with values that are React Elements'\n\t\t);\n\t}\n\n\tdo {\n\t\t// twiddle our thumbs\n\t} while ( proceed( conversionMap ) );\n\treturn createElement( Fragment, null, ...output );\n};\n\n/**\n * Validate conversion map.\n *\n * A map is considered valid if it's an object and every value in the object\n * is a React Element\n *\n * @private\n *\n * @param conversionMap The map being validated.\n *\n * @return True means the map is valid.\n */\nconst isValidConversionMap = (\n\tconversionMap: Record< string, ReactElement >\n): boolean => {\n\tconst isObject =\n\t\ttypeof conversionMap === 'object' && conversionMap !== null;\n\tconst values = isObject && Object.values( conversionMap );\n\treturn (\n\t\tisObject &&\n\t\tvalues.length > 0 &&\n\t\tvalues.every( ( element ) => isValidElement( element ) )\n\t);\n};\n\ntype TokenType = 'no-more-tokens' | 'self-closed' | 'opener' | 'closer';\ntype TokenResult =\n\t| [ TokenType & 'no-more-tokens' ]\n\t| [\n\t\t\tTokenType & ( 'self-closed' | 'opener' | 'closer' ),\n\t\t\tstring,\n\t\t\tnumber,\n\t\t\tnumber,\n\t ];\n\n/**\n * This is the iterator over the matches in the string.\n *\n * @private\n *\n * @param conversionMap The conversion map for the string.\n *\n * @return true for continuing to iterate, false for finished.\n */\nfunction proceed( conversionMap: Record< string, ReactElement > ): boolean {\n\tconst next = nextToken();\n\tconst [ tokenType, name, startOffset, tokenLength ] = next;\n\tconst stackDepth = stack.length;\n\tconst leadingTextStart = startOffset > offset ? offset : null;\n\tif ( name && ! conversionMap[ name ] ) {\n\t\taddText();\n\t\treturn false;\n\t}\n\tswitch ( tokenType ) {\n\t\tcase 'no-more-tokens':\n\t\t\tif ( stackDepth !== 0 ) {\n\t\t\t\tconst { leadingTextStart: stackLeadingText, tokenStart } =\n\t\t\t\t\tstack.pop();\n\t\t\t\toutput.push( indoc.substr( stackLeadingText, tokenStart ) );\n\t\t\t}\n\t\t\taddText();\n\t\t\treturn false;\n\n\t\tcase 'self-closed':\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\tif ( null !== leadingTextStart ) {\n\t\t\t\t\toutput.push(\n\t\t\t\t\t\tindoc.substr(\n\t\t\t\t\t\t\tleadingTextStart,\n\t\t\t\t\t\t\tstartOffset - leadingTextStart\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\toutput.push( conversionMap[ name ] );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we found an inner element.\n\t\t\taddChild(\n\t\t\t\tcreateFrame( conversionMap[ name ], startOffset, tokenLength )\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'opener':\n\t\t\tstack.push(\n\t\t\t\tcreateFrame(\n\t\t\t\t\tconversionMap[ name ],\n\t\t\t\t\tstartOffset,\n\t\t\t\t\ttokenLength,\n\t\t\t\t\tstartOffset + tokenLength,\n\t\t\t\t\tleadingTextStart\n\t\t\t\t)\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'closer':\n\t\t\t// If we're not nesting then this is easy - close the block.\n\t\t\tif ( 1 === stackDepth ) {\n\t\t\t\tcloseOuterElement( startOffset );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we're nested and we have to close out the current\n\t\t\t// block and add it as a innerBlock to the parent.\n\t\t\tconst stackTop = stack.pop();\n\t\t\tconst text = indoc.substr(\n\t\t\t\tstackTop.prevOffset,\n\t\t\t\tstartOffset - stackTop.prevOffset\n\t\t\t);\n\t\t\tstackTop.children.push( text );\n\t\t\tstackTop.prevOffset = startOffset + tokenLength;\n\t\t\tconst frame = createFrame(\n\t\t\t\tstackTop.element,\n\t\t\t\tstackTop.tokenStart,\n\t\t\t\tstackTop.tokenLength,\n\t\t\t\tstartOffset + tokenLength\n\t\t\t);\n\t\t\tframe.children = stackTop.children;\n\t\t\taddChild( frame );\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tdefault:\n\t\t\taddText();\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Grabs the next token match in the string and returns it's details.\n *\n * @private\n *\n * @return An array of details for the token matched.\n */\nfunction nextToken(): TokenResult {\n\tconst matches = tokenizer.exec( indoc );\n\t// We have no more tokens.\n\tif ( null === matches ) {\n\t\treturn [ 'no-more-tokens' ];\n\t}\n\tconst startedAt = matches.index;\n\tconst [ match, isClosing, name, isSelfClosed ] = matches;\n\tconst length = match.length;\n\tif ( isSelfClosed ) {\n\t\treturn [ 'self-closed', name, startedAt, length ];\n\t}\n\tif ( isClosing ) {\n\t\treturn [ 'closer', name, startedAt, length ];\n\t}\n\treturn [ 'opener', name, startedAt, length ];\n}\n\n/**\n * Pushes text extracted from the indoc string to the output stack given the\n * current rawLength value and offset (if rawLength is provided ) or the\n * indoc.length and offset.\n *\n * @private\n */\nfunction addText(): void {\n\tconst length = indoc.length - offset;\n\tif ( 0 === length ) {\n\t\treturn;\n\t}\n\toutput.push( indoc.substr( offset, length ) );\n}\n\n/**\n * Pushes a child element to the associated parent element's children for the\n * parent currently active in the stack.\n *\n * @private\n *\n * @param {Frame} frame The Frame containing the child element and it's\n * token information.\n */\nfunction addChild( frame: Frame ): void {\n\tconst { element, tokenStart, tokenLength, prevOffset, children } = frame;\n\tconst parent = stack[ stack.length - 1 ];\n\tconst text = indoc.substr(\n\t\tparent.prevOffset,\n\t\ttokenStart - parent.prevOffset\n\t);\n\n\tif ( text ) {\n\t\tparent.children.push( text );\n\t}\n\n\tparent.children.push( cloneElement( element, null, ...children ) );\n\tparent.prevOffset = prevOffset ? prevOffset : tokenStart + tokenLength;\n}\n\n/**\n * This is called for closing tags. It creates the element currently active in\n * the stack.\n *\n * @private\n *\n * @param {number} endOffset Offset at which the closing tag for the element\n * begins in the string. If this is greater than the\n * prevOffset attached to the element, then this\n * helps capture any remaining nested text nodes in\n * the element.\n */\nfunction closeOuterElement( endOffset: number ): void {\n\tconst { element, leadingTextStart, prevOffset, tokenStart, children } =\n\t\tstack.pop();\n\n\tconst text = endOffset\n\t\t? indoc.substr( prevOffset, endOffset - prevOffset )\n\t\t: indoc.substr( prevOffset );\n\n\tif ( text ) {\n\t\tchildren.push( text );\n\t}\n\n\tif ( null !== leadingTextStart ) {\n\t\toutput.push(\n\t\t\tindoc.substr( leadingTextStart, tokenStart - leadingTextStart )\n\t\t);\n\t}\n\n\toutput.push( cloneElement( element, null, ...children ) );\n}\n\nexport default createInterpolateElement;\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Internal dependencies\n */\nimport {\n\tcreateElement,\n\tcloneElement,\n\tFragment,\n\tisValidElement,\n\ttype Element as ReactElement,\n} from './react';\nimport type {\n\tConversionMap,\n\tInterpolationInput,\n\tInterpolationString,\n} from './types';\n\nlet indoc: string;\nlet offset: number;\nlet output: ( string | ReactElement )[];\nlet stack: Frame[];\n\n/**\n * Matches tags in the localized string\n *\n * This is used for extracting the tag pattern groups for parsing the localized\n * string and along with the map converting it to a react element.\n *\n * There are four references extracted using this tokenizer:\n *\n * match: Full match of the tag (i.e. <strong>, </strong>, <br/>)\n * isClosing: The closing slash, if it exists.\n * name: The name portion of the tag (strong, br) (if )\n * isSelfClosed: The slash on a self closing tag, if it exists.\n */\nconst tokenizer = /<(\\/)?(\\w+)\\s*(\\/)?>/g;\n\ninterface Frame {\n\t/**\n\t * A parent element which may still have nested children not yet parsed.\n\t */\n\telement: ReactElement;\n\n\t/**\n\t * Offset at which parent element first appears.\n\t */\n\ttokenStart: number;\n\n\t/**\n\t * Length of string marking start of parent element.\n\t */\n\ttokenLength: number;\n\n\t/**\n\t * Running offset at which parsing should continue.\n\t */\n\tprevOffset?: number;\n\n\t/**\n\t * Offset at which last closing element finished, used for finding text between elements.\n\t */\n\tleadingTextStart?: number | null;\n\n\t/**\n\t * Children.\n\t */\n\tchildren: ( string | ReactElement )[];\n}\n\n/**\n * Tracks recursive-descent parse state.\n *\n * This is a Stack frame holding parent elements until all children have been\n * parsed.\n *\n * @private\n * @param element A parent element which may still have\n * nested children not yet parsed.\n * @param tokenStart Offset at which parent element first\n * appears.\n * @param tokenLength Length of string marking start of parent\n * element.\n * @param prevOffset Running offset at which parsing should\n * continue.\n * @param leadingTextStart Offset at which last closing element\n * finished, used for finding text between\n * elements.\n *\n * @return The stack frame tracking parse progress.\n */\nfunction createFrame(\n\telement: ReactElement,\n\ttokenStart: number,\n\ttokenLength: number,\n\tprevOffset?: number,\n\tleadingTextStart?: number | null\n): Frame {\n\treturn {\n\t\telement,\n\t\ttokenStart,\n\t\ttokenLength,\n\t\tprevOffset,\n\t\tleadingTextStart,\n\t\tchildren: [],\n\t};\n}\n\n/**\n * This function creates an interpolated element from a passed in string with\n * specific tags matching how the string should be converted to an element via\n * the conversion map value.\n *\n * @example\n * For example, for the given string:\n *\n * \"This is a <span>string</span> with <a>a link</a> and a self-closing\n * <CustomComponentB/> tag\"\n *\n * You would have something like this as the conversionMap value:\n *\n * ```js\n * {\n * span: <span />,\n * a: <a href={ 'https://github.com' } />,\n * CustomComponentB: <CustomComponent />,\n * }\n * ```\n *\n * @param interpolatedString The interpolation string to be parsed.\n * @param conversionMap The map used to convert the string to\n * a react element.\n * @throws {TypeError}\n * @return A wp element.\n */\nfunction createInterpolateElement< Input extends InterpolationInput >(\n\tinterpolatedString: Input,\n\tconversionMap: ConversionMap< InterpolationString< Input > >\n): ReactElement {\n\tindoc = interpolatedString;\n\toffset = 0;\n\toutput = [];\n\tstack = [];\n\ttokenizer.lastIndex = 0;\n\n\tif ( ! isValidConversionMap( conversionMap ) ) {\n\t\tthrow new TypeError(\n\t\t\t'The conversionMap provided is not valid. It must be an object with values that are React Elements'\n\t\t);\n\t}\n\n\tdo {\n\t\t// twiddle our thumbs\n\t} while ( proceed( conversionMap ) );\n\treturn createElement( Fragment, null, ...output );\n}\n\n/**\n * Validate conversion map.\n *\n * A map is considered valid if it's an object and every value in the object\n * is a React Element\n *\n * @private\n *\n * @param conversionMap The map being validated.\n *\n * @return True means the map is valid.\n */\nconst isValidConversionMap = (\n\tconversionMap: Record< string, ReactElement >\n): boolean => {\n\tconst isObject =\n\t\ttypeof conversionMap === 'object' && conversionMap !== null;\n\tconst values = isObject && Object.values( conversionMap );\n\treturn (\n\t\tisObject &&\n\t\tvalues.length > 0 &&\n\t\tvalues.every( ( element ) => isValidElement( element ) )\n\t);\n};\n\ntype TokenType = 'no-more-tokens' | 'self-closed' | 'opener' | 'closer';\ntype TokenResult =\n\t| [ TokenType & 'no-more-tokens' ]\n\t| [\n\t\t\tTokenType & ( 'self-closed' | 'opener' | 'closer' ),\n\t\t\tstring,\n\t\t\tnumber,\n\t\t\tnumber,\n\t ];\n\n/**\n * This is the iterator over the matches in the string.\n *\n * @private\n *\n * @param conversionMap The conversion map for the string.\n *\n * @return true for continuing to iterate, false for finished.\n */\nfunction proceed( conversionMap: Record< string, ReactElement > ): boolean {\n\tconst next = nextToken();\n\tconst [ tokenType, name, startOffset, tokenLength ] = next;\n\tconst stackDepth = stack.length;\n\tconst leadingTextStart = startOffset > offset ? offset : null;\n\tif ( name && ! conversionMap[ name ] ) {\n\t\taddText();\n\t\treturn false;\n\t}\n\tswitch ( tokenType ) {\n\t\tcase 'no-more-tokens':\n\t\t\tif ( stackDepth !== 0 ) {\n\t\t\t\tconst { leadingTextStart: stackLeadingText, tokenStart } =\n\t\t\t\t\tstack.pop();\n\t\t\t\toutput.push( indoc.substr( stackLeadingText, tokenStart ) );\n\t\t\t}\n\t\t\taddText();\n\t\t\treturn false;\n\n\t\tcase 'self-closed':\n\t\t\tif ( 0 === stackDepth ) {\n\t\t\t\tif ( null !== leadingTextStart ) {\n\t\t\t\t\toutput.push(\n\t\t\t\t\t\tindoc.substr(\n\t\t\t\t\t\t\tleadingTextStart,\n\t\t\t\t\t\t\tstartOffset - leadingTextStart\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\toutput.push( conversionMap[ name ] );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we found an inner element.\n\t\t\taddChild(\n\t\t\t\tcreateFrame( conversionMap[ name ], startOffset, tokenLength )\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'opener':\n\t\t\tstack.push(\n\t\t\t\tcreateFrame(\n\t\t\t\t\tconversionMap[ name ],\n\t\t\t\t\tstartOffset,\n\t\t\t\t\ttokenLength,\n\t\t\t\t\tstartOffset + tokenLength,\n\t\t\t\t\tleadingTextStart\n\t\t\t\t)\n\t\t\t);\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tcase 'closer':\n\t\t\t// If we're not nesting then this is easy - close the block.\n\t\t\tif ( 1 === stackDepth ) {\n\t\t\t\tcloseOuterElement( startOffset );\n\t\t\t\toffset = startOffset + tokenLength;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Otherwise we're nested and we have to close out the current\n\t\t\t// block and add it as a innerBlock to the parent.\n\t\t\tconst stackTop = stack.pop();\n\t\t\tconst text = indoc.substr(\n\t\t\t\tstackTop.prevOffset,\n\t\t\t\tstartOffset - stackTop.prevOffset\n\t\t\t);\n\t\t\tstackTop.children.push( text );\n\t\t\tstackTop.prevOffset = startOffset + tokenLength;\n\t\t\tconst frame = createFrame(\n\t\t\t\tstackTop.element,\n\t\t\t\tstackTop.tokenStart,\n\t\t\t\tstackTop.tokenLength,\n\t\t\t\tstartOffset + tokenLength\n\t\t\t);\n\t\t\tframe.children = stackTop.children;\n\t\t\taddChild( frame );\n\t\t\toffset = startOffset + tokenLength;\n\t\t\treturn true;\n\n\t\tdefault:\n\t\t\taddText();\n\t\t\treturn false;\n\t}\n}\n\n/**\n * Grabs the next token match in the string and returns it's details.\n *\n * @private\n *\n * @return An array of details for the token matched.\n */\nfunction nextToken(): TokenResult {\n\tconst matches = tokenizer.exec( indoc );\n\t// We have no more tokens.\n\tif ( null === matches ) {\n\t\treturn [ 'no-more-tokens' ];\n\t}\n\tconst startedAt = matches.index;\n\tconst [ match, isClosing, name, isSelfClosed ] = matches;\n\tconst length = match.length;\n\tif ( isSelfClosed ) {\n\t\treturn [ 'self-closed', name, startedAt, length ];\n\t}\n\tif ( isClosing ) {\n\t\treturn [ 'closer', name, startedAt, length ];\n\t}\n\treturn [ 'opener', name, startedAt, length ];\n}\n\n/**\n * Pushes text extracted from the indoc string to the output stack given the\n * current rawLength value and offset (if rawLength is provided ) or the\n * indoc.length and offset.\n *\n * @private\n */\nfunction addText(): void {\n\tconst length = indoc.length - offset;\n\tif ( 0 === length ) {\n\t\treturn;\n\t}\n\toutput.push( indoc.substr( offset, length ) );\n}\n\n/**\n * Pushes a child element to the associated parent element's children for the\n * parent currently active in the stack.\n *\n * @private\n *\n * @param {Frame} frame The Frame containing the child element and it's\n * token information.\n */\nfunction addChild( frame: Frame ): void {\n\tconst { element, tokenStart, tokenLength, prevOffset, children } = frame;\n\tconst parent = stack[ stack.length - 1 ];\n\tconst text = indoc.substr(\n\t\tparent.prevOffset,\n\t\ttokenStart - parent.prevOffset\n\t);\n\n\tif ( text ) {\n\t\tparent.children.push( text );\n\t}\n\n\tparent.children.push( cloneElement( element, null, ...children ) );\n\tparent.prevOffset = prevOffset ? prevOffset : tokenStart + tokenLength;\n}\n\n/**\n * This is called for closing tags. It creates the element currently active in\n * the stack.\n *\n * @private\n *\n * @param {number} endOffset Offset at which the closing tag for the element\n * begins in the string. If this is greater than the\n * prevOffset attached to the element, then this\n * helps capture any remaining nested text nodes in\n * the element.\n */\nfunction closeOuterElement( endOffset: number ): void {\n\tconst { element, leadingTextStart, prevOffset, tokenStart, children } =\n\t\tstack.pop();\n\n\tconst text = endOffset\n\t\t? indoc.substr( prevOffset, endOffset - prevOffset )\n\t\t: indoc.substr( prevOffset );\n\n\tif ( text ) {\n\t\tchildren.push( text );\n\t}\n\n\tif ( null !== leadingTextStart ) {\n\t\toutput.push(\n\t\t\tindoc.substr( leadingTextStart, tokenStart - leadingTextStart )\n\t\t);\n\t}\n\n\toutput.push( cloneElement( element, null, ...children ) );\n}\n\nexport default createInterpolateElement;\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -2,6 +2,7 @@
2
2
  * Internal dependencies
3
3
  */
4
4
  import { type Element as ReactElement } from './react';
5
+ import type { ConversionMap, InterpolationInput, InterpolationString } from './types';
5
6
  /**
6
7
  * This function creates an interpolated element from a passed in string with
7
8
  * specific tags matching how the string should be converted to an element via
@@ -29,6 +30,6 @@ import { type Element as ReactElement } from './react';
29
30
  * @throws {TypeError}
30
31
  * @return A wp element.
31
32
  */
32
- declare const createInterpolateElement: (interpolatedString: string, conversionMap: Record<string, ReactElement>) => ReactElement;
33
+ declare function createInterpolateElement<Input extends InterpolationInput>(interpolatedString: Input, conversionMap: ConversionMap<InterpolationString<Input>>): ReactElement;
33
34
  export default createInterpolateElement;
34
35
  //# sourceMappingURL=create-interpolate-element.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-interpolate-element.d.ts","sourceRoot":"","sources":["../src/create-interpolate-element.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAKN,KAAK,OAAO,IAAI,YAAY,EAC5B,MAAM,SAAS,CAAC;AA4FjB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,QAAA,MAAM,wBAAwB,GAC7B,oBAAoB,MAAM,EAC1B,eAAe,MAAM,CAAE,MAAM,EAAE,YAAY,CAAE,KAC3C,YAiBF,CAAC;AAwOF,eAAe,wBAAwB,CAAC"}
1
+ {"version":3,"file":"create-interpolate-element.d.ts","sourceRoot":"","sources":["../src/create-interpolate-element.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAKN,KAAK,OAAO,IAAI,YAAY,EAC5B,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EACX,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,MAAM,SAAS,CAAC;AA4FjB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,iBAAS,wBAAwB,CAAE,KAAK,SAAS,kBAAkB,EAClE,kBAAkB,EAAE,KAAK,EACzB,aAAa,EAAE,aAAa,CAAE,mBAAmB,CAAE,KAAK,CAAE,CAAE,GAC1D,YAAY,CAiBd;AAwOD,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { ReactElement } from 'react';
2
+ /**
3
+ * Mirrors `TransformedText` from @wordpress/i18n.
4
+ * We don't import directly to avoid a circular dependency.
5
+ */
6
+ type TransformedText<T extends string> = string & {
7
+ readonly __transformedText: T;
8
+ };
9
+ /**
10
+ * The input that can be passed to `createInterpolateElement`.
11
+ */
12
+ export type InterpolationInput = string | TransformedText<string>;
13
+ /**
14
+ * The literal string extracted from the input.
15
+ */
16
+ export type InterpolationString<Input> = Input extends TransformedText<infer Text> ? Text : Input;
17
+ /**
18
+ * Recursively trims trailing spaces from a string type.
19
+ * Matches the runtime tokenizer's `\s*` before the closing `>` or `/>`.
20
+ */
21
+ type TrimTrailingSpaces<S extends string> = S extends `${infer Rest} ` ? TrimTrailingSpaces<Rest> : S;
22
+ /**
23
+ * Helper type to extract tag name and handle closing/self-closing indicators.
24
+ * Filters out tags with spaces as they won't be parsed by the tokenizer.
25
+ */
26
+ type ExtractTagName<T extends string> = T extends `/${string}` ? never : TrimTrailingSpaces<T> extends infer Name extends string ? Name extends '' ? never : Name extends `${string} ${string}` ? never : Name extends `${infer Base}/` ? Base : Name : never;
27
+ /**
28
+ * Utility type to extract all tag names from a template literal string.
29
+ * Only handles simple tags without attributes, matching the runtime tokenizer.
30
+ */
31
+ export type ExtractTags<T extends string> = T extends `${string}<${infer Tag}>${infer After}` ? ExtractTagName<Tag> | ExtractTags<After> : never;
32
+ /**
33
+ * Utility type to create a conversion map that:
34
+ * - Makes extracted tag keys optional
35
+ * - Only allows properties for tags found in the template literal
36
+ */
37
+ export type ConversionMap<T extends string> = Partial<Record<ExtractTags<T>, ReactElement>>;
38
+ export {};
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE1C;;;GAGG;AACH,KAAK,eAAe,CAAE,CAAC,SAAS,MAAM,IAAK,MAAM,GAAG;IACnD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,CAAC;CAC9B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,eAAe,CAAE,MAAM,CAAE,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAE,KAAK,IAAK,KAAK,SAAS,eAAe,CACvE,MAAM,IAAI,CACV,GACE,IAAI,GACJ,KAAK,CAAC;AAET;;;GAGG;AACH,KAAK,kBAAkB,CAAE,CAAC,SAAS,MAAM,IAAK,CAAC,SAAS,GAAI,MAAM,IAAK,GAAG,GACvE,kBAAkB,CAAE,IAAI,CAAE,GAC1B,CAAC,CAAC;AAEL;;;GAGG;AACH,KAAK,cAAc,CAAE,CAAC,SAAS,MAAM,IAEpC,CAAC,SAAS,IAAK,MAAO,EAAE,GACrB,KAAK,GACL,kBAAkB,CAAE,CAAC,CAAE,SAAS,MAAM,IAAI,SAAS,MAAM,GACzD,IAAI,SAAS,EAAE,GACd,KAAK,GACL,IAAI,SAAS,GAAI,MAAO,IAAK,MAAO,EAAE,GACtC,KAAK,GACL,IAAI,SAAS,GAAI,MAAM,IAAK,GAAG,GAC/B,IAAI,GACJ,IAAI,GACL,KAAK,CAAC;AAEV;;;GAGG;AACH,MAAM,MAAM,WAAW,CAAE,CAAC,SAAS,MAAM,IACxC,CAAC,SAAS,GAAI,MAAO,IAAK,MAAM,GAAI,IAAK,MAAM,KAAM,EAAE,GACpD,cAAc,CAAE,GAAG,CAAE,GAAG,WAAW,CAAE,KAAK,CAAE,GAC5C,KAAK,CAAC;AAEV;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAE,CAAC,SAAS,MAAM,IAAK,OAAO,CACtD,MAAM,CAAE,WAAW,CAAE,CAAC,CAAE,EAAE,YAAY,CAAE,CACxC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/element",
3
- "version": "6.42.0",
3
+ "version": "6.44.1-next.v.202604091042.0+668146787",
4
4
  "description": "Element React module for WordPress.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -47,7 +47,7 @@
47
47
  "dependencies": {
48
48
  "@types/react": "^18.3.27",
49
49
  "@types/react-dom": "^18.3.1",
50
- "@wordpress/escape-html": "^3.42.0",
50
+ "@wordpress/escape-html": "^3.43.1-next.v.202604091042.0+668146787",
51
51
  "change-case": "^4.1.2",
52
52
  "is-plain-object": "^5.0.0",
53
53
  "react": "^18.3.0",
@@ -56,5 +56,5 @@
56
56
  "publishConfig": {
57
57
  "access": "public"
58
58
  },
59
- "gitHead": "c20787b1778ae64c2db65643b1c236309d68e6ba"
59
+ "gitHead": "73606df74f1c38a084bfa5db97205259ef817593"
60
60
  }
@@ -8,6 +8,11 @@ import {
8
8
  isValidElement,
9
9
  type Element as ReactElement,
10
10
  } from './react';
11
+ import type {
12
+ ConversionMap,
13
+ InterpolationInput,
14
+ InterpolationString,
15
+ } from './types';
11
16
 
12
17
  let indoc: string;
13
18
  let offset: number;
@@ -126,10 +131,10 @@ function createFrame(
126
131
  * @throws {TypeError}
127
132
  * @return A wp element.
128
133
  */
129
- const createInterpolateElement = (
130
- interpolatedString: string,
131
- conversionMap: Record< string, ReactElement >
132
- ): ReactElement => {
134
+ function createInterpolateElement< Input extends InterpolationInput >(
135
+ interpolatedString: Input,
136
+ conversionMap: ConversionMap< InterpolationString< Input > >
137
+ ): ReactElement {
133
138
  indoc = interpolatedString;
134
139
  offset = 0;
135
140
  output = [];
@@ -146,7 +151,7 @@ const createInterpolateElement = (
146
151
  // twiddle our thumbs
147
152
  } while ( proceed( conversionMap ) );
148
153
  return createElement( Fragment, null, ...output );
149
- };
154
+ }
150
155
 
151
156
  /**
152
157
  * Validate conversion map.
@@ -8,6 +8,11 @@ import { render } from '@testing-library/react';
8
8
  */
9
9
  import { createElement, Fragment, Component } from '../react';
10
10
  import createInterpolateElement from '../create-interpolate-element';
11
+ import type {
12
+ ExtractTags,
13
+ InterpolationInput,
14
+ InterpolationString,
15
+ } from '../types';
11
16
 
12
17
  describe( 'createInterpolateElement', () => {
13
18
  it( 'throws an error when there is no conversion map', () => {
@@ -26,10 +31,11 @@ describe( 'createInterpolateElement', () => {
26
31
  it( 'throws an error when there is an invalid conversion map', () => {
27
32
  const testString = 'This is a <someValue/> string';
28
33
  expect( () =>
29
- createInterpolateElement( testString, [
30
- 'someValue',
31
- { value: 10 },
32
- ] )
34
+ createInterpolateElement(
35
+ testString,
36
+ // @ts-expect-error - Invalid argument type
37
+ [ 'someValue', { value: 10 } ]
38
+ )
33
39
  ).toThrow( TypeError );
34
40
  } );
35
41
  it(
@@ -39,7 +45,8 @@ describe( 'createInterpolateElement', () => {
39
45
  const testString = 'This is a <item /> string and <somethingElse/>';
40
46
  expect( () =>
41
47
  createInterpolateElement( testString, {
42
- someValue: <em />,
48
+ item: <em />,
49
+ // @ts-expect-error - Invalid type for somethingElse
43
50
  somethingElse: 10,
44
51
  } )
45
52
  ).toThrow( TypeError );
@@ -53,6 +60,7 @@ describe( 'createInterpolateElement', () => {
53
60
  const expectedElement = <>{ testString }</>;
54
61
  expect(
55
62
  createInterpolateElement( testString, {
63
+ // @ts-expect-error - Unknown tag
56
64
  someValue: <strong />,
57
65
  } )
58
66
  ).toEqual( expectedElement );
@@ -162,8 +170,8 @@ describe( 'createInterpolateElement', () => {
162
170
  } );
163
171
  it( 'returns expected output for complex replacement', () => {
164
172
  class TestComponent extends Component {
165
- render( props ) {
166
- return <div { ...props } />;
173
+ render() {
174
+ return <div { ...this.props } />;
167
175
  }
168
176
  }
169
177
  const testString =
@@ -217,6 +225,37 @@ describe( 'createInterpolateElement', () => {
217
225
  expect( container ).toContainHTML( '<strong>string!</strong>' );
218
226
  expect( container ).not.toContainHTML( '<em>' );
219
227
  } );
228
+ it( 'extracts all tag names from a template literal string', () => {
229
+ // Type-level test: verify ExtractTags extracts all tags correctly.
230
+ type Tags = ExtractTags< '<a>link</a> and <b>bold <c>nested</c></b>' >;
231
+ const tags: Tags[] = [ 'a', 'b', 'c' ];
232
+ expect( tags ).toHaveLength( 3 );
233
+ } );
234
+ it( 'extracts tags from a TransformedText input', () => {
235
+ // Type-level test: verify InterpolationString unwraps TransformedText.
236
+ type Text = InterpolationString<
237
+ string & {
238
+ readonly __transformedText: '<a>link</a> and <em>emphasis</em>';
239
+ }
240
+ >;
241
+ type Tags = ExtractTags< Text >;
242
+ const tags: Tags[] = [ 'a', 'em' ];
243
+ expect( tags ).toHaveLength( 2 );
244
+ } );
245
+ it( 'extracts tags from a sprintf (TransformedText) input', () => {
246
+ // Type-level test: sprintf returns TransformedText, so
247
+ // InterpolationString unwraps it for tag inference.
248
+ type SprintfResult = string & {
249
+ readonly __transformedText: '<Name>%1$s</Name> wrote <Link>%2$s</Link>';
250
+ };
251
+ const _check: InterpolationInput = '' as SprintfResult;
252
+ void _check;
253
+
254
+ type Text = InterpolationString< SprintfResult >;
255
+ type Tags = ExtractTags< Text >;
256
+ const tags: Tags[] = [ 'Name', 'Link' ];
257
+ expect( tags ).toHaveLength( 2 );
258
+ } );
220
259
  it( 'handles parsing emojii correctly', () => {
221
260
  const testString = '👳‍♀️<icon>🚨🤷‍♂️⛈️fully</icon> here';
222
261
  const expectedElement = createElement(
package/src/types.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { ReactElement } from 'react';
2
+
3
+ /**
4
+ * Mirrors `TransformedText` from @wordpress/i18n.
5
+ * We don't import directly to avoid a circular dependency.
6
+ */
7
+ type TransformedText< T extends string > = string & {
8
+ readonly __transformedText: T;
9
+ };
10
+
11
+ /**
12
+ * The input that can be passed to `createInterpolateElement`.
13
+ */
14
+ export type InterpolationInput = string | TransformedText< string >;
15
+
16
+ /**
17
+ * The literal string extracted from the input.
18
+ */
19
+ export type InterpolationString< Input > = Input extends TransformedText<
20
+ infer Text
21
+ >
22
+ ? Text
23
+ : Input;
24
+
25
+ /**
26
+ * Recursively trims trailing spaces from a string type.
27
+ * Matches the runtime tokenizer's `\s*` before the closing `>` or `/>`.
28
+ */
29
+ type TrimTrailingSpaces< S extends string > = S extends `${ infer Rest } `
30
+ ? TrimTrailingSpaces< Rest >
31
+ : S;
32
+
33
+ /**
34
+ * Helper type to extract tag name and handle closing/self-closing indicators.
35
+ * Filters out tags with spaces as they won't be parsed by the tokenizer.
36
+ */
37
+ type ExtractTagName< T extends string > =
38
+ // Skip closing tags like "/div"
39
+ T extends `/${ string }`
40
+ ? never
41
+ : TrimTrailingSpaces< T > extends infer Name extends string
42
+ ? Name extends ''
43
+ ? never // Empty tag name
44
+ : Name extends `${ string } ${ string }`
45
+ ? never // Skip tags with inner spaces like "spaced token"
46
+ : Name extends `${ infer Base }/`
47
+ ? Base // Self-closing tags like "br/"
48
+ : Name // Regular opening tags like "div"
49
+ : never;
50
+
51
+ /**
52
+ * Utility type to extract all tag names from a template literal string.
53
+ * Only handles simple tags without attributes, matching the runtime tokenizer.
54
+ */
55
+ export type ExtractTags< T extends string > =
56
+ T extends `${ string }<${ infer Tag }>${ infer After }`
57
+ ? ExtractTagName< Tag > | ExtractTags< After >
58
+ : never;
59
+
60
+ /**
61
+ * Utility type to create a conversion map that:
62
+ * - Makes extracted tag keys optional
63
+ * - Only allows properties for tags found in the template literal
64
+ */
65
+ export type ConversionMap< T extends string > = Partial<
66
+ Record< ExtractTags< T >, ReactElement >
67
+ >;