@wordpress/element 6.42.0 → 6.43.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 +2 -0
- package/README.md +2 -2
- package/build/create-interpolate-element.cjs +2 -2
- package/build/create-interpolate-element.cjs.map +2 -2
- package/build/types.cjs +19 -0
- package/build/types.cjs.map +7 -0
- package/build-module/create-interpolate-element.mjs +2 -2
- package/build-module/create-interpolate-element.mjs.map +2 -2
- package/build-module/types.mjs +1 -0
- package/build-module/types.mjs.map +7 -0
- package/build-types/create-interpolate-element.d.ts +2 -1
- package/build-types/create-interpolate-element.d.ts.map +1 -1
- package/build-types/types.d.ts +40 -0
- package/build-types/types.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/create-interpolate-element.ts +10 -5
- package/src/test/{create-interpolate-element.js → create-interpolate-element.tsx} +28 -7
- package/src/types.ts +68 -0
package/CHANGELOG.md
CHANGED
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_ `
|
|
113
|
-
- _conversionMap_ `
|
|
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
|
-
|
|
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": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAMO;
|
|
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
|
}
|
package/build/types.cjs
ADDED
|
@@ -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 * This type is used by @wordpress/i18n to make sprintf type-safe.\n *\n * We don't want to import from there directly to avoid a circular dependency.\n */\ntype TranslatableText< T extends string > = string & {\n\treadonly __translatableText: T;\n};\n\n/**\n * The input that can be passed to `createInterpolateElement`.\n */\nexport type InterpolationInput = string | TranslatableText< string >;\n\n/**\n * The literal string extracted from the input.\n */\nexport type InterpolationString< Input > = Input extends TranslatableText<\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
|
-
|
|
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": ";AAGA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEM;
|
|
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
|
|
@@ -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
|
|
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,
|
|
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,40 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* This type is used by @wordpress/i18n to make sprintf type-safe.
|
|
4
|
+
*
|
|
5
|
+
* We don't want to import from there directly to avoid a circular dependency.
|
|
6
|
+
*/
|
|
7
|
+
type TranslatableText<T extends string> = string & {
|
|
8
|
+
readonly __translatableText: T;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The input that can be passed to `createInterpolateElement`.
|
|
12
|
+
*/
|
|
13
|
+
export type InterpolationInput = string | TranslatableText<string>;
|
|
14
|
+
/**
|
|
15
|
+
* The literal string extracted from the input.
|
|
16
|
+
*/
|
|
17
|
+
export type InterpolationString<Input> = Input extends TranslatableText<infer Text> ? Text : Input;
|
|
18
|
+
/**
|
|
19
|
+
* Recursively trims trailing spaces from a string type.
|
|
20
|
+
* Matches the runtime tokenizer's `\s*` before the closing `>` or `/>`.
|
|
21
|
+
*/
|
|
22
|
+
type TrimTrailingSpaces<S extends string> = S extends `${infer Rest} ` ? TrimTrailingSpaces<Rest> : S;
|
|
23
|
+
/**
|
|
24
|
+
* Helper type to extract tag name and handle closing/self-closing indicators.
|
|
25
|
+
* Filters out tags with spaces as they won't be parsed by the tokenizer.
|
|
26
|
+
*/
|
|
27
|
+
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;
|
|
28
|
+
/**
|
|
29
|
+
* Utility type to extract all tag names from a template literal string.
|
|
30
|
+
* Only handles simple tags without attributes, matching the runtime tokenizer.
|
|
31
|
+
*/
|
|
32
|
+
export type ExtractTags<T extends string> = T extends `${string}<${infer Tag}>${infer After}` ? ExtractTagName<Tag> | ExtractTags<After> : never;
|
|
33
|
+
/**
|
|
34
|
+
* Utility type to create a conversion map that:
|
|
35
|
+
* - Makes extracted tag keys optional
|
|
36
|
+
* - Only allows properties for tags found in the template literal
|
|
37
|
+
*/
|
|
38
|
+
export type ConversionMap<T extends string> = Partial<Record<ExtractTags<T>, ReactElement>>;
|
|
39
|
+
export {};
|
|
40
|
+
//# 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;;;;GAIG;AACH,KAAK,gBAAgB,CAAE,CAAC,SAAS,MAAM,IAAK,MAAM,GAAG;IACpD,QAAQ,CAAC,kBAAkB,EAAE,CAAC,CAAC;CAC/B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,gBAAgB,CAAE,MAAM,CAAE,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,mBAAmB,CAAE,KAAK,IAAK,KAAK,SAAS,gBAAgB,CACxE,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.
|
|
3
|
+
"version": "6.43.0",
|
|
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.
|
|
50
|
+
"@wordpress/escape-html": "^3.43.0",
|
|
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": "
|
|
59
|
+
"gitHead": "2cea90674d11aa521ec3f71652fb3a6a4c383969"
|
|
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
|
-
|
|
130
|
-
interpolatedString:
|
|
131
|
-
conversionMap:
|
|
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,7 @@ 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 { ExtractTags, InterpolationString } from '../types';
|
|
11
12
|
|
|
12
13
|
describe( 'createInterpolateElement', () => {
|
|
13
14
|
it( 'throws an error when there is no conversion map', () => {
|
|
@@ -26,10 +27,11 @@ describe( 'createInterpolateElement', () => {
|
|
|
26
27
|
it( 'throws an error when there is an invalid conversion map', () => {
|
|
27
28
|
const testString = 'This is a <someValue/> string';
|
|
28
29
|
expect( () =>
|
|
29
|
-
createInterpolateElement(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
createInterpolateElement(
|
|
31
|
+
testString,
|
|
32
|
+
// @ts-expect-error - Invalid argument type
|
|
33
|
+
[ 'someValue', { value: 10 } ]
|
|
34
|
+
)
|
|
33
35
|
).toThrow( TypeError );
|
|
34
36
|
} );
|
|
35
37
|
it(
|
|
@@ -39,7 +41,8 @@ describe( 'createInterpolateElement', () => {
|
|
|
39
41
|
const testString = 'This is a <item /> string and <somethingElse/>';
|
|
40
42
|
expect( () =>
|
|
41
43
|
createInterpolateElement( testString, {
|
|
42
|
-
|
|
44
|
+
item: <em />,
|
|
45
|
+
// @ts-expect-error - Invalid type for somethingElse
|
|
43
46
|
somethingElse: 10,
|
|
44
47
|
} )
|
|
45
48
|
).toThrow( TypeError );
|
|
@@ -53,6 +56,7 @@ describe( 'createInterpolateElement', () => {
|
|
|
53
56
|
const expectedElement = <>{ testString }</>;
|
|
54
57
|
expect(
|
|
55
58
|
createInterpolateElement( testString, {
|
|
59
|
+
// @ts-expect-error - Unknown tag
|
|
56
60
|
someValue: <strong />,
|
|
57
61
|
} )
|
|
58
62
|
).toEqual( expectedElement );
|
|
@@ -162,8 +166,8 @@ describe( 'createInterpolateElement', () => {
|
|
|
162
166
|
} );
|
|
163
167
|
it( 'returns expected output for complex replacement', () => {
|
|
164
168
|
class TestComponent extends Component {
|
|
165
|
-
render(
|
|
166
|
-
return <div { ...props } />;
|
|
169
|
+
render() {
|
|
170
|
+
return <div { ...this.props } />;
|
|
167
171
|
}
|
|
168
172
|
}
|
|
169
173
|
const testString =
|
|
@@ -217,6 +221,23 @@ describe( 'createInterpolateElement', () => {
|
|
|
217
221
|
expect( container ).toContainHTML( '<strong>string!</strong>' );
|
|
218
222
|
expect( container ).not.toContainHTML( '<em>' );
|
|
219
223
|
} );
|
|
224
|
+
it( 'extracts all tag names from a template literal string', () => {
|
|
225
|
+
// Type-level test: verify ExtractTags extracts all tags correctly.
|
|
226
|
+
type Tags = ExtractTags< '<a>link</a> and <b>bold <c>nested</c></b>' >;
|
|
227
|
+
const tags: Tags[] = [ 'a', 'b', 'c' ];
|
|
228
|
+
expect( tags ).toHaveLength( 3 );
|
|
229
|
+
} );
|
|
230
|
+
it( 'extracts tags from a TranslatableText input', () => {
|
|
231
|
+
// Type-level test: verify InterpolationString unwraps TranslatableText.
|
|
232
|
+
type Text = InterpolationString<
|
|
233
|
+
string & {
|
|
234
|
+
readonly __translatableText: '<a>link</a> and <em>emphasis</em>';
|
|
235
|
+
}
|
|
236
|
+
>;
|
|
237
|
+
type Tags = ExtractTags< Text >;
|
|
238
|
+
const tags: Tags[] = [ 'a', 'em' ];
|
|
239
|
+
expect( tags ).toHaveLength( 2 );
|
|
240
|
+
} );
|
|
220
241
|
it( 'handles parsing emojii correctly', () => {
|
|
221
242
|
const testString = '👳♀️<icon>🚨🤷♂️⛈️fully</icon> here';
|
|
222
243
|
const expectedElement = createElement(
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This type is used by @wordpress/i18n to make sprintf type-safe.
|
|
5
|
+
*
|
|
6
|
+
* We don't want to import from there directly to avoid a circular dependency.
|
|
7
|
+
*/
|
|
8
|
+
type TranslatableText< T extends string > = string & {
|
|
9
|
+
readonly __translatableText: T;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The input that can be passed to `createInterpolateElement`.
|
|
14
|
+
*/
|
|
15
|
+
export type InterpolationInput = string | TranslatableText< string >;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The literal string extracted from the input.
|
|
19
|
+
*/
|
|
20
|
+
export type InterpolationString< Input > = Input extends TranslatableText<
|
|
21
|
+
infer Text
|
|
22
|
+
>
|
|
23
|
+
? Text
|
|
24
|
+
: Input;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Recursively trims trailing spaces from a string type.
|
|
28
|
+
* Matches the runtime tokenizer's `\s*` before the closing `>` or `/>`.
|
|
29
|
+
*/
|
|
30
|
+
type TrimTrailingSpaces< S extends string > = S extends `${ infer Rest } `
|
|
31
|
+
? TrimTrailingSpaces< Rest >
|
|
32
|
+
: S;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Helper type to extract tag name and handle closing/self-closing indicators.
|
|
36
|
+
* Filters out tags with spaces as they won't be parsed by the tokenizer.
|
|
37
|
+
*/
|
|
38
|
+
type ExtractTagName< T extends string > =
|
|
39
|
+
// Skip closing tags like "/div"
|
|
40
|
+
T extends `/${ string }`
|
|
41
|
+
? never
|
|
42
|
+
: TrimTrailingSpaces< T > extends infer Name extends string
|
|
43
|
+
? Name extends ''
|
|
44
|
+
? never // Empty tag name
|
|
45
|
+
: Name extends `${ string } ${ string }`
|
|
46
|
+
? never // Skip tags with inner spaces like "spaced token"
|
|
47
|
+
: Name extends `${ infer Base }/`
|
|
48
|
+
? Base // Self-closing tags like "br/"
|
|
49
|
+
: Name // Regular opening tags like "div"
|
|
50
|
+
: never;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Utility type to extract all tag names from a template literal string.
|
|
54
|
+
* Only handles simple tags without attributes, matching the runtime tokenizer.
|
|
55
|
+
*/
|
|
56
|
+
export type ExtractTags< T extends string > =
|
|
57
|
+
T extends `${ string }<${ infer Tag }>${ infer After }`
|
|
58
|
+
? ExtractTagName< Tag > | ExtractTags< After >
|
|
59
|
+
: never;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Utility type to create a conversion map that:
|
|
63
|
+
* - Makes extracted tag keys optional
|
|
64
|
+
* - Only allows properties for tags found in the template literal
|
|
65
|
+
*/
|
|
66
|
+
export type ConversionMap< T extends string > = Partial<
|
|
67
|
+
Record< ExtractTags< T >, ReactElement >
|
|
68
|
+
>;
|