@thi.ng/hiccup 5.0.9 → 5.1.1

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
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-03T12:13:31Z
3
+ - **Last updated**: 2023-12-11T10:07:09Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,18 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [5.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/hiccup@5.1.0) (2023-12-09)
13
+
14
+ #### 🚀 Features
15
+
16
+ - allow `class` attrib as string array ([448edd0](https://github.com/thi-ng/umbrella/commit/448edd0))
17
+ - update mergeClasses()
18
+ - update docs & tests
19
+
20
+ #### 🩹 Bug fixes
21
+
22
+ - fix mergeClasses(), add tests ([c734794](https://github.com/thi-ng/umbrella/commit/c734794))
23
+
12
24
  ### [5.0.6](https://github.com/thi-ng/umbrella/tree/@thi.ng/hiccup@5.0.6) (2023-11-09)
13
25
 
14
26
  #### ♻️ Refactoring
package/README.md CHANGED
@@ -173,7 +173,7 @@ For Node.js REPL:
173
173
  const hiccup = await import("@thi.ng/hiccup");
174
174
  ```
175
175
 
176
- Package sizes (brotli'd, pre-treeshake): ESM: 2.14 KB
176
+ Package sizes (brotli'd, pre-treeshake): ESM: 2.17 KB
177
177
 
178
178
  ## Dependencies
179
179
 
@@ -198,6 +198,7 @@ directory are using this package:
198
198
  | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/markdown-parser.jpg" width="240"/> | Markdown to Hiccup to HTML parser / transformer | [Demo](https://demo.thi.ng/umbrella/markdown/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/markdown) |
199
199
  | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/package-stats.png" width="240"/> | CLI util to visualize umbrella pkg stats | | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/package-stats) |
200
200
  | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/pointfree-svg.png" width="240"/> | Generate SVG using pointfree DSL | | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/pointfree-svg) |
201
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/rdom-formgen.jpg" width="240"/> | Basic usage of the declarative rdom-forms generator | [Demo](https://demo.thi.ng/umbrella/rdom-formgen/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/rdom-formgen) |
201
202
  | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/rstream-grid.jpg" width="240"/> | Interactive grid generator, SVG generation & export, undo/redo support | [Demo](https://demo.thi.ng/umbrella/rstream-grid/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/rstream-grid) |
202
203
 
203
204
  ## API
package/api.js CHANGED
@@ -1,53 +1,52 @@
1
- /** @internal */
2
- export const PROC_TAGS = {
3
- "?xml": "?>\n",
4
- "!DOCTYPE": ">\n",
5
- "!ENTITY": ">\n",
6
- "!ELEMENT": ">\n",
7
- "!ATTLIST": ">\n",
1
+ const PROC_TAGS = {
2
+ "?xml": "?>\n",
3
+ "!DOCTYPE": ">\n",
4
+ "!ENTITY": ">\n",
5
+ "!ELEMENT": ">\n",
6
+ "!ATTLIST": ">\n"
8
7
  };
9
- /** @internal */
10
- export const RE_TAG = /^([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?$/;
11
- /** @internal */
12
- export const COMMENT = "__COMMENT__";
13
- /** @internal */
14
- export const CDATA = "!CDATA";
15
- /** @internal */
16
- export const DOCTYPE = "!DOCTYPE";
17
- /**
18
- * XML processing instruction in hiccup format.
19
- *
20
- * @remarks
21
- * Translates to `<?xml version="1.0" encoding="UTF-8"?>`
22
- */
23
- export const XML_PROC = ["?xml", { version: "1.0", encoding: "UTF-8" }];
24
- /**
25
- * `<!DOCTYPE html>` in hiccup format
26
- */
27
- export const DOCTYPE_HTML = [DOCTYPE, "html"];
28
- /** @internal */
29
- export const NO_SPANS = {
30
- button: 1,
31
- option: 1,
32
- script: 1,
33
- style: 1,
34
- text: 1,
35
- textarea: 1,
36
- title: 1,
8
+ const RE_TAG = /^([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?$/;
9
+ const COMMENT = "__COMMENT__";
10
+ const CDATA = "!CDATA";
11
+ const DOCTYPE = "!DOCTYPE";
12
+ const XML_PROC = ["?xml", { version: "1.0", encoding: "UTF-8" }];
13
+ const DOCTYPE_HTML = [DOCTYPE, "html"];
14
+ const NO_SPANS = {
15
+ button: 1,
16
+ option: 1,
17
+ script: 1,
18
+ style: 1,
19
+ text: 1,
20
+ textarea: 1,
21
+ title: 1
37
22
  };
38
- const tagMap = (tags) => tags.split(" ").reduce((acc, x) => ((acc[x] = true), acc), {});
39
- /** @internal */
40
- // tslint:disable-next-line
41
- export const SVG_TAGS = tagMap("animate animateColor animateMotion animateTransform circle clipPath color-profile defs desc discard ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop style svg switch symbol text textPath title tref tspan use view");
42
- /** @internal */
43
- // tslint:disable-next-line
44
- export const VOID_TAGS = tagMap("area base br col command embed hr img input keygen link meta param source stop track use wbr ?xml");
45
- /** @internal */
46
- // tslint:disable-next-line
47
- export const NO_CLOSE_EMPTY = tagMap("animate circle ellipse line path polygon polyline rect");
48
- /** @internal */
49
- export const ATTRIB_JOIN_DELIMS = {
50
- accept: ",",
51
- sizes: ",",
52
- srcset: ",",
23
+ const tagMap = (tags) => tags.split(" ").reduce((acc, x) => (acc[x] = true, acc), {});
24
+ const SVG_TAGS = tagMap(
25
+ "animate animateColor animateMotion animateTransform circle clipPath color-profile defs desc discard ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter font foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop style svg switch symbol text textPath title tref tspan use view"
26
+ );
27
+ const VOID_TAGS = tagMap(
28
+ "area base br col command embed hr img input keygen link meta param source stop track use wbr ?xml"
29
+ );
30
+ const NO_CLOSE_EMPTY = tagMap(
31
+ "animate circle ellipse line path polygon polyline rect"
32
+ );
33
+ const ATTRIB_JOIN_DELIMS = {
34
+ class: " ",
35
+ accept: ",",
36
+ sizes: ",",
37
+ srcset: ","
38
+ };
39
+ export {
40
+ ATTRIB_JOIN_DELIMS,
41
+ CDATA,
42
+ COMMENT,
43
+ DOCTYPE,
44
+ DOCTYPE_HTML,
45
+ NO_CLOSE_EMPTY,
46
+ NO_SPANS,
47
+ PROC_TAGS,
48
+ RE_TAG,
49
+ SVG_TAGS,
50
+ VOID_TAGS,
51
+ XML_PROC
53
52
  };
package/attribs.d.ts CHANGED
@@ -1,23 +1,28 @@
1
1
  /**
2
- * Takes a space separated string of existing CSS class names and merges
3
- * it with `val`, which is either another string of class names, an
4
- * object of booleans or an `IDeref` evaluating to either. Returns
5
- * updated class string.
2
+ * Takes a space separated string of existing CSS class names and merges it with
3
+ * `val`, which is either another string or string array of class names, an
4
+ * object of booleans or an `IDeref` evaluating to either. Returns updated class
5
+ * string.
6
6
  *
7
7
  * @remarks
8
8
  * If `val` evaluates to a string, it will be appended to `existing`.
9
9
  *
10
- * If `val` is an object, its keys are used as class names and their
11
- * values indicate if the class should be added or removed from the
12
- * existing set.
10
+ * If `val` is an array, it will be joined as space-separated string and
11
+ * concatenated to the existing one.
12
+ *
13
+ * If `val` is an object, its keys are used as class names and their values
14
+ * indicate if the class should be added or removed from the existing set.
13
15
  *
14
16
  * @example
15
17
  * ```ts
16
18
  * mergeClasses("foo bar", { foo: false, baz: true })
17
19
  * // "bar baz"
18
20
  *
21
+ * mergeClasses("foo", ["bar", "baz"]);
22
+ * // "foo bar baz"
23
+ *
19
24
  * mergeClasses("foo bar", "baz");
20
- * // "baz"
25
+ * // "foo bar baz"
21
26
  * ```
22
27
  *
23
28
  * @param existing -
package/attribs.js CHANGED
@@ -1,59 +1,32 @@
1
1
  import { deref } from "@thi.ng/api/deref";
2
+ import { isArray } from "@thi.ng/checks/is-array";
2
3
  import { isString } from "@thi.ng/checks/is-string";
3
- /**
4
- * Takes a space separated string of existing CSS class names and merges
5
- * it with `val`, which is either another string of class names, an
6
- * object of booleans or an `IDeref` evaluating to either. Returns
7
- * updated class string.
8
- *
9
- * @remarks
10
- * If `val` evaluates to a string, it will be appended to `existing`.
11
- *
12
- * If `val` is an object, its keys are used as class names and their
13
- * values indicate if the class should be added or removed from the
14
- * existing set.
15
- *
16
- * @example
17
- * ```ts
18
- * mergeClasses("foo bar", { foo: false, baz: true })
19
- * // "bar baz"
20
- *
21
- * mergeClasses("foo bar", "baz");
22
- * // "baz"
23
- * ```
24
- *
25
- * @param existing -
26
- * @param val -
27
- */
28
- export const mergeClasses = (existing, val) => {
29
- val = deref(val);
30
- if (val == null)
31
- return existing;
32
- if (isString(val))
33
- return existing + " " + val;
34
- const classes = new Set(existing.split(" "));
35
- for (let id in val) {
36
- deref(val[id]) ? classes.add(id) : classes.delete(id);
37
- }
38
- return [...classes].join(" ");
4
+ const mergeClasses = (existing, val) => {
5
+ val = deref(val);
6
+ if (val == null)
7
+ return existing;
8
+ if (isArray(val))
9
+ val = val.join(" ");
10
+ if (isString(val))
11
+ return existing ? existing + " " + val : val;
12
+ const classes = new Set(existing ? existing.split(" ") : void 0);
13
+ for (let id in val) {
14
+ deref(val[id]) ? classes.add(id) : classes.delete(id);
15
+ }
16
+ return [...classes].join(" ");
39
17
  };
40
- /**
41
- * Takes an attrib object and optional element ID and CSS class names from Emmet-style
42
- * hiccup tag, then transforms and merges definitions, returns attribs.
43
- *
44
- * @param attribs -
45
- * @param id -
46
- * @param classes -
47
- */
48
- export const mergeEmmetAttribs = (attribs, id, classes) => {
49
- id && (attribs.id = id);
50
- let aclass = deref(attribs.class);
51
- if (classes) {
52
- classes = classes.replace(/\./g, " ");
53
- attribs.class = aclass ? mergeClasses(classes, aclass) : classes;
54
- }
55
- else if (aclass) {
56
- attribs.class = isString(aclass) ? aclass : mergeClasses("", aclass);
57
- }
58
- return attribs;
18
+ const mergeEmmetAttribs = (attribs, id, classes) => {
19
+ id && (attribs.id = id);
20
+ let aclass = deref(attribs.class);
21
+ if (classes) {
22
+ classes = classes.replace(/\./g, " ");
23
+ attribs.class = aclass ? mergeClasses(classes, aclass) : classes;
24
+ } else if (aclass) {
25
+ attribs.class = isString(aclass) ? aclass : mergeClasses("", aclass);
26
+ }
27
+ return attribs;
28
+ };
29
+ export {
30
+ mergeClasses,
31
+ mergeEmmetAttribs
59
32
  };
package/css.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import { deref } from "@thi.ng/api/deref";
2
2
  import { isFunction } from "@thi.ng/checks/is-function";
3
- export const css = (rules) => {
4
- let css = "";
5
- let v;
6
- for (let r in rules) {
7
- v = deref(rules[r]);
8
- if (isFunction(v))
9
- v = v(rules);
10
- if (v != null)
11
- css += `${r}:${v};`;
12
- }
13
- return css;
3
+ const css = (rules) => {
4
+ let css2 = "";
5
+ let v;
6
+ for (let r in rules) {
7
+ v = deref(rules[r]);
8
+ if (isFunction(v))
9
+ v = v(rules);
10
+ if (v != null)
11
+ css2 += `${r}:${v};`;
12
+ }
13
+ return css2;
14
+ };
15
+ export {
16
+ css
14
17
  };
package/deref.js CHANGED
@@ -1,23 +1,15 @@
1
1
  import { implementsFunction } from "@thi.ng/checks/implements-function";
2
- /**
3
- * Takes an arbitrary `ctx` object and array of `keys`. Attempts to call
4
- * `.deref()` on all given keys' values and stores result values instead
5
- * of original. Returns updated copy of `ctx` or original if `ctx` is
6
- * `null` or no keys were given.
7
- *
8
- * @param ctx - user context object
9
- * @param keys - keys to deref
10
- *
11
- * @internal
12
- */
13
- export const derefContext = (ctx, keys) => {
14
- if (ctx == null || !keys || !keys.length)
15
- return ctx;
16
- const res = { ...ctx };
17
- for (let k of keys) {
18
- const v = res[k];
19
- if (implementsFunction(v, "deref"))
20
- res[k] = v.deref();
21
- }
22
- return res;
2
+ const derefContext = (ctx, keys) => {
3
+ if (ctx == null || !keys || !keys.length)
4
+ return ctx;
5
+ const res = { ...ctx };
6
+ for (let k of keys) {
7
+ const v = res[k];
8
+ if (implementsFunction(v, "deref"))
9
+ res[k] = v.deref();
10
+ }
11
+ return res;
12
+ };
13
+ export {
14
+ derefContext
23
15
  };
package/normalize.js CHANGED
@@ -3,21 +3,24 @@ import { isString } from "@thi.ng/checks/is-string";
3
3
  import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
4
4
  import { RE_TAG } from "./api.js";
5
5
  import { mergeEmmetAttribs } from "./attribs.js";
6
- export const normalize = (tag) => {
7
- let name = tag[0];
8
- let match;
9
- const hasAttribs = isPlainObject(tag[1]);
10
- const attribs = hasAttribs ? { ...tag[1] } : {};
11
- if (!isString(name) || !(match = RE_TAG.exec(name))) {
12
- illegalArgs(`"${name}" is not a valid tag name`);
6
+ const normalize = (tag) => {
7
+ let name = tag[0];
8
+ let match;
9
+ const hasAttribs = isPlainObject(tag[1]);
10
+ const attribs = hasAttribs ? { ...tag[1] } : {};
11
+ if (!isString(name) || !(match = RE_TAG.exec(name))) {
12
+ illegalArgs(`"${name}" is not a valid tag name`);
13
+ }
14
+ name = match[1];
15
+ mergeEmmetAttribs(attribs, match[2], match[3]);
16
+ if (tag.length > 1) {
17
+ tag = tag.slice(hasAttribs ? 2 : 1).filter((x) => x != null);
18
+ if (tag.length > 0) {
19
+ return [name, attribs, tag];
13
20
  }
14
- name = match[1];
15
- mergeEmmetAttribs(attribs, match[2], match[3]);
16
- if (tag.length > 1) {
17
- tag = tag.slice(hasAttribs ? 2 : 1).filter((x) => x != null);
18
- if (tag.length > 0) {
19
- return [name, attribs, tag];
20
- }
21
- }
22
- return [name, attribs];
21
+ }
22
+ return [name, attribs];
23
+ };
24
+ export {
25
+ normalize
23
26
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/hiccup",
3
- "version": "5.0.9",
3
+ "version": "5.1.1",
4
4
  "description": "HTML/SVG/XML serialization of nested data structures, iterables & closures",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -33,15 +35,15 @@
33
35
  "test": "bun test"
34
36
  },
35
37
  "dependencies": {
36
- "@thi.ng/api": "^8.9.10",
37
- "@thi.ng/checks": "^3.4.10",
38
- "@thi.ng/errors": "^2.4.4",
39
- "@thi.ng/strings": "^3.7.1"
38
+ "@thi.ng/api": "^8.9.12",
39
+ "@thi.ng/checks": "^3.4.12",
40
+ "@thi.ng/errors": "^2.4.6",
41
+ "@thi.ng/strings": "^3.7.3"
40
42
  },
41
43
  "devDependencies": {
42
44
  "@microsoft/api-extractor": "^7.38.3",
43
- "@thi.ng/atom": "^5.2.16",
44
- "@thi.ng/testament": "^0.4.3",
45
+ "@thi.ng/atom": "^5.2.18",
46
+ "esbuild": "^0.19.8",
45
47
  "rimraf": "^5.0.5",
46
48
  "tools": "^0.0.1",
47
49
  "typedoc": "^0.25.4",
@@ -126,5 +128,5 @@
126
128
  ],
127
129
  "year": 2016
128
130
  },
129
- "gitHead": "04d1de79f256d7a53c6b5fd157b37f49bc88e11d\n"
131
+ "gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n"
130
132
  }
package/prefix.js CHANGED
@@ -1,17 +1,7 @@
1
- /**
2
- * Takes an object of RDF/XML prefixes and returns formatted string for
3
- * the RDFa `prefix` attribute.
4
- *
5
- * @example
6
- * ```ts
7
- * import { foaf, xsd } from "@thi.ng/prefixes";
8
- *
9
- * formatPrefixes({ foaf, xsd })
10
- * // "foaf: http://xmlns.com/foaf/0.1/ rdf: http://www.w3.org/2001/XMLSchema#"
11
- * ```
12
- *
13
- * @param prefixes -
14
- */
15
- export const formatPrefixes = (prefixes) => Object.keys(prefixes)
16
- .reduce((acc, k) => (acc.push(`${k}: ${prefixes[k]}`), acc), [])
17
- .join(" ");
1
+ const formatPrefixes = (prefixes) => Object.keys(prefixes).reduce(
2
+ (acc, k) => (acc.push(`${k}: ${prefixes[k]}`), acc),
3
+ []
4
+ ).join(" ");
5
+ export {
6
+ formatPrefixes
7
+ };
package/serialize.js CHANGED
@@ -7,256 +7,108 @@ import { isPlainObject } from "@thi.ng/checks/is-plain-object";
7
7
  import { isString } from "@thi.ng/checks/is-string";
8
8
  import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
9
9
  import { escapeEntitiesNum } from "@thi.ng/strings/entities";
10
- import { ATTRIB_JOIN_DELIMS, CDATA, COMMENT, NO_CLOSE_EMPTY, NO_SPANS, PROC_TAGS, VOID_TAGS, } from "./api.js";
10
+ import {
11
+ ATTRIB_JOIN_DELIMS,
12
+ CDATA,
13
+ COMMENT,
14
+ NO_CLOSE_EMPTY,
15
+ NO_SPANS,
16
+ PROC_TAGS,
17
+ VOID_TAGS
18
+ } from "./api.js";
11
19
  import { css } from "./css.js";
12
20
  import { normalize } from "./normalize.js";
13
21
  import { formatPrefixes } from "./prefix.js";
14
- /**
15
- * Recursively normalizes and serializes given tree as HTML/SVG/XML string.
16
- * Expands any embedded component functions with their results.
17
- *
18
- * @remarks
19
- * Each node of the input tree can have one of the following input forms:
20
- *
21
- * ```js
22
- * ["tag", ...]
23
- * ["tag#id.class1.class2", ...]
24
- * ["tag", {other: "attrib"}, ...]
25
- * ["tag", {...}, "body", function, ...]
26
- * [function, arg1, arg2, ...]
27
- * [{render: (ctx,...) => [...]}, args...]
28
- * iterable
29
- * ```
30
- *
31
- * Tags can be defined in "Emmet" convention, e.g.
32
- *
33
- * ```js
34
- * ["div#foo.bar.baz", "hi"] // <div id="foo" class="bar baz">hi</div>
35
- * ```
36
- *
37
- * The presence of the attributes object (2nd array index) is optional. Any
38
- * attribute values, incl. functions are allowed. If the latter, the function is
39
- * called with the full attribs object as argument and the return value is used
40
- * for the attribute. This allows for the dynamic creation of attrib values
41
- * based on other attribs. The only exception to this are event attributes, i.e.
42
- * attribute names starting with "on". Function values assigned to event
43
- * attributes will be omitted from the output.
44
- *
45
- * ```js
46
- * ["div#foo", { bar: (attribs) => attribs.id + "-bar" }]
47
- * // <div id="foo" bar="foo-bar"></div>
48
- * ```
49
- *
50
- * The `style` attribute can ONLY be defined as string or object.
51
- *
52
- * ```js
53
- * ["div", { style: { color: "red", background: "#000" } }]
54
- * // <div style="color:red;background:#000;"></div>
55
- * ```
56
- *
57
- * Boolean attribs are serialized in HTML5 syntax (present or not). `null`,
58
- * `undefined` or empty string attrib values are ignored.
59
- *
60
- * Any `null` or `undefined` array values (other than in head position) will
61
- * also be removed, unless a function is in head position.
62
- *
63
- * A function in head position of a node acts as a mechanism for component
64
- * composition & delayed execution. The function will only be executed at
65
- * serialization time. In this case the optional global context object and all
66
- * other elements of that node / array are passed as arguments when that
67
- * function is called. The return value the function MUST be a valid new tree
68
- * (or `undefined`).
69
- *
70
- * If the `ctx` option is given it'll be passed to each embedded component fns.
71
- * Optionally call {@link derefContext} prior to {@link serialize} to auto-deref
72
- * context keys with values implementing the
73
- * [`IDeref`](https://docs.thi.ng/umbrella/api/interfaces/IDeref.html)
74
- * interface.
75
- *
76
- * ```js
77
- * const foo = (ctx, a, b) => ["div#" + a, ctx.foo, b];
78
- *
79
- * serialize([foo, "id", "body"], { ctx: { foo: { class: "black" } } })
80
- * // <div id="id" class="black">body</div>
81
- * ```
82
- *
83
- * Functions located in other positions are called ONLY with the global context
84
- * arg and can return any (serializable) value (i.e. new trees, strings,
85
- * numbers, iterables or any type with a suitable `.toString()`, `.toHiccup()`
86
- * or `.deref()` implementation).
87
- *
88
- * hiccup & hdom control attributes (i.e. attrib names prefixed with `__`) will
89
- * be omitted from the output. The only control attrib supported by this package
90
- * is `__serialize`. If set to `false`, the entire tree branch below (and
91
- * including) the element with that attrib will be excluded from the output.
92
- *
93
- * **See {@link SerializeOpts} for further available options.**
94
- *
95
- * Single or multiline comments can be included using the special `COMMENT` tag
96
- * (`"__COMMENT__"`) (always WITHOUT attributes!).
97
- *
98
- * ```js
99
- * [COMMENT, "Hello world"]
100
- * // <!-- Hello world -->
101
- *
102
- * [COMMENT, "Hello", "world"]
103
- * // <!--
104
- * // Hello
105
- * // world
106
- * // -->
107
- * ```
108
- *
109
- * Currently, the only processing / DTD instructions supported are:
110
- *
111
- * - `?xml`
112
- * - `!DOCTYTPE`
113
- * - `!ELEMENT`
114
- * - `!ENTITY`
115
- * - `!ATTLIST`
116
- *
117
- * These are used as follows (attribs are only allowed for `?xml`, all others
118
- * only accept a body string which is taken as is):
119
- *
120
- * ```js
121
- * serialize(["?xml", { version: "1.0", standalone: "yes" }])
122
- * // <?xml version="1.0" standalone="yes"?>
123
- *
124
- * ["!DOCTYPE", "html"] // (also available as DOCTYPE_HTML)
125
- * // <!DOCTYPE html>
126
- * ```
127
- *
128
- * @param tree - hiccup elements / component tree
129
- * @param opts - options
130
- */
131
- export const serialize = (tree, opts, path = [0]) => {
132
- const $opts = {
133
- escape: false,
134
- escapeFn: escapeEntitiesNum,
135
- span: false,
136
- keys: false,
137
- ...opts,
138
- };
139
- if (opts?.keys == null && $opts.span)
140
- $opts.keys = true;
141
- return _serialize(tree, $opts, path);
22
+ const serialize = (tree, opts, path = [0]) => {
23
+ const $opts = {
24
+ escape: false,
25
+ escapeFn: escapeEntitiesNum,
26
+ span: false,
27
+ keys: false,
28
+ ...opts
29
+ };
30
+ if (opts?.keys == null && $opts.span)
31
+ $opts.keys = true;
32
+ return _serialize(tree, $opts, path);
142
33
  };
143
- const _serialize = (tree, opts, path) => tree == null
144
- ? ""
145
- : Array.isArray(tree)
146
- ? serializeElement(tree, opts, path)
147
- : isFunction(tree)
148
- ? _serialize(tree(opts.ctx), opts, path)
149
- : implementsFunction(tree, "toHiccup")
150
- ? _serialize(tree.toHiccup(opts.ctx), opts, path)
151
- : isDeref(tree)
152
- ? _serialize(tree.deref(), opts, path)
153
- : isNotStringAndIterable(tree)
154
- ? serializeIter(tree, opts, path)
155
- : ((tree = __escape(String(tree), opts)), opts.span)
156
- ? `<span${opts.keys ? ` key="${path.join("-")}"` : ""}>${tree}</span>`
157
- : tree;
34
+ const _serialize = (tree, opts, path) => tree == null ? "" : Array.isArray(tree) ? serializeElement(tree, opts, path) : isFunction(tree) ? _serialize(tree(opts.ctx), opts, path) : implementsFunction(tree, "toHiccup") ? _serialize(tree.toHiccup(opts.ctx), opts, path) : isDeref(tree) ? _serialize(tree.deref(), opts, path) : isNotStringAndIterable(tree) ? serializeIter(tree, opts, path) : (tree = __escape(String(tree), opts), opts.span) ? `<span${opts.keys ? ` key="${path.join("-")}"` : ""}>${tree}</span>` : tree;
158
35
  const serializeElement = (tree, opts, path) => {
159
- let tag = tree[0];
160
- return !tree.length
161
- ? ""
162
- : isFunction(tag)
163
- ? _serialize(tag.apply(null, [opts.ctx, ...tree.slice(1)]), opts, path)
164
- : implementsFunction(tag, "render")
165
- ? _serialize(tag.render.apply(null, [opts.ctx, ...tree.slice(1)]), opts, path)
166
- : tag === COMMENT
167
- ? serializeComment(tree)
168
- : tag == CDATA
169
- ? serializeCData(tree)
170
- : isString(tag)
171
- ? serializeTag(tree, opts, path)
172
- : isNotStringAndIterable(tree)
173
- ? serializeIter(tree, opts, path)
174
- : illegalArgs(`invalid tree node: ${tree}`);
36
+ let tag = tree[0];
37
+ return !tree.length ? "" : isFunction(tag) ? _serialize(tag.apply(null, [opts.ctx, ...tree.slice(1)]), opts, path) : implementsFunction(tag, "render") ? _serialize(
38
+ tag.render.apply(null, [opts.ctx, ...tree.slice(1)]),
39
+ opts,
40
+ path
41
+ ) : tag === COMMENT ? serializeComment(tree) : tag == CDATA ? serializeCData(tree) : isString(tag) ? serializeTag(tree, opts, path) : isNotStringAndIterable(tree) ? serializeIter(tree, opts, path) : illegalArgs(`invalid tree node: ${tree}`);
175
42
  };
176
43
  const serializeTag = (tree, opts, path) => {
177
- tree = normalize(tree);
178
- const attribs = tree[1];
179
- if (attribs.__skip || attribs.__serialize === false)
180
- return "";
181
- opts.keys && attribs.key === undefined && (attribs.key = path.join("-"));
182
- const tag = tree[0];
183
- const body = tree[2]
184
- ? serializeBody(tag, tree[2], opts, path)
185
- : !VOID_TAGS[tag] && !NO_CLOSE_EMPTY[tag]
186
- ? `></${tag}>`
187
- : PROC_TAGS[tag] || "/>";
188
- return `<${tag}${serializeAttribs(attribs, opts)}${body}`;
44
+ tree = normalize(tree);
45
+ const attribs = tree[1];
46
+ if (attribs.__skip || attribs.__serialize === false)
47
+ return "";
48
+ opts.keys && attribs.key === void 0 && (attribs.key = path.join("-"));
49
+ const tag = tree[0];
50
+ const body = tree[2] ? serializeBody(tag, tree[2], opts, path) : !VOID_TAGS[tag] && !NO_CLOSE_EMPTY[tag] ? `></${tag}>` : PROC_TAGS[tag] || "/>";
51
+ return `<${tag}${serializeAttribs(attribs, opts)}${body}`;
189
52
  };
190
53
  const serializeAttribs = (attribs, opts) => {
191
- let res = "";
192
- for (let a in attribs) {
193
- if (a.startsWith("__"))
194
- continue;
195
- const v = serializeAttrib(attribs, a, deref(attribs[a]), opts);
196
- v != null && (res += v);
197
- }
198
- return res;
54
+ let res = "";
55
+ for (let a in attribs) {
56
+ if (a.startsWith("__"))
57
+ continue;
58
+ const v = serializeAttrib(attribs, a, deref(attribs[a]), opts);
59
+ v != null && (res += v);
60
+ }
61
+ return res;
199
62
  };
200
63
  const serializeAttrib = (attribs, a, v, opts) => {
201
- return v == null
202
- ? null
203
- : isFunction(v) && (/^on\w+/.test(a) || (v = v(attribs)) == null)
204
- ? null
205
- : v === true
206
- ? " " + a
207
- : v === false
208
- ? null
209
- : a === "data"
210
- ? serializeDataAttribs(v, opts)
211
- : attribPair(a, v, opts);
64
+ return v == null ? null : isFunction(v) && (/^on\w+/.test(a) || (v = v(attribs)) == null) ? null : v === true ? " " + a : v === false ? null : a === "data" ? serializeDataAttribs(v, opts) : attribPair(a, v, opts);
212
65
  };
213
66
  const attribPair = (a, v, opts) => {
214
- v =
215
- a === "style" && isPlainObject(v)
216
- ? css(v)
217
- : a === "prefix" && isPlainObject(v)
218
- ? formatPrefixes(v)
219
- : isArray(v)
220
- ? v.join(ATTRIB_JOIN_DELIMS[a] || " ")
221
- : v.toString();
222
- return v.length ? ` ${a}="${__escape(v, opts)}"` : null;
67
+ v = a === "style" && isPlainObject(v) ? css(v) : a === "prefix" && isPlainObject(v) ? formatPrefixes(v) : isArray(v) ? v.join(ATTRIB_JOIN_DELIMS[a] || " ") : v.toString();
68
+ return v.length ? ` ${a}="${__escape(v, opts)}"` : null;
223
69
  };
224
70
  const serializeDataAttribs = (data, opts) => {
225
- let res = "";
226
- for (let id in data) {
227
- let v = deref(data[id]);
228
- isFunction(v) && (v = v(data));
229
- v != null && (res += ` data-${id}="${__escape(v, opts)}"`);
230
- }
231
- return res;
71
+ let res = "";
72
+ for (let id in data) {
73
+ let v = deref(data[id]);
74
+ isFunction(v) && (v = v(data));
75
+ v != null && (res += ` data-${id}="${__escape(v, opts)}"`);
76
+ }
77
+ return res;
232
78
  };
233
79
  const serializeBody = (tag, body, opts, path) => {
234
- if (VOID_TAGS[tag]) {
235
- illegalArgs(`No body allowed in tag: ${tag}`);
236
- }
237
- const proc = PROC_TAGS[tag];
238
- let res = proc ? " " : ">";
239
- if (opts.span && !proc && !NO_SPANS[tag])
240
- opts = { ...opts, span: true };
241
- for (let i = 0, n = body.length; i < n; i++) {
242
- res += _serialize(body[i], opts, [...path, i]);
243
- }
244
- return res + (proc || `</${tag}>`);
80
+ if (VOID_TAGS[tag]) {
81
+ illegalArgs(`No body allowed in tag: ${tag}`);
82
+ }
83
+ const proc = PROC_TAGS[tag];
84
+ let res = proc ? " " : ">";
85
+ if (opts.span && !proc && !NO_SPANS[tag])
86
+ opts = { ...opts, span: true };
87
+ for (let i = 0, n = body.length; i < n; i++) {
88
+ res += _serialize(body[i], opts, [...path, i]);
89
+ }
90
+ return res + (proc || `</${tag}>`);
245
91
  };
246
- const serializeComment = (tree) => tree.length > 2
247
- ? `\n<!--\n${tree
248
- .slice(1)
249
- .map((x) => " " + x)
250
- .join("\n")}\n-->\n`
251
- : `\n<!-- ${tree[1]} -->\n`;
252
- const serializeCData = (tree) => `<![CDATA[\n${tree.slice(1).join("\n")}\n]]>`;
92
+ const serializeComment = (tree) => tree.length > 2 ? `
93
+ <!--
94
+ ${tree.slice(1).map((x) => " " + x).join("\n")}
95
+ -->
96
+ ` : `
97
+ <!-- ${tree[1]} -->
98
+ `;
99
+ const serializeCData = (tree) => `<![CDATA[
100
+ ${tree.slice(1).join("\n")}
101
+ ]]>`;
253
102
  const serializeIter = (iter, opts, path) => {
254
- const res = [];
255
- const p = path.slice(0, path.length - 1);
256
- let k = 0;
257
- for (let i of iter) {
258
- res.push(_serialize(i, opts, [...p, k++]));
259
- }
260
- return res.join("");
103
+ const res = [];
104
+ const p = path.slice(0, path.length - 1);
105
+ let k = 0;
106
+ for (let i of iter) {
107
+ res.push(_serialize(i, opts, [...p, k++]));
108
+ }
109
+ return res.join("");
261
110
  };
262
111
  const __escape = (x, opts) => opts.escape ? opts.escapeFn(x) : x;
112
+ export {
113
+ serialize
114
+ };