create-html-element 5.0.0 → 6.0.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.
Files changed (4) hide show
  1. package/index.d.ts +56 -2
  2. package/index.js +48 -15
  3. package/package.json +8 -8
  4. package/readme.md +34 -2
package/index.d.ts CHANGED
@@ -29,7 +29,43 @@ export type TextOptions = {
29
29
  readonly text?: string;
30
30
  };
31
31
 
32
- export type Options = BaseOptions & MergeExclusive<HtmlOptions, TextOptions>;
32
+ export type ChildrenOptions = {
33
+ /**
34
+ HTML tag children.
35
+
36
+ Strings are escaped, objects are passed to `createHtmlElement`.
37
+
38
+ This option is mutually exclusive with the `html` and `text` options.
39
+
40
+ @example
41
+ ```
42
+ import createHtmlElement from 'create-html-element';
43
+
44
+ createHtmlElement({
45
+ name: 'div',
46
+ children: [
47
+ '<unsafe>',
48
+ {
49
+ name: 'iframe',
50
+ attributes: {
51
+ src: 'https://example.com'
52
+ }
53
+ },
54
+ {
55
+ name: 'span',
56
+ text: 'Label here <em>plz</em>'
57
+ }
58
+ ]
59
+ });
60
+ //=> '<div>&lt;unsafe&gt;<iframe src="https://example.com"></iframe><span>Label here &lt;em&gt;plz&lt;/em&gt;</span></div>'
61
+ ```
62
+ */
63
+ readonly children?: readonly Child[];
64
+ };
65
+
66
+ export type Child = string | (Options & {readonly length?: never});
67
+
68
+ export type Options = BaseOptions & MergeExclusive<HtmlOptions, MergeExclusive<TextOptions, ChildrenOptions>>;
33
69
 
34
70
  /**
35
71
  Create a HTML element string.
@@ -56,8 +92,26 @@ createHtmlElement({
56
92
 
57
93
  createHtmlElement({text: 'Hello <em>World</em>'});
58
94
  //=> '<div>Hello &lt;em&gt;World&lt;/em&gt;</div>'
95
+
96
+ createHtmlElement({
97
+ name: 'div',
98
+ children: [
99
+ '<unsafe>',
100
+ {
101
+ name: 'iframe',
102
+ attributes: {
103
+ src: 'https://example.com'
104
+ }
105
+ },
106
+ {
107
+ name: 'span',
108
+ text: 'Label here <em>plz</em>'
109
+ }
110
+ ]
111
+ });
112
+ //=> '<div>&lt;unsafe&gt;<iframe src="https://example.com"></iframe><span>Label here &lt;em&gt;plz&lt;/em&gt;</span></div>'
59
113
  ```
60
114
  */
61
115
  export default function createHtmlElement(options?: Options): string;
62
116
 
63
- export {HTMLAttributes} from 'stringify-attributes';
117
+ export type {HTMLAttributes} from 'stringify-attributes';
package/index.js CHANGED
@@ -4,24 +4,57 @@ import {htmlEscape} from 'escape-goat';
4
4
 
5
5
  const voidHtmlTags = new Set(voidHtmlTagsArray);
6
6
 
7
- export default function createHtmlElement(
8
- {
9
- name = 'div',
10
- attributes = {},
11
- html = '',
12
- text,
13
- } = {},
14
- ) {
15
- if (html && text) {
16
- throw new Error('The `html` and `text` options are mutually exclusive');
7
+ // Matches a valid HTML tag name (a letter followed by letters, digits, hyphens, underscores, or periods).
8
+ const tagNamePattern = /^[a-z][\w\-.]*$/iv;
9
+
10
+ export default function createHtmlElement({
11
+ name = 'div',
12
+ attributes = {},
13
+ html,
14
+ text,
15
+ children,
16
+ } = {}) {
17
+ if (!tagNamePattern.test(name)) {
18
+ throw new Error(`Invalid tag name: ${JSON.stringify(name)}`);
19
+ }
20
+
21
+ const hasHtml = html !== undefined;
22
+ const hasText = text !== undefined;
23
+ const hasChildren = children !== undefined;
24
+
25
+ if ([hasHtml, hasText, hasChildren].filter(Boolean).length > 1) {
26
+ throw new Error('The `html`, `text`, and `children` options are mutually exclusive');
27
+ }
28
+
29
+ if (hasChildren && !Array.isArray(children)) {
30
+ throw new TypeError('The `children` option must be an array');
17
31
  }
18
32
 
19
- const content = text ? htmlEscape(text) : html;
20
- let result = `<${name}${stringifyAttributes(attributes)}>`;
33
+ const openingTag = `<${name}${stringifyAttributes(attributes)}>`;
34
+
35
+ if (voidHtmlTags.has(name)) {
36
+ return openingTag;
37
+ }
38
+
39
+ let content = '';
40
+
41
+ if (hasChildren) {
42
+ content = children.map(child => {
43
+ if (typeof child === 'string') {
44
+ return htmlEscape(child);
45
+ }
46
+
47
+ if (typeof child === 'object' && child !== null && !Array.isArray(child)) {
48
+ return createHtmlElement(child);
49
+ }
21
50
 
22
- if (!voidHtmlTags.has(name)) {
23
- result += `${content}</${name}>`;
51
+ throw new TypeError('Children must be strings or objects');
52
+ }).join('');
53
+ } else if (hasText) {
54
+ content = htmlEscape(text);
55
+ } else if (hasHtml) {
56
+ content = html;
24
57
  }
25
58
 
26
- return result;
59
+ return `${openingTag}${content}</${name}>`;
27
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-html-element",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Create a HTML element string",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/create-html-element",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "sideEffects": false,
19
19
  "engines": {
20
- "node": "^18.20.0 || >=20.10.0"
20
+ "node": ">=22"
21
21
  },
22
22
  "scripts": {
23
23
  "test": "xo && ava && tsd"
@@ -37,13 +37,13 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "escape-goat": "^4.0.0",
40
- "html-tags": "^4.0.0",
41
- "stringify-attributes": "^4.0.0",
42
- "type-fest": "^4.27.0"
40
+ "html-tags": "^5.1.0",
41
+ "stringify-attributes": "^5.0.0",
42
+ "type-fest": "^5.7.0"
43
43
  },
44
44
  "devDependencies": {
45
- "ava": "^6.2.0",
46
- "tsd": "^0.31.2",
47
- "xo": "^0.59.3"
45
+ "ava": "^8.0.1",
46
+ "tsd": "^0.33.0",
47
+ "xo": "^3.0.2"
48
48
  }
49
49
  }
package/readme.md CHANGED
@@ -58,13 +58,45 @@ HTML tag attributes.
58
58
 
59
59
  HTML tag value in unescaped HTML.
60
60
 
61
- This option is mutually exclusive with the `text` option.
61
+ This option is mutually exclusive with the `text` and `children` options.
62
62
 
63
63
  ##### text
64
64
 
65
65
  HTML tag value in escaped HTML.
66
66
 
67
- This option is mutually exclusive with the `html` option.
67
+ This option is mutually exclusive with the `html` and `children` options.
68
+
69
+ ##### children
70
+
71
+ Type: `Array<string|object>`
72
+
73
+ HTML tag children.
74
+
75
+ Strings are escaped, objects are passed to `createHtmlElement`.
76
+
77
+ This option is mutually exclusive with the `html` and `text` options.
78
+
79
+ ```js
80
+ import createHtmlElement from 'create-html-element';
81
+
82
+ createHtmlElement({
83
+ name: 'div',
84
+ children: [
85
+ '<unsafe>',
86
+ {
87
+ name: 'iframe',
88
+ attributes: {
89
+ src: 'https://example.com'
90
+ }
91
+ },
92
+ {
93
+ name: 'span',
94
+ text: 'Label here <em>plz</em>'
95
+ }
96
+ ]
97
+ });
98
+ //=> '<div>&lt;unsafe&gt;<iframe src="https://example.com"></iframe><span>Label here &lt;em&gt;plz&lt;/em&gt;</span></div>'
99
+ ```
68
100
 
69
101
  ## Related
70
102