unicode-input-toolconverter 0.2.0 → 0.2.2

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 (143) hide show
  1. package/.nyc_output/out.json +8980 -1849
  2. package/CHANGES.md +11 -1
  3. package/README.md +6 -11
  4. package/_locales/en-US/messages.json +153 -6
  5. package/_locales/hu-HU/messages.json +153 -6
  6. package/_locales/pt-BR/messages.json +153 -6
  7. package/_locales/sv-SE/messages.json +153 -6
  8. package/babel.config.json +0 -1
  9. package/browser_action/characterSelection.js +18 -11
  10. package/browser_action/charrefConverters.js +286 -79
  11. package/browser_action/chartBuild.js +97 -27
  12. package/browser_action/encodingBehaviors.js +10 -7
  13. package/browser_action/entityBehaviors.js +42 -16
  14. package/browser_action/index-es.html +13 -1
  15. package/browser_action/index-instrumented.html +13 -1
  16. package/browser_action/index-pages.html +13 -1
  17. package/browser_action/index.html +13 -1
  18. package/browser_action/index.iife.min.js +9 -1
  19. package/browser_action/index.iife.min.js.map +1 -1
  20. package/browser_action/index.instrumented.iife.min.js +1 -1
  21. package/browser_action/index.instrumented.iife.min.js.map +1 -1
  22. package/browser_action/index.js +5 -5
  23. package/browser_action/preferences/prefDefaults.js +12 -3
  24. package/browser_action/service-worker/sw-activateCallback.js +1 -2
  25. package/browser_action/service-worker/sw-resources.json +6 -4
  26. package/browser_action/templateUtils/elements.js +12 -1
  27. package/browser_action/templateUtils/fill.js +6 -0
  28. package/browser_action/templateUtils/validation.js +4 -1
  29. package/browser_action/templates/chartBuild.js +167 -102
  30. package/browser_action/templates/index.js +227 -53
  31. package/browser_action/templatesElementCustomization/widgets.js +40 -15
  32. package/browser_action/unicode/UnicodeConverter.js +99 -73
  33. package/browser_action/unicode/charrefunicodeDb.js +100 -50
  34. package/browser_action/unicode/getScriptInfoForCodePoint.js +13 -4
  35. package/browser_action/unicode/hangul.js +27 -20
  36. package/browser_action/unicode/lastScriptNames.json +1 -1
  37. package/browser_action/unicode/parseUnihanFromTextFileStrings.js +44 -25
  38. package/browser_action/unicode/unicodeFieldInfo.js +112 -90
  39. package/browser_action/unicode/unicodeScripts.js +409 -35
  40. package/browser_action/unicode/unihan.js +4 -3
  41. package/browser_action/unicode/unihanDbPopulate.js +7 -2
  42. package/browser_action/unicode/unihanFields.js +34 -0
  43. package/browser_action/unicodecharref.js +353 -190
  44. package/browser_action/utils/DOMUtils.js +95 -34
  45. package/browser_action/utils/FetchUtils.js +7 -6
  46. package/browser_action/utils/TextUtils.js +4 -4
  47. package/browser_action/utils/TypedArrayUtils.js +9 -5
  48. package/browser_action/utils/semicolonSeparatedToArray.js +3 -3
  49. package/browser_action/utils/setupServiceWorker.js +2 -2
  50. package/eslint.config.js +11 -0
  51. package/icons/openWindow16.png +0 -0
  52. package/icons/openWindow24.png +0 -0
  53. package/lib/background.html +7 -0
  54. package/lib/background.js +2 -2
  55. package/package.json +79 -70
  56. package/pnpm-workspace.yaml +8 -0
  57. package/server.js +66 -51
  58. package/sw.js +97 -56
  59. package/tools/entities-import.js +4 -1
  60. package/tools/findEsResources.js +33 -10
  61. package/tools/fix-sw-resources-vendor-paths.js +58 -0
  62. package/tools/list-locales.js +2 -2
  63. package/tools/parseUnicodeCharts.js +126 -60
  64. package/tools/ucd-import.js +1 -10
  65. package/tools/unicode-charts.html +49 -20
  66. package/tools/unihan-import.js +45 -15
  67. package/tools/write-sw-version.js +16 -0
  68. package/tsconfig.json +23 -0
  69. package/typings/__coverage__.d.ts +5 -0
  70. package/typings/apple-system-profiler.d.ts +17 -0
  71. package/typings/commands.d.ts +19 -0
  72. package/typings/json-6.d.ts +4 -0
  73. package/typings/rollup-plugin-istanbul.d.ts +3 -0
  74. package/vendor/camelcase/index.d.ts +154 -0
  75. package/vendor/camelcase/index.js +137 -23
  76. package/vendor/fflate/esm/browser.d.ts +1539 -0
  77. package/vendor/fflate/esm/browser.js +82 -55
  78. package/vendor/intl-dom/dist/Formatter.d.ts +120 -0
  79. package/vendor/intl-dom/dist/Formatter.d.ts.map +1 -0
  80. package/vendor/intl-dom/dist/collation.d.ts +42 -0
  81. package/vendor/intl-dom/dist/collation.d.ts.map +1 -0
  82. package/vendor/intl-dom/dist/defaultAllSubstitutions.d.ts +52 -0
  83. package/vendor/intl-dom/dist/defaultAllSubstitutions.d.ts.map +1 -0
  84. package/vendor/intl-dom/dist/defaultInsertNodes.d.ts +96 -0
  85. package/vendor/intl-dom/dist/defaultInsertNodes.d.ts.map +1 -0
  86. package/vendor/intl-dom/dist/defaultKeyCheckerConverter.d.ts +17 -0
  87. package/vendor/intl-dom/dist/defaultKeyCheckerConverter.d.ts.map +1 -0
  88. package/vendor/intl-dom/dist/defaultLocaleResolver.d.ts +285 -0
  89. package/vendor/intl-dom/dist/defaultLocaleResolver.d.ts.map +1 -0
  90. package/vendor/intl-dom/dist/findLocaleStrings.d.ts +88 -0
  91. package/vendor/intl-dom/dist/findLocaleStrings.d.ts.map +1 -0
  92. package/vendor/intl-dom/dist/getDOMForLocaleString.d.ts +67 -0
  93. package/vendor/intl-dom/dist/getDOMForLocaleString.d.ts.map +1 -0
  94. package/vendor/intl-dom/dist/getMessageForKeyByStyle.d.ts +73 -0
  95. package/vendor/intl-dom/dist/getMessageForKeyByStyle.d.ts.map +1 -0
  96. package/vendor/intl-dom/dist/getStringFromMessageAndDefaults.d.ts +26 -0
  97. package/vendor/intl-dom/dist/getStringFromMessageAndDefaults.d.ts.map +1 -0
  98. package/vendor/intl-dom/dist/i18n.d.ts +113 -0
  99. package/vendor/intl-dom/dist/i18n.d.ts.map +1 -0
  100. package/vendor/intl-dom/dist/index.d.ts +92 -0
  101. package/vendor/intl-dom/dist/index.d.ts.map +1 -0
  102. package/vendor/intl-dom/dist/index.esm.d.ts +34 -0
  103. package/vendor/intl-dom/dist/index.esm.js +854 -896
  104. package/vendor/intl-dom/dist/index.esm.min.js +4 -0
  105. package/vendor/intl-dom/dist/index.esm.min.js.map +1 -0
  106. package/vendor/intl-dom/dist/index.umd.js +2953 -0
  107. package/vendor/intl-dom/dist/index.umd.min.js +4 -0
  108. package/vendor/intl-dom/dist/index.umd.min.js.map +1 -0
  109. package/vendor/intl-dom/dist/promiseChainForValues.d.ts +34 -0
  110. package/vendor/intl-dom/dist/promiseChainForValues.d.ts.map +1 -0
  111. package/vendor/intl-dom/dist/shared.d.ts +20 -0
  112. package/vendor/intl-dom/dist/shared.d.ts.map +1 -0
  113. package/vendor/intl-dom/dist/utils.d.ts +53 -0
  114. package/vendor/intl-dom/dist/utils.d.ts.map +1 -0
  115. package/vendor/jamilih/dist/jml.d.mts +468 -0
  116. package/vendor/jamilih/dist/{jml-es.js → jml.mjs} +1489 -343
  117. package/vendor/jquery/dist/jquery.js +1677 -2713
  118. package/vendor/json-6/dist/index.mjs +2 -0
  119. package/vendor/miller-columns/CHANGES.md +185 -0
  120. package/vendor/miller-columns/LICENSE-MIT.txt +21 -0
  121. package/vendor/miller-columns/README.md +249 -0
  122. package/vendor/miller-columns/demos/index.html +69 -0
  123. package/vendor/miller-columns/demos/index.js +72 -0
  124. package/vendor/miller-columns/dist/index-es.js +748 -0
  125. package/vendor/miller-columns/dist/index-es.min.d.ts +25 -0
  126. package/vendor/miller-columns/dist/index-es.min.js +7 -1
  127. package/vendor/miller-columns/dist/index-umd.js +757 -0
  128. package/vendor/miller-columns/dist/index-umd.min.js +7 -0
  129. package/vendor/miller-columns/dist/index.d.ts +25 -0
  130. package/vendor/miller-columns/dist/millerColumns.d.ts +10 -0
  131. package/vendor/miller-columns/eslint.config.js +46 -0
  132. package/vendor/miller-columns/fix-declarations.js +18 -0
  133. package/vendor/miller-columns/miller-columns.css +24 -1
  134. package/vendor/miller-columns/package.json +73 -0
  135. package/vendor/miller-columns/pnpm-workspace.yaml +2 -0
  136. package/vendor/miller-columns/src/index.js +642 -0
  137. package/vendor/miller-columns/src/millerColumns.ts +12 -0
  138. package/vendor/miller-columns/tsconfig-prod.json +23 -0
  139. package/vendor/miller-columns/tsconfig.json +21 -0
  140. package/vendor/simple-prefs/dist/index.esm.d.ts +1 -0
  141. package/vendor/simple-prefs/dist/index.esm.js +164 -216
  142. package/vendor/simple-prefs/dist/simple-prefs.d.ts +117 -0
  143. package/lgtm.yml +0 -5
@@ -1,40 +1,830 @@
1
- var _win;
1
+ /**
2
+ * The recognized keys on the optional leading options object passed to `jml()`.
3
+ *
4
+ * An object in first-argument position is only treated as an options object
5
+ * when it carries at least one of these keys.
6
+ *
7
+ * `$mode` (reserved for a future SVG/XML mode) and `$state` (the internal
8
+ * traversal marker) are deliberately absent: they are reserved names that
9
+ * `jml()` rejects when author-supplied. Templating dialects layered on Jamilih
10
+ * may use any other `$`-prefixed key freely; `validateJamilih`'s
11
+ * `allowableOptions` governs validation of those.
12
+ * @type {string[]}
13
+ */
14
+ const possibleOptions = ['$plugins', '$Map'];
15
+
16
+ /**
17
+ * Structural + policy validator for Jamilih input.
18
+ *
19
+ * Window-free and dependency-free: it re-implements the `jml()` argument
20
+ * grammar as a non-mutating recursive checker and never calls `jml()` or
21
+ * touches a DOM. Its only import is the shared {@link possibleOptions} list.
22
+ */
23
+
24
+
25
+ /**
26
+ * @typedef {"javascript"|"json"} ValidateJamilihFormat
27
+ */
28
+
29
+ /**
30
+ * @typedef {object} ValidateJamilihOptions
31
+ * @property {ValidateJamilihFormat} [format] Default `"javascript"`. `"json"`
32
+ * additionally requires every value to be losslessly JSON round-trippable.
33
+ * @property {boolean} [allowInnerHTML] Default `true`. When `false`, an
34
+ * `innerHTML` attribute key is rejected.
35
+ * @property {boolean} [allowDOM] Default `true`; forced `false` when
36
+ * `format` is `"json"`. When effectively `false`, raw DOM nodes are rejected.
37
+ * @property {string[]} [allowableOptions] Whitelist of extensible
38
+ * `$`-prefixed properties accepted wherever `$` magic is read. Sentinels:
39
+ * `"default"` expands to the builtin option keys, `"any"` / `"*"` permits
40
+ * any unknown `$`-key. Omitting the option is equivalent to `["default"]`.
41
+ * @property {boolean} [failFast] Default `false`. When `true`, stop at the
42
+ * first error.
43
+ */
44
+
45
+ /**
46
+ * @typedef {object} JamilihValidationError
47
+ * @property {string} code Stable enum, e.g. `"BAD_CHILD"`.
48
+ * @property {string} message Human-readable description.
49
+ * @property {string} path JSON-pointer-ish location, e.g. `"/2/0/1"`.
50
+ */
51
+
52
+ /**
53
+ * @typedef {object} JamilihValidationResult
54
+ * @property {boolean} valid
55
+ * @property {JamilihValidationError[]} errors
56
+ */
57
+
58
+ const FAIL_FAST = Symbol('validateJamilih.failFast');
59
+ const RESERVED_NAMES = ['$state', '$mode'];
60
+
61
+ // Node-producing keys a first-argument object may carry (mirrors `jml()`).
62
+ const FIRST_ARG_NODE_KEYS = ['#', '$text', '$document', '$DOCTYPE', '$attribute'];
63
+
64
+ // `$`-keys `_checkAtts` / the first-argument object handling recognize. Valid
65
+ // only as an attributes object or an array-wrapped first-argument object, not
66
+ // as a bare object child.
67
+ const MAGIC_ATTR_BUILTINS = ['$on', '$symbol', '$define', '$data', '$custom', '$shadow', '$attribute', '$text', '$document', '$DOCTYPE'];
68
+ const TEXTUAL_TYPES = ['string', 'number', 'boolean'];
69
+ const NON_NODE_PRIMITIVE_TYPES = ['function', 'symbol', 'bigint'];
70
+
71
+ // First-argument special strings and how many following string args they take.
72
+ const SPECIAL_STRING_ARGC = {
73
+ '!': 1,
74
+ '&': 1,
75
+ '#': 1,
76
+ '#x': 1,
77
+ '?': 2,
78
+ '![': 1
79
+ };
80
+
81
+ /**
82
+ * @param {unknown} v
83
+ * @returns {boolean}
84
+ */
85
+ function isDOMNode(v) {
86
+ return Boolean(v && typeof v === 'object' && typeof (/** @type {{nodeType?: unknown}} */v).nodeType === 'number');
87
+ }
88
+
89
+ /**
90
+ * @param {unknown} v
91
+ * @returns {boolean}
92
+ */
93
+ function isMapLike(v) {
94
+ return /^\[object (?:Weak)?Map\]$/u.test(Object.prototype.toString.call(v));
95
+ }
96
+
97
+ /**
98
+ * @param {unknown} v
99
+ * @returns {boolean}
100
+ */
101
+ function isPlainObject(v) {
102
+ if (!v || typeof v !== 'object' || Array.isArray(v)) {
103
+ return false;
104
+ }
105
+ const proto = Object.getPrototypeOf(v);
106
+ return proto === Object.prototype || proto === null;
107
+ }
108
+
109
+ /**
110
+ * @param {string} key
111
+ * @returns {boolean}
112
+ */
113
+ function isPluginKey(key) {
114
+ return key.startsWith('$_');
115
+ }
116
+
117
+ /**
118
+ * @param {unknown} v
119
+ * @returns {boolean}
120
+ */
121
+ function isNullish(v) {
122
+ return v === null || v === undefined;
123
+ }
124
+
125
+ /**
126
+ * @param {ValidateJamilihOptions} options
127
+ * @throws {TypeError} If `allowableOptions` is not an array.
128
+ * @returns {{allowAny: boolean, allowable: Set<string>, emptyPolicy: boolean}}
129
+ */
130
+ function resolveAllowable(options) {
131
+ const provided = Object.hasOwn(options, 'allowableOptions');
132
+ const raw = provided ? options.allowableOptions : ['default'];
133
+ if (!Array.isArray(raw)) {
134
+ throw new TypeError('`allowableOptions` must be an array of strings');
135
+ }
136
+ let allowAny = false;
137
+ const allowable = new Set();
138
+ for (const entry of raw) {
139
+ if (entry === 'any' || entry === '*') {
140
+ allowAny = true;
141
+ } else if (entry === 'default') {
142
+ for (const k of possibleOptions) {
143
+ allowable.add(k);
144
+ }
145
+ } else {
146
+ allowable.add(entry);
147
+ }
148
+ }
149
+ return {
150
+ allowAny,
151
+ allowable,
152
+ emptyPolicy: provided && raw.length === 0
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Validate a single-array Jamilih structure against structural and policy
158
+ * rules.
159
+ * @param {unknown} structure The JSON-serializable Jamilih array form.
160
+ * @param {ValidateJamilihOptions} [options]
161
+ * @throws {TypeError} If `format` or `allowableOptions` is invalid.
162
+ * @returns {JamilihValidationResult}
163
+ */
164
+ const validateJamilih = (structure, options = {}) => {
165
+ const format = options.format ?? 'javascript';
166
+ if (format !== 'javascript' && format !== 'json') {
167
+ throw new TypeError(`Unknown \`format\`: ${JSON.stringify(format)}`);
168
+ }
169
+ const allowInnerHTML = options.allowInnerHTML !== false;
170
+ const failFast = options.failFast === true;
171
+ const {
172
+ allowAny,
173
+ allowable,
174
+ emptyPolicy
175
+ } = resolveAllowable(options);
176
+
177
+ // `json` can never carry a DOM node; the explicit combination is a conflict.
178
+ const domConflict = format === 'json' && options.allowDOM === true;
179
+ const allowDOM = format === 'json' ? false : options.allowDOM !== false;
180
+
181
+ /** @type {JamilihValidationError[]} */
182
+ const errors = [];
183
+
184
+ /**
185
+ * @param {string} code
186
+ * @param {string} message
187
+ * @param {(string|number)[]} pathParts
188
+ * @returns {void}
189
+ */
190
+ const err = (code, message, pathParts) => {
191
+ errors.push({
192
+ code,
193
+ message,
194
+ path: pathParts.length ? '/' + pathParts.join('/') : '/'
195
+ });
196
+ if (failFast) {
197
+ throw FAIL_FAST;
198
+ }
199
+ };
200
+
201
+ /**
202
+ * @param {string} key
203
+ * @returns {boolean}
204
+ */
205
+ const isReserved = key => RESERVED_NAMES.includes(key);
206
+
207
+ /**
208
+ * @param {string} key
209
+ * @returns {boolean}
210
+ */
211
+ const isOptionKey = key => possibleOptions.includes(key);
212
+
213
+ /**
214
+ * Deep JSON round-trippability check for `format: "json"`.
215
+ * @param {unknown} v
216
+ * @param {(string|number)[]} path
217
+ * @returns {void}
218
+ */
219
+ const checkJSONValue = (v, path) => {
220
+ if (v === null) {
221
+ return;
222
+ }
223
+ const t = typeof v;
224
+ if (t === 'string' || t === 'boolean') {
225
+ return;
226
+ }
227
+ if (t === 'number') {
228
+ if (!Number.isFinite(v)) {
229
+ err('NON_JSON_VALUE', '`NaN`/`Infinity` is not valid JSON', path);
230
+ }
231
+ return;
232
+ }
233
+ if (t === 'undefined') {
234
+ err('NON_JSON_VALUE', '`undefined` is not valid JSON', path);
235
+ return;
236
+ }
237
+ if (NON_NODE_PRIMITIVE_TYPES.includes(t)) {
238
+ err('NON_JSON_VALUE', `A ${t} value is not valid JSON`, path);
239
+ return;
240
+ }
241
+ if (isDOMNode(v)) {
242
+ err('NON_JSON_VALUE', 'A DOM node is not valid JSON', path);
243
+ return;
244
+ }
245
+ if (isMapLike(v)) {
246
+ err('NON_JSON_VALUE', 'A Map/WeakMap is not valid JSON', path);
247
+ return;
248
+ }
249
+ if (Array.isArray(v)) {
250
+ v.forEach((item, i) => checkJSONValue(item, [...path, i]));
251
+ return;
252
+ }
253
+ if (!isPlainObject(v)) {
254
+ err('NON_JSON_VALUE', 'A non-plain object (Date/RegExp/class instance) is not valid JSON', path);
255
+ return;
256
+ }
257
+ for (const [k, item] of Object.entries(/** @type {Record<string, unknown>} */v)) {
258
+ checkJSONValue(item, [...path, k]);
259
+ }
260
+ };
261
+
262
+ /**
263
+ * @param {string} key
264
+ * @param {unknown} val
265
+ * @param {(string|number)[]} path
266
+ * @returns {void}
267
+ */
268
+ const checkExtensibleMagicKey = (key, val, path) => {
269
+ if (allowAny || allowable.has(key)) {
270
+ if (format === 'json') {
271
+ checkJSONValue(val, path);
272
+ }
273
+ return;
274
+ }
275
+ err('UNKNOWN_MAGIC_PROPERTY', `Unknown \`$\`-prefixed property \`${key}\` is not permitted by \`allowableOptions\``, path);
276
+ };
277
+
278
+ /**
279
+ * @param {Record<string, unknown>} obj
280
+ * @param {(string|number)[]} path
281
+ * @returns {void}
282
+ */
283
+ const flagMisplacedOptions = (obj, path) => {
284
+ for (const k of Object.keys(obj)) {
285
+ if (k === '$state' || isOptionKey(k)) {
286
+ err('MISPLACED_OPTIONS_OBJECT', `\`${k}\` is a root-only option and cannot appear on a nested array head`, [...path, k]);
287
+ }
288
+ }
289
+ };
290
+
291
+ /**
292
+ * @param {unknown} val
293
+ * @param {(string|number)[]} path
294
+ * @returns {void}
295
+ */
296
+ const validatePluginsOption = (val, path) => {
297
+ if (!Array.isArray(val)) {
298
+ err('BAD_ATTRIBUTES_OBJECT', '`$plugins` must be an array', path);
299
+ return;
300
+ }
301
+ val.forEach((p, i) => {
302
+ if (!p || typeof p !== 'object') {
303
+ err('BAD_ATTRIBUTES_OBJECT', '`$plugins` entries must be objects', [...path, i]);
304
+ return;
305
+ }
306
+ const plugin = /** @type {{name?: unknown, set?: unknown}} */p;
307
+ if (typeof plugin.name !== 'string' || !plugin.name.startsWith('$_')) {
308
+ err('BAD_ATTRIBUTES_OBJECT', 'Plugin `name` must be a string beginning with `$_`', [...path, i, 'name']);
309
+ }
310
+ if (format === 'json') {
311
+ err('NON_JSON_CONSTRUCT', 'Plugins require a `set` function; not valid in `format: "json"`', [...path, i, 'set']);
312
+ } else if (typeof plugin.set !== 'function') {
313
+ err('BAD_ATTRIBUTES_OBJECT', 'Plugin `set` must be a function', [...path, i, 'set']);
314
+ }
315
+ });
316
+ };
317
+
318
+ /**
319
+ * @param {unknown} val
320
+ * @param {(string|number)[]} path
321
+ * @returns {void}
322
+ */
323
+ const validateMapOption = (val, path) => {
324
+ if (format === 'json') {
325
+ err('NON_JSON_CONSTRUCT', '`$Map` carries a Map/WeakMap; not valid in `format: "json"`', path);
326
+ return;
327
+ }
328
+ const ok = isMapLike(val) || Array.isArray(val) && (val[0] === undefined || isMapLike(val[0])) || isPlainObject(val) && Object.hasOwn(/** @type {object} */val, 'root');
329
+ if (!ok) {
330
+ err('BAD_DATA', '`$Map` must be `[map, value]` or `{root: [...], ...}`', path);
331
+ }
332
+ };
333
+
334
+ /**
335
+ * @param {unknown} val
336
+ * @param {(string|number)[]} path
337
+ * @returns {void}
338
+ */
339
+ const validateOnObject = (val, path) => {
340
+ if (!isPlainObject(val)) {
341
+ err('BAD_ON_HANDLER', '`$on` must be an object of event handlers', path);
342
+ return;
343
+ }
344
+ const entries = Object.entries(/** @type {Record<string, unknown>} */val);
345
+ if (entries.length === 0) {
346
+ return; // documented no-op
347
+ }
348
+ if (format === 'json') {
349
+ err('NON_JSON_CONSTRUCT', '`$on` handlers are functions; not valid in `format: "json"`', path);
350
+ return;
351
+ }
352
+ for (const [evt, handler] of entries) {
353
+ const okHandler = typeof handler === 'function' || Array.isArray(handler) && typeof handler[0] === 'function';
354
+ if (!okHandler) {
355
+ err('BAD_ON_HANDLER', `\`$on.${evt}\` must be a function or \`[function, capturing]\``, [...path, evt]);
356
+ }
357
+ }
358
+ };
359
+
360
+ /**
361
+ * @param {unknown} val
362
+ * @param {(string|number)[]} path
363
+ * @returns {void}
364
+ */
365
+ const validateShadowObject = (val, path) => {
366
+ if (!isPlainObject(val)) {
367
+ err('BAD_SHADOW', '`$shadow` must be an object', path);
368
+ return;
369
+ }
370
+ const obj = /** @type {Record<string, unknown>} */val;
371
+ for (const slot of ['template', 'content']) {
372
+ if (!Object.hasOwn(obj, slot)) {
373
+ continue;
374
+ }
375
+ const v = obj[slot];
376
+ if (Array.isArray(v)) {
377
+ validateChildrenContainer(v, [...path, slot]);
378
+ } else if (isDOMNode(v)) {
379
+ if (!allowDOM) {
380
+ err('DOM_NODE_NOT_ALLOWED', `\`$shadow.${slot}\` DOM node is not allowed`, [...path, slot]);
381
+ }
382
+ } else if (typeof v !== 'string' && typeof v !== 'boolean' && !isNullish(v)) {
383
+ err('BAD_SHADOW', `\`$shadow.${slot}\` must be a Jamilih array, selector string, or DOM node`, [...path, slot]);
384
+ }
385
+ }
386
+ };
387
+
388
+ /**
389
+ * @param {unknown} val
390
+ * @param {(string|number)[]} path
391
+ * @returns {void}
392
+ */
393
+ const validateDocumentObject = (val, path) => {
394
+ if (!isPlainObject(val)) {
395
+ err('BAD_ATTRIBUTES_OBJECT', '`$document` must be an object', path);
396
+ return;
397
+ }
398
+ const obj = /** @type {Record<string, unknown>} */val;
399
+ for (const slot of ['childNodes', 'head', 'body']) {
400
+ if (Array.isArray(obj[slot])) {
401
+ validateChildrenContainer(/** @type {unknown[]} */obj[slot], [...path, slot]);
402
+ }
403
+ }
404
+ };
405
+
406
+ /**
407
+ * @param {Record<string, unknown>} obj
408
+ * @param {(string|number)[]} path
409
+ * @param {boolean} isChildHead
410
+ * @returns {void}
411
+ */
412
+ const validateAttributesObject = (obj, path, isChildHead) => {
413
+ for (const [key, val] of Object.entries(obj)) {
414
+ const at = [...path, key];
415
+ if (key === '#') {
416
+ if (Array.isArray(val)) {
417
+ validateChildrenContainer(val, at);
418
+ } else {
419
+ err('BAD_ATTRIBUTES_OBJECT', '`#` must hold an array of children', at);
420
+ }
421
+ continue;
422
+ }
423
+ if (isReserved(key)) {
424
+ err('RESERVED_OPTION', `\`${key}\` is reserved by Jamilih and may not be supplied`, at);
425
+ continue;
426
+ }
427
+ if (isOptionKey(key)) {
428
+ err('MISPLACED_OPTIONS_OBJECT', `\`${key}\` is a root-only option and cannot appear ${isChildHead ? 'on a nested array head' : 'in an attributes object'}`, at);
429
+ continue;
430
+ }
431
+ if (key === 'innerHTML') {
432
+ if (!allowInnerHTML) {
433
+ err('INNERHTML_NOT_ALLOWED', '`innerHTML` is not allowed (`allowInnerHTML: false`)', at);
434
+ } else if (typeof val !== 'string' && !isNullish(val)) {
435
+ err('BAD_ATTRIBUTES_OBJECT', '`innerHTML` must be a string', at);
436
+ }
437
+ continue;
438
+ }
439
+ if (key === '$text') {
440
+ if (typeof val !== 'string') {
441
+ err('BAD_ATTRIBUTES_OBJECT', '`$text` must be a string', at);
442
+ }
443
+ continue;
444
+ }
445
+ if (key === '$attribute') {
446
+ const bad = !Array.isArray(val) || val.length < 2 || val.length > 3 || val.slice(1).some(s => !isNullish(s) && typeof s !== 'string');
447
+ if (bad) {
448
+ err('BAD_ATTRIBUTE_NODE', '`$attribute` must be `[namespace, name, value?]`', at);
449
+ }
450
+ continue;
451
+ }
452
+ if (key === '$DOCTYPE') {
453
+ if (!isPlainObject(val) || typeof (/** @type {{name?: unknown}} */val).name !== 'string') {
454
+ err('BAD_DOCTYPE', '`$DOCTYPE` must be an object with a string `name`', at);
455
+ }
456
+ continue;
457
+ }
458
+ if (key === '$document') {
459
+ validateDocumentObject(val, at);
460
+ continue;
461
+ }
462
+ if (key === '$on') {
463
+ validateOnObject(val, at);
464
+ continue;
465
+ }
466
+ if (key === '$symbol') {
467
+ if (format === 'json') {
468
+ err('NON_JSON_CONSTRUCT', '`$symbol` is not valid in `format: "json"`', at);
469
+ } else {
470
+ const bad = !Array.isArray(val) || val.length !== 2 || typeof val[0] !== 'string' && typeof val[0] !== 'symbol' || typeof val[1] !== 'function' && !isPlainObject(val[1]);
471
+ if (bad) {
472
+ err('BAD_SYMBOL', '`$symbol` must be `[symbol|string, function|object]`', at);
473
+ }
474
+ }
475
+ continue;
476
+ }
477
+ if (key === '$define') {
478
+ if (format === 'json') {
479
+ err('NON_JSON_CONSTRUCT', '`$define` is not valid in `format: "json"`', at);
480
+ } else if (typeof val !== 'function' && !isPlainObject(val) && !Array.isArray(val)) {
481
+ err('BAD_DEFINE', '`$define` must be a function, mixin object, or array', at);
482
+ }
483
+ continue;
484
+ }
485
+ if (key === '$data') {
486
+ if (format === 'json') {
487
+ err('NON_JSON_CONSTRUCT', '`$data` requires an options `Map`; not valid in `format: "json"`', at);
488
+ } else if (val !== true && !Array.isArray(val) && !isMapLike(val) && !isPlainObject(val)) {
489
+ err('BAD_DATA', '`$data` must be `true`, an array, a Map, or a data object', at);
490
+ }
491
+ continue;
492
+ }
493
+ if (key === '$custom') {
494
+ if (val && (typeof val === 'object' || typeof val === 'function') && Object.prototype.propertyIsEnumerable.call(val, '__proto__')) {
495
+ err('BAD_CUSTOM_PROTO', '`$custom` may not define `__proto__`', at);
496
+ }
497
+ if (format === 'json') {
498
+ checkJSONValue(val, at);
499
+ }
500
+ continue;
501
+ }
502
+ if (key === '$shadow') {
503
+ validateShadowObject(val, at);
504
+ continue;
505
+ }
506
+ if (isPluginKey(key)) {
507
+ if (format === 'json') {
508
+ checkJSONValue(val, at);
509
+ }
510
+ continue;
511
+ }
512
+ if (!key.startsWith('$') && key.startsWith('on')) {
513
+ if (format === 'json' && typeof val === 'function') {
514
+ err('NON_JSON_CONSTRUCT', `\`${key}\` handler function is not valid in \`format: "json"\``, at);
515
+ }
516
+ continue;
517
+ }
518
+ if (key.startsWith('$')) {
519
+ checkExtensibleMagicKey(key, val, at);
520
+ continue;
521
+ }
522
+ // Ordinary attribute (including `class`, `style`, `dataset`, ...). In
523
+ // `javascript` mode jml() coerces the value via `setAttribute`, so
524
+ // nothing further is enforced here.
525
+ if (format === 'json') {
526
+ checkJSONValue(val, at);
527
+ }
528
+ }
529
+ };
530
+
531
+ /**
532
+ * A children-array container: each entry is a child node.
533
+ * @param {unknown[]} arr
534
+ * @param {(string|number)[]} path
535
+ * @returns {void}
536
+ */
537
+ const validateChildrenContainer = (arr, path) => {
538
+ arr.forEach((child, j) => {
539
+ const at = [...path, j];
540
+ if (isNullish(child)) {
541
+ err('BAD_CHILD', '`null`/`undefined` is not a valid child', at);
542
+ return;
543
+ }
544
+ const t = typeof child;
545
+ if (TEXTUAL_TYPES.includes(t)) {
546
+ if (format === 'json' && t === 'number' && !Number.isFinite(child)) {
547
+ err('NON_JSON_VALUE', '`NaN`/`Infinity` text child is not valid JSON', at);
548
+ }
549
+ return;
550
+ }
551
+ if (NON_NODE_PRIMITIVE_TYPES.includes(t)) {
552
+ err('BAD_CHILD', `A bare ${t} is not a valid child`, at);
553
+ return;
554
+ }
555
+ if (isDOMNode(child)) {
556
+ if (!allowDOM) {
557
+ err('DOM_NODE_NOT_ALLOWED', 'Raw DOM node children are not allowed', at);
558
+ }
559
+ return;
560
+ }
561
+ if (Array.isArray(child)) {
562
+ if (child.length === 0) {
563
+ err('BAD_CHILD', 'A child array must not be empty', at);
564
+ return;
565
+ }
566
+ const head = child[0];
567
+ if (typeof head !== 'string' && !isPlainObject(head)) {
568
+ err('BAD_CHILD', 'A child array must be headed by a string or object', at);
569
+ return;
570
+ }
571
+ validateArgSequence(child, 0, at, true);
572
+ return;
573
+ }
574
+ if (isPlainObject(child)) {
575
+ const obj = /** @type {Record<string, unknown>} */child;
576
+ if (Object.hasOwn(obj, '#')) {
577
+ if (Array.isArray(obj['#'])) {
578
+ validateChildrenContainer(/** @type {unknown[]} */obj['#'], [...at, '#']);
579
+ } else {
580
+ err('BAD_CHILD', 'A `#` fragment child must hold an array', [...at, '#']);
581
+ }
582
+ return;
583
+ }
584
+ const keys = Object.keys(obj);
585
+ if (keys.length > 0 && keys.every(k => k.startsWith('$'))) {
586
+ // Templating-dialect placeholder child; policy-checked, opaque value.
587
+ flagMisplacedOptions(obj, at);
588
+ for (const k of keys) {
589
+ if (MAGIC_ATTR_BUILTINS.includes(k)) {
590
+ err('BAD_CHILD', `\`${k}\` produces a node and must be array-wrapped (\`[{${k}: ...}]\`), not a bare object child`, [...at, k]);
591
+ } else if (!isReserved(k) && !isOptionKey(k)) {
592
+ checkExtensibleMagicKey(k, obj[k], [...at, k]);
593
+ }
594
+ }
595
+ return;
596
+ }
597
+ err('BAD_CHILD', 'A plain object child must be a `#` fragment or `$`-magic placeholder', at);
598
+ return;
599
+ }
600
+ if (isMapLike(child)) {
601
+ err('BAD_CHILD', 'A Map/WeakMap is not a valid child', at);
602
+ return;
603
+ }
604
+ err('BAD_CHILD', 'Unrecognized child value', at);
605
+ });
606
+ };
607
+
608
+ /**
609
+ * @param {unknown[]} args
610
+ * @param {number} i
611
+ * @param {(string|number)[]} path
612
+ * @returns {number} Number of *additional* args consumed after `i`.
613
+ */
614
+ const validateStringArg = (args, i, path) => {
615
+ const arg = /** @type {string} */args[i];
616
+ if (arg === '' || !Object.hasOwn(SPECIAL_STRING_ARGC, arg)) {
617
+ // Fragment marker or an ordinary element name; nothing to consume here.
618
+ return 0;
619
+ }
620
+ const need = SPECIAL_STRING_ARGC[(/** @type {keyof typeof SPECIAL_STRING_ARGC} */arg)];
621
+ for (let k = 1; k <= need; k++) {
622
+ const follow = args[i + k];
623
+ if (isNullish(follow) || typeof follow !== 'string' && typeof follow !== 'number') {
624
+ err(arg === '?' ? 'BAD_PROCESSING_INSTRUCTION' : 'BAD_SPECIAL_ARG', `\`${arg}\` must be followed by ${need} string argument${need > 1 ? 's' : ''}`, [...path, i]);
625
+ return 0;
626
+ }
627
+ }
628
+ return need;
629
+ };
630
+
631
+ /**
632
+ * @param {unknown[]} args
633
+ * @param {number} start
634
+ * @param {(string|number)[]} path
635
+ * @param {boolean} [headIsChild] The object at `start`, if any, is the head
636
+ * of a nested child array (so root-only options there are misplaced).
637
+ * @returns {void}
638
+ */
639
+ const validateArgSequence = (args, start, path, headIsChild = false) => {
640
+ let i = start;
641
+ while (i < args.length) {
642
+ const arg = args[i];
643
+ const at = [...path, i];
644
+ if (arg === null) {
645
+ if (i !== args.length - 1) {
646
+ err('MISPLACED_NULL', '`null` is only allowed as the final argument', at);
647
+ }
648
+ i += 1;
649
+ continue;
650
+ }
651
+ if (arg === undefined) {
652
+ err('BAD_FIRST_ARG', '`undefined` is not a valid Jamilih argument', at);
653
+ i += 1;
654
+ continue;
655
+ }
656
+ const t = typeof arg;
657
+ if (t === 'string') {
658
+ i += 1 + validateStringArg(args, i, path);
659
+ continue;
660
+ }
661
+ if (t !== 'object') {
662
+ err('UNKNOWN_TYPE', `Unexpected ${t} in argument position`, at);
663
+ i += 1;
664
+ continue;
665
+ }
666
+ if (isDOMNode(arg)) {
667
+ if (!allowDOM) {
668
+ err('DOM_NODE_NOT_ALLOWED', 'Raw DOM nodes are not allowed here', at);
669
+ }
670
+ } else if (Array.isArray(arg)) {
671
+ validateChildrenContainer(arg, at);
672
+ } else if (isMapLike(arg)) {
673
+ err('DOM_NODE_NOT_ALLOWED', 'A Map/WeakMap is not a valid Jamilih argument', at);
674
+ } else if (isPlainObject(arg)) {
675
+ validateAttributesObject(/** @type {Record<string, unknown>} */arg, at, headIsChild && i === start);
676
+ } else {
677
+ err('UNKNOWN_TYPE', 'Unrecognized argument type', at);
678
+ }
679
+ i += 1;
680
+ }
681
+ };
682
+
683
+ /**
684
+ * @param {Record<string, unknown>} obj
685
+ * @param {(string|number)[]} path
686
+ * @returns {void}
687
+ */
688
+ const validateOptionsObject = (obj, path) => {
689
+ if (emptyPolicy) {
690
+ err('OPTIONS_OBJECT_NOT_ALLOWED', 'A leading options object is not permitted (`allowableOptions: []`)', path);
691
+ return;
692
+ }
693
+ for (const [key, val] of Object.entries(obj)) {
694
+ if (isReserved(key)) {
695
+ err('RESERVED_OPTION', `\`${key}\` is reserved by Jamilih and may not be supplied`, [...path, key]);
696
+ continue;
697
+ }
698
+ if (isOptionKey(key)) {
699
+ if (!allowAny && !allowable.has(key)) {
700
+ err('DISALLOWED_OPTION', `Option \`${key}\` is not permitted by \`allowableOptions\``, [...path, key]);
701
+ } else if (key === '$plugins') {
702
+ validatePluginsOption(val, [...path, key]);
703
+ } else if (key === '$Map') {
704
+ validateMapOption(val, [...path, key]);
705
+ }
706
+ continue;
707
+ }
708
+ if (!key.startsWith('$')) {
709
+ err('ATTRIBUTES_BEFORE_ELEMENT', 'A genuine attribute may not appear on an object before an element', [...path, key]);
710
+ continue;
711
+ }
712
+ checkExtensibleMagicKey(key, val, [...path, key]);
713
+ }
714
+ };
715
+
716
+ /**
717
+ * @param {unknown} struct
718
+ * @returns {void}
719
+ */
720
+ const validateStructure = struct => {
721
+ if (!Array.isArray(struct)) {
722
+ err('NOT_ARRAY', 'Jamilih structure must be an array', []);
723
+ return;
724
+ }
725
+ if (struct.length === 0) {
726
+ err('EMPTY_ARRAY', 'Jamilih structure must not be empty', []);
727
+ return;
728
+ }
729
+ let argStart = 0;
730
+ const first = struct[0];
731
+ if (isPlainObject(first) && !isDOMNode(first)) {
732
+ const firstObj = /** @type {Record<string, unknown>} */first;
733
+ const keys = Object.keys(firstObj);
734
+ if (possibleOptions.some(k => keys.includes(k))) {
735
+ validateOptionsObject(firstObj, ['0']);
736
+ argStart = 1;
737
+ } else if (keys.some(k => RESERVED_NAMES.includes(k))) {
738
+ for (const k of keys) {
739
+ if (RESERVED_NAMES.includes(k)) {
740
+ err('RESERVED_OPTION', `\`${k}\` is reserved by Jamilih and may not be supplied`, ['0', k]);
741
+ }
742
+ }
743
+ argStart = 1;
744
+ } else if (keys.some(k => FIRST_ARG_NODE_KEYS.includes(k))) ; else if (keys.every(k => k.startsWith('$'))) {
745
+ // Dialect-only / empty object: base Jamilih skips it.
746
+ argStart = 1;
747
+ } else {
748
+ err('ATTRIBUTES_BEFORE_ELEMENT', 'A genuine attribute may not appear on an object before an element', ['0']);
749
+ argStart = 1;
750
+ }
751
+ }
752
+ validateArgSequence(struct, argStart, []);
753
+ };
754
+ try {
755
+ if (domConflict) {
756
+ err('OPTION_CONFLICT', '`allowDOM: true` conflicts with `format: "json"`; DOM nodes stay rejected', []);
757
+ }
758
+ validateStructure(structure);
759
+ } catch (e) {
760
+ /* c8 ignore next 3 -- defensive: only the fail-fast sentinel is caught here */
761
+ if (e !== FAIL_FAST) {
762
+ throw e;
763
+ }
764
+ }
765
+ return {
766
+ valid: errors.length === 0,
767
+ errors
768
+ };
769
+ };
770
+
771
+ /**
772
+ * Boolean predicate wrapper around {@link validateJamilih}.
773
+ * @param {unknown} structure
774
+ * @param {ValidateJamilihOptions} [options]
775
+ * @returns {boolean}
776
+ */
777
+ const isValidJamilih = (structure, options) => validateJamilih(structure, options).valid;
778
+
779
+ /* eslint-disable unicorn/prefer-global-this -- Easier */
780
+ /* eslint-disable unicorn/no-break-in-nested-loop -- Performance to avoid function? */
781
+ /* eslint-disable sonarjs/no-control-regex -- Intentional */
782
+ /* eslint-disable unicorn/no-top-level-assignment-in-function -- Only need module level */
2
783
  /*
3
784
  Possible todos:
4
785
  0. Add XSLT to JML-string stylesheet (or even vice versa)
5
- 0. IE problem: Add JsonML code to handle name attribute (during element creation)
6
- 0. Element-specific: IE object-param handling
7
786
 
8
787
  Todos inspired by JsonML: https://github.com/mckamey/jsonml/blob/master/jsonml-html.js
9
-
10
- 0. duplicate attributes?
11
788
  0. expand ATTR_MAP
12
- 0. equivalent of markup, to allow strings to be embedded within an object (e.g., {$value: '<div>id</div>'}); advantage over innerHTML in that it wouldn't need to work as the entire contents (nor destroy any existing content or handlers)
13
- 0. More validation?
14
- 0. JsonML DOM Level 0 listener
15
- 0. Whitespace trimming?
16
-
17
- JsonML element-specific:
18
- 0. table appending
19
- 0. canHaveChildren necessary? (attempts to append to script and img)
20
789
 
21
790
  Other Todos:
22
791
  0. Note to self: Integrate research from other jml notes
23
- 0. Allow Jamilih to be seeded with an existing element, so as to be able to add/modify attributes and children
792
+ 0. Allow Jamilih to be seeded with an existing element, so as to be able to
793
+ add/modify attributes and children
24
794
  0. Allow array as single first argument
25
- 0. Settle on whether need to use null as last argument to return array (or fragment) or other way to allow appending? Options object at end instead to indicate whether returning array, fragment, first element, etc.?
795
+ 0. Settle on whether need to use null as last argument to return array (or
796
+ fragment) or other way to allow appending? Options object at end instead
797
+ to indicate whether returning array, fragment, first element, etc.?
26
798
  0. Allow building of generic XML (pass configuration object)
27
799
  0. Allow building content internally as a string (though allowing DOM methods, etc.?)
28
800
  0. Support JsonML empty string element name to represent fragments?
29
- 0. Redo browser testing of jml (including ensuring IE7 can work even if test framework can't work)
801
+ 0. Redo browser testing of jml
30
802
  */
31
803
 
804
+
805
+ /**
806
+ * @typedef {Window & {DocumentFragment: typeof DocumentFragment}} HTMLWindow
807
+ */
808
+
809
+ /**
810
+ * @typedef {unknown} ArbitraryValue
811
+ */
812
+
813
+ /**
814
+ * @typedef {unknown} StoredValue
815
+ */
816
+
817
+ /* eslint-disable jsdoc/reject-any-type -- user-defined callback arguments */
32
818
  /**
33
- * @typedef {Window & {DocumentFragment: any}} HTMLWindow
819
+ * @typedef {any} UserArg
34
820
  */
821
+ /**
822
+ * @typedef {any} ElementExpando
823
+ */
824
+ /* eslint-enable jsdoc/reject-any-type */
35
825
 
36
826
  /**
37
- * @typedef {any} ArbitraryValue
827
+ * @typedef {HTMLElement & {[key: string]: ElementExpando}} ExpandoHTMLElement
38
828
  */
39
829
 
40
830
  /**
@@ -56,7 +846,7 @@ Other Todos:
56
846
  */
57
847
 
58
848
  /**
59
- * @type {import('jsdom').DOMWindow|HTMLWindow|undefined}
849
+ * @type {import('jsdom').DOMWindow|HTMLWindow|typeof globalThis|undefined}
60
850
  */
61
851
  let win;
62
852
 
@@ -66,16 +856,17 @@ if (typeof window !== 'undefined' && window) {
66
856
  }
67
857
 
68
858
  /* c8 ignore next */
69
- let doc = typeof document !== 'undefined' && document || ((_win = win) === null || _win === void 0 ? void 0 : _win.document);
859
+ let doc = typeof document !== 'undefined' && document || win?.document;
70
860
 
71
861
  // STATIC PROPERTIES
72
862
 
73
- const possibleOptions = ['$plugins',
74
- // '$mode', // Todo (SVG/XML)
75
- // '$state', // Used internally
76
- '$map' // Add any other options here
77
- ];
78
-
863
+ /**
864
+ * Brands `opts` objects that `jml()` created or adopted internally, so a
865
+ * user-supplied `$state` (which would corrupt root detection) can be told
866
+ * apart from the internal traversal marker of the same name.
867
+ * @type {WeakSet<object>}
868
+ */
869
+ const internalOpts = new WeakSet();
79
870
  const NS_HTML = 'http://www.w3.org/1999/xhtml',
80
871
  hyphenForCamelCase = /-([a-z])/gu;
81
872
  const ATTR_MAP = new Map([['maxlength', 'maxLength'], ['minlength', 'minLength'], ['readonly', 'readOnly']]);
@@ -88,7 +879,7 @@ const BOOL_ATTS = ['checked', 'defaultChecked', 'defaultSelected', 'disabled', '
88
879
  'readOnly', 'selected'];
89
880
 
90
881
  // From JsonML
91
- const ATTR_DOM = [...BOOL_ATTS, 'accessKey',
882
+ const ATTR_DOM = new Set([...BOOL_ATTS, 'accessKey',
92
883
  // HTMLElement
93
884
  'async', 'autocapitalize',
94
885
  // HTMLElement
@@ -106,17 +897,17 @@ const ATTR_DOM = [...BOOL_ATTS, 'accessKey',
106
897
  // HTMLElement
107
898
  'translate',
108
899
  // HTMLElement
109
- 'value', 'willvalidate'];
900
+ 'value', 'willvalidate']);
110
901
  // Todo: Add more to this as useful for templating
111
902
  // to avoid setting through nullish value
112
- const NULLABLES = ['autocomplete', 'dir',
903
+ const NULLABLES = new Set(['autocomplete', 'dir',
113
904
  // HTMLElement
114
905
  'integrity',
115
906
  // script, link
116
907
  'lang',
117
908
  // HTMLElement
118
909
  'max', 'min', 'minLength', 'maxLength', 'title' // HTMLElement
119
- ];
910
+ ]);
120
911
 
121
912
  /**
122
913
  * @param {string} sel
@@ -137,19 +928,9 @@ const $$ = sel => {
137
928
  if (!doc) {
138
929
  throw new Error('No document object');
139
930
  }
140
- return [... /** @type {NodeListOf<HTMLElement>} */doc.querySelectorAll(sel)];
931
+ return [...(/** @type {NodeListOf<HTMLElement>} */doc.querySelectorAll(sel))];
141
932
  };
142
933
 
143
- /**
144
- * Retrieve the (lower-cased) HTML name of a node.
145
- * @static
146
- * @param {Node} node The HTML node
147
- * @returns {string} The lower-cased node name
148
- */
149
- function _getHTMLNodeName(node) {
150
- return node.nodeName && node.nodeName.toLowerCase();
151
- }
152
-
153
934
  /**
154
935
  * @private
155
936
  * @static
@@ -159,7 +940,7 @@ function _getHTMLNodeName(node) {
159
940
  * @returns {void}
160
941
  */
161
942
  function _appendNode(parent, child) {
162
- const parentName = _getHTMLNodeName(parent);
943
+ const parentName = parent.nodeName?.toLowerCase();
163
944
  if (parentName === 'template') {
164
945
  /** @type {HTMLTemplateElement} */parent.content.append(child);
165
946
  return;
@@ -170,9 +951,10 @@ function _appendNode(parent, child) {
170
951
  /**
171
952
  * Attach event in a cross-browser fashion.
172
953
  * @static
173
- * @param {HTMLElement} el DOM element to which to attach the event
954
+ * @template {HTMLElement} T
955
+ * @param {T} el DOM element to which to attach the event
174
956
  * @param {string} type The DOM event (without 'on') to attach to the element
175
- * @param {(evt: Event & {target: HTMLElement}) => void} handler The event handler to attach to the element
957
+ * @param {(evt: Event & {target: T}) => void} handler The event handler to attach to the element
176
958
  * @param {boolean} [capturing] Whether or not the event should be
177
959
  * capturing (W3C-browsers only); default is false; NOT IN USE
178
960
  * @returns {void}
@@ -183,13 +965,13 @@ function _addEvent(el, type, handler, capturing) {
183
965
  }
184
966
 
185
967
  /**
186
- * Creates a text node of the result of resolving an entity or character reference.
187
- * @param {'entity'|'decimal'|'hexadecimal'} type Type of reference
188
- * @param {string} prefix Text to prefix immediately after the "&"
189
- * @param {string} arg The body of the reference
190
- * @throws {TypeError}
191
- * @returns {Text} The text node of the resolved reference
192
- */
968
+ * Creates a text node of the result of resolving an entity or character reference.
969
+ * @param {'entity'|'decimal'|'hexadecimal'} type Type of reference
970
+ * @param {string} prefix Text to prefix immediately after the "&"
971
+ * @param {string} arg The body of the reference
972
+ * @throws {TypeError}
973
+ * @returns {Text} The text node of the resolved reference
974
+ */
193
975
  function _createSafeReference(type, prefix, arg) {
194
976
  /* c8 ignore next 3 */
195
977
  if (!doc) {
@@ -204,14 +986,15 @@ function _createSafeReference(type, prefix, arg) {
204
986
  // Todo: No workaround for XML?
205
987
  // eslint-disable-next-line no-unsanitized/property
206
988
  elContainer.innerHTML = '&' + prefix + arg + ';';
989
+ // eslint-disable-next-line unicorn/prefer-dom-node-html-methods -- No Safari support
207
990
  return doc.createTextNode(elContainer.innerHTML);
208
991
  }
209
992
 
210
993
  /**
211
- * @param {string} n0 Whole expression match (including "-")
212
- * @param {string} n1 Lower-case letter match
213
- * @returns {string} Uppercased letter
214
- */
994
+ * @param {string} n0 Whole expression match (including "-")
995
+ * @param {string} n1 Lower-case letter match
996
+ * @returns {string} Uppercased letter
997
+ */
215
998
  function _upperCase(n0, n1) {
216
999
  return n1.toUpperCase();
217
1000
  }
@@ -225,24 +1008,32 @@ function _isNullish(o) {
225
1008
  return o === null || o === undefined;
226
1009
  }
227
1010
 
1011
+ /**
1012
+ * @param {unknown} item
1013
+ * @returns {item is HTMLElement}
1014
+ */
1015
+ function _isHTMLElement(item) {
1016
+ return Boolean(item && typeof item === 'object' && 'nodeType' in item && item.nodeType === 1);
1017
+ }
1018
+
228
1019
  // Todo: Make as public utility, but also return types for undefined, boolean, number, document, etc.
229
1020
  /**
230
- * @private
231
- * @static
232
- * @param {string|JamilihAttributes|JamilihArray|JamilihChildren|
233
- * JamilihDocumentFragment|JamilihAttributeNode|
234
- * JamilihOptions|HTMLElement|Document|DocumentFragment|null|undefined} item
235
- * @returns {"string"|"null"|"array"|"element"|"fragment"|"object"|
236
- * "symbol"|"bigint"|"function"|"number"|"boolean"|"undefined"|
237
- * "document"|"non-container node"}
238
- */
1021
+ * @private
1022
+ * @static
1023
+ * @param {string|JamilihAttributes|JamilihArray|JamilihChildren|
1024
+ * JamilihDocumentFragment|JamilihAttributeNode|
1025
+ * JamilihOptions|JamilihDialectObject|HTMLElement|Document|DocumentFragment|
1026
+ * null|undefined} item
1027
+ * @returns {"string"|"null"|"array"|"element"|"fragment"|"object"|
1028
+ * "symbol"|"bigint"|"function"|"number"|"boolean"|"undefined"|
1029
+ * "document"|"processing-instruction"|"non-container node"}
1030
+ */
239
1031
  function _getType(item) {
240
- const type = typeof item;
241
-
242
1032
  // Appease TS
243
1033
  if (typeof item === 'string' || typeof item === 'undefined') {
244
1034
  return 'string';
245
1035
  }
1036
+ const type = typeof item;
246
1037
  switch (type) {
247
1038
  case 'object':
248
1039
  if (item === null) {
@@ -255,6 +1046,8 @@ function _getType(item) {
255
1046
  switch (item.nodeType) {
256
1047
  case 1:
257
1048
  return 'element';
1049
+ case 7:
1050
+ return 'processing-instruction';
258
1051
  case 9:
259
1052
  return 'document';
260
1053
  case 11:
@@ -270,23 +1063,31 @@ function _getType(item) {
270
1063
  }
271
1064
 
272
1065
  /**
273
- * @private
274
- * @static
275
- * @param {DocumentFragment} frag
276
- * @param {Node} node
277
- * @returns {DocumentFragment}
278
- */
1066
+ * @private
1067
+ * @static
1068
+ * @param {DocumentFragment} frag
1069
+ * @param {Node} node
1070
+ * @returns {DocumentFragment}
1071
+ */
279
1072
  function _fragReducer(frag, node) {
280
1073
  frag.append(node);
281
1074
  return frag;
282
1075
  }
283
1076
 
284
1077
  /**
285
- * @private
286
- * @static
287
- * @param {Object<string, string>} xmlnsObj
288
- * @returns {(...n: string[]) => string}
289
- */
1078
+ * @param {string} str
1079
+ * @returns {string}
1080
+ */
1081
+ function escapeReplacer(str) {
1082
+ return str.replaceAll('$', '$$$$');
1083
+ }
1084
+
1085
+ /**
1086
+ * @private
1087
+ * @static
1088
+ * @param {Object<string, string>} xmlnsObj
1089
+ * @returns {(...n: string[]) => string}
1090
+ */
290
1091
  function _replaceDefiner(xmlnsObj) {
291
1092
  /**
292
1093
  * @param {string[]} n
@@ -294,10 +1095,10 @@ function _replaceDefiner(xmlnsObj) {
294
1095
  */
295
1096
  return function (...n) {
296
1097
  const n0 = n[0];
297
- let retStr = xmlnsObj[''] ? ' xmlns="' + xmlnsObj[''] + '"' : n0; // Preserve XHTML
1098
+ let retStr = xmlnsObj[''] ? ' xmlns="' + escapeReplacer(xmlnsObj['']) + '"' : n0; // Preserve XHTML
298
1099
  for (const [ns, xmlnsVal] of Object.entries(xmlnsObj)) {
299
1100
  if (ns !== '') {
300
- retStr += ' xmlns:' + ns + '="' + xmlnsVal + '"';
1101
+ retStr += ' xmlns:' + ns + '="' + escapeReplacer(xmlnsVal) + '"';
301
1102
  }
302
1103
  }
303
1104
  return retStr;
@@ -320,38 +1121,49 @@ function _replaceDefiner(xmlnsObj) {
320
1121
  function _childrenToJML(node) {
321
1122
  return function (childNodeJML, i) {
322
1123
  const cn = node.childNodes[i];
323
- const j = Array.isArray(childNodeJML) ? jml(... /** @type {JamilihArray} */childNodeJML) : jml(childNodeJML);
1124
+ const j = Array.isArray(childNodeJML) ? jml(...(/** @type {JamilihArray} */childNodeJML)) : jml(childNodeJML);
324
1125
  cn.replaceWith(j);
325
1126
  };
326
1127
  }
327
1128
 
1129
+ /**
1130
+ * The `$`-prefixed properties a templating dialect has registered on the
1131
+ * globally augmentable {@link JamilihDialectProperties} interface (see
1132
+ * `src/jamilih-dialect.d.ts`). This is the escape hatch that lets those keys
1133
+ * appear on a leading Jamilih object and as a bare `$`-only child; a `$`-key
1134
+ * that has not been registered (and any non-`$` attribute) stays a type
1135
+ * error. Resolves to `never` until the interface is augmented, so it adds
1136
+ * nothing to a union in the default (strict) case.
1137
+ * @typedef {[keyof JamilihDialectProperties] extends [never] ? never : Partial<JamilihDialectProperties>} JamilihDialectObject
1138
+ */
1139
+
328
1140
  /**
329
1141
  * Keep this in sync with `JamilihArray`'s first argument (minus `Document`).
330
1142
  * @typedef {JamilihDoc|JamilihDoctype|JamilihTextNode|
331
- * JamilihAttributeNode|JamilihOptions|ElementName|HTMLElement|
332
- * JamilihDocumentFragment
333
- * } JamilihFirstArg
334
- */
1143
+ * JamilihAttributeNode|JamilihOptions|JamilihDialectObject|ElementName|
1144
+ * HTMLElement|JamilihDocumentFragment
1145
+ * } JamilihFirstArg
1146
+ */
335
1147
 
336
1148
  /**
337
- * @callback JamilihAppender
338
- * @param {JamilihArray|JamilihFirstArg|Node|TextNodeString} childJML
339
- * @returns {void}
340
- */
1149
+ * @callback JamilihAppender
1150
+ * @param {JamilihArray|JamilihArrayLike|JamilihFirstArg|Node|TextNodeString} childJML
1151
+ * @returns {void}
1152
+ */
341
1153
 
342
1154
  /**
343
- * @private
344
- * @static
345
- * @param {ParentNode} node
346
- * @returns {JamilihAppender}
347
- */
1155
+ * @private
1156
+ * @static
1157
+ * @param {ParentNode} node
1158
+ * @returns {JamilihAppender}
1159
+ */
348
1160
  function _appendJML(node) {
349
1161
  return function (childJML) {
350
1162
  if (typeof childJML === 'string' || typeof childJML === 'number') {
351
1163
  throw new TypeError('Unexpected text string/number in the head');
352
1164
  }
353
1165
  if (Array.isArray(childJML)) {
354
- node.append(jml(...childJML));
1166
+ node.append(jml(...(/** @type {JamilihArray} */childJML)));
355
1167
  } else if (typeof childJML === 'object' && 'nodeType' in childJML) {
356
1168
  node.append(childJML);
357
1169
  } else {
@@ -361,23 +1173,23 @@ function _appendJML(node) {
361
1173
  }
362
1174
 
363
1175
  /**
364
- * @callback appender
365
- * @param {JamilihArray|JamilihFirstArg|Node|TextNodeString} childJML
366
- * @returns {void}
367
- */
1176
+ * @callback appender
1177
+ * @param {JamilihArray|JamilihArrayLike|JamilihFirstArg|Node|TextNodeString} childJML
1178
+ * @returns {void}
1179
+ */
368
1180
 
369
1181
  /**
370
- * @private
371
- * @static
372
- * @param {ParentNode} node
373
- * @returns {appender}
374
- */
1182
+ * @private
1183
+ * @static
1184
+ * @param {ParentNode} node
1185
+ * @returns {appender}
1186
+ */
375
1187
  function _appendJMLOrText(node) {
376
1188
  return function (childJML) {
377
1189
  if (typeof childJML === 'string' || typeof childJML === 'number') {
378
1190
  node.append(String(childJML));
379
1191
  } else if (Array.isArray(childJML)) {
380
- node.append(jml(...childJML));
1192
+ node.append(jml(...(/** @type {JamilihArray} */childJML)));
381
1193
  } else if (typeof childJML === 'object' && 'nodeType' in childJML) {
382
1194
  node.append(childJML);
383
1195
  } else {
@@ -387,9 +1199,9 @@ function _appendJMLOrText(node) {
387
1199
  }
388
1200
 
389
1201
  /**
390
- * @private
391
- * @static
392
- */
1202
+ * @private
1203
+ * @static
1204
+ */
393
1205
  /*
394
1206
  function _DOMfromJMLOrString (childNodeJML) {
395
1207
  if (typeof childNodeJML === 'string') {
@@ -400,9 +1212,9 @@ function _DOMfromJMLOrString (childNodeJML) {
400
1212
  */
401
1213
 
402
1214
  /**
403
- * @typedef {HTMLElement|DocumentFragment|Comment|Attr|
404
- * Text|Document|DocumentType|ProcessingInstruction|CDATASection} JamilihReturn
405
- */
1215
+ * @typedef {HTMLElement|DocumentFragment|Comment|Attr|
1216
+ * Text|Document|DocumentType|ProcessingInstruction|CDATASection} JamilihReturn
1217
+ */
406
1218
  // 'string|JamilihOptions|JamilihDocumentFragment|JamilihAttributes|(string|JamilihArray)[]
407
1219
 
408
1220
  /**
@@ -419,11 +1231,11 @@ function _DOMfromJMLOrString (childNodeJML) {
419
1231
 
420
1232
  /**
421
1233
  * @typedef {{
422
- * open?: boolean|ShadowRootJamilihArrayContainer,
423
- * closed?: boolean|ShadowRootJamilihArrayContainer,
424
- * template?: string|HTMLTemplateElement|TemplateJamilihArray,
425
- * content?: ShadowRootJamilihArrayContainer|DocumentFragment
426
- * }} JamilihShadowRootObject
1234
+ * open?: boolean|ShadowRootJamilihArrayContainer,
1235
+ * closed?: boolean|ShadowRootJamilihArrayContainer,
1236
+ * template?: string|HTMLTemplateElement|TemplateJamilihArray,
1237
+ * content?: ShadowRootJamilihArrayContainer|DocumentFragment
1238
+ * }} JamilihShadowRootObject
427
1239
  */
428
1240
 
429
1241
  /**
@@ -445,18 +1257,21 @@ function _DOMfromJMLOrString (childNodeJML) {
445
1257
  */
446
1258
 
447
1259
  /**
448
- * @typedef {(this: HTMLElement, event: Event & {target: HTMLElement}) => void} EventHandler
1260
+ * @template {HTMLElement} [T=HTMLElement]
1261
+ * @typedef {(this: T, event: Event & {target: T}) => void} EventHandler
449
1262
  */
450
1263
 
451
1264
  /**
1265
+ * @template {HTMLElement} [T=HTMLElement]
452
1266
  * @typedef {{
453
- * [key: string]: EventHandler|[EventHandler, boolean]
1267
+ * [key: string]: EventHandler<T>|[EventHandler<T>, boolean]
454
1268
  * }} OnAttributeObject
455
1269
  */
456
1270
 
457
1271
  /**
1272
+ * @template {HTMLElement} [T=HTMLElement]
458
1273
  * @typedef {{
459
- * $on?: OnAttributeObject|null
1274
+ * $on?: OnAttributeObject<T>|null
460
1275
  * }} OnAttribute
461
1276
  */
462
1277
 
@@ -468,7 +1283,6 @@ function _DOMfromJMLOrString (childNodeJML) {
468
1283
  * @typedef {((this: HTMLElement, event?: Event) => void)} HandlerAttributeValue
469
1284
  */
470
1285
 
471
- /* eslint-disable jsdoc/valid-types -- jsdoc-type-pratt-parser Bug */
472
1286
  /**
473
1287
  * @typedef {{
474
1288
  * [key: string]: HandlerAttributeValue
@@ -492,23 +1306,22 @@ function _DOMfromJMLOrString (childNodeJML) {
492
1306
  */
493
1307
 
494
1308
  /**
495
- * @typedef {{[key: string]: string|number|boolean|((this: DefineMixin, ...args: any[]) => any)}} DefineMixin
1309
+ * @typedef {{[key: string]: unknown}} DefineMixin
496
1310
  */
497
1311
 
498
1312
  /**
499
1313
  * @typedef {{
500
1314
  * new (): HTMLElement;
501
- * prototype: HTMLElement & {[key: string]: any}
1315
+ * prototype: HTMLElement
502
1316
  * }} DefineConstructor
503
1317
  */
504
- /* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/131 */
505
1318
 
506
1319
  /**
507
1320
  * @typedef {(this: HTMLElement) => void} DefineUserConstructor
508
1321
  */
509
1322
 
510
1323
  /**
511
- * @typedef {[DefineConstructor|DefineUserConstructor|DefineMixin, DefineOptions?]|[DefineConstructor|DefineUserConstructor, DefineMixin?, DefineOptions?]} DefineObjectArray
1324
+ * @typedef {[DefineConstructor|DefineUserConstructor|DefineMixin, DefineOptions?]|[DefineMixin, DefineConstructor]|[DefineConstructor|DefineUserConstructor, DefineMixin?, DefineOptions?]} DefineObjectArray
512
1325
  */
513
1326
 
514
1327
  /**
@@ -516,11 +1329,27 @@ function _DOMfromJMLOrString (childNodeJML) {
516
1329
  */
517
1330
 
518
1331
  /**
519
- * @typedef {{elem?: HTMLElement, [key: string]: any}} SymbolObject
1332
+ * @template [T=ArbitraryValue]
1333
+ * @template {HTMLElement} [U=HTMLElement]
1334
+ * @typedef {T & {elem?: U}} SymbolObject
1335
+ */
1336
+
1337
+ /**
1338
+ * @template {HTMLElement} [T=HTMLElement]
1339
+ * @typedef {(this: T, ...args: UserArg[]) => UserArg} SymbolMethod
1340
+ */
1341
+
1342
+ /**
1343
+ * @typedef {(...args: UserArg[]) => UserArg} BoundSymbolMethod
1344
+ */
1345
+
1346
+ /**
1347
+ * @template {HTMLElement} [T=HTMLElement]
1348
+ * @typedef {[symbol|string, SymbolMethod<T>|SymbolObject<ArbitraryValue, T>]} SymbolArray
520
1349
  */
521
1350
 
522
1351
  /**
523
- * @typedef {[symbol|string, ((this: HTMLElement, ...args: any[]) => any)|SymbolObject]} SymbolArray
1352
+ * @typedef {BoundSymbolMethod|SymbolObject|ArbitraryValue} SymbolResult
524
1353
  */
525
1354
 
526
1355
  /**
@@ -528,7 +1357,7 @@ function _DOMfromJMLOrString (childNodeJML) {
528
1357
  */
529
1358
 
530
1359
  /**
531
- * @typedef {[string, object]|string|{[key: string]: any}} PluginValue
1360
+ * @typedef {[string, object]|string|object} PluginValue
532
1361
  */
533
1362
 
534
1363
  /**
@@ -543,15 +1372,15 @@ function _DOMfromJMLOrString (childNodeJML) {
543
1372
 
544
1373
  /**
545
1374
  * @typedef {{
546
- * [key: string]: string|number|((this: HTMLElement, ...args: any[]) => any)
547
- * }} DataAttributeObject
548
- */
1375
+ * [key: string]: string|number|((this: HTMLElement, ...args: UserArg[]) => UserArg)
1376
+ * }} DataAttributeObject
1377
+ */
549
1378
 
550
1379
  /**
551
1380
  * @typedef {{
552
- * $data?: true|string[]|Map<any, any>|WeakMap<any, any>|DataAttributeObject|
1381
+ * $data?: true|string[]|Map<HTMLElement, UserArg>|WeakMap<HTMLElement, UserArg>|DataAttributeObject|
553
1382
  * [undefined, DataAttributeObject]|
554
- * [Map<any, any>|WeakMap<any, any>|undefined, DataAttributeObject]
1383
+ * [Map<HTMLElement, UserArg>|WeakMap<HTMLElement, UserArg>|undefined, DataAttributeObject]
555
1384
  * }} DataAttribute
556
1385
  */
557
1386
 
@@ -573,18 +1402,16 @@ function _DOMfromJMLOrString (childNodeJML) {
573
1402
  * }} JamilihShadowRootAttribute
574
1403
  */
575
1404
 
576
- /* eslint-disable jsdoc/valid-types -- jsdoc-type-pratt-parser Bug */
577
1405
  /**
578
1406
  * @typedef {{
579
1407
  * is?: string|null,
580
1408
  * $define?: DefineObject
581
1409
  * }} DefineAttribute
582
1410
  */
583
- /* eslint-enable jsdoc/valid-types -- jsdoc-type-pratt-parser Bug */
584
1411
 
585
1412
  /**
586
1413
  * @typedef {{
587
- * $custom?: {[key: string]: any}
1414
+ * $custom?: {[key: string]: unknown}
588
1415
  * }} CustomAttribute
589
1416
  */
590
1417
 
@@ -614,6 +1441,11 @@ function _DOMfromJMLOrString (childNodeJML) {
614
1441
  /**
615
1442
  * @typedef {{
616
1443
  * title?: string,
1444
+ * xmlDeclaration?: {
1445
+ * version: string,
1446
+ * encoding: string,
1447
+ * standalone: boolean
1448
+ * },
617
1449
  * childNodes?: JamilihChildType[],
618
1450
  * $DOCTYPE?: JamilihDocumentType,
619
1451
  * head?: JamilihChildren
@@ -652,21 +1484,33 @@ function _DOMfromJMLOrString (childNodeJML) {
652
1484
  */
653
1485
 
654
1486
  /**
1487
+ * `JamilihDialectObject` is deliberately absent: this type is reused for the
1488
+ * attributes position, where every recognized `$`-magic key must keep its
1489
+ * specific value type.
1490
+ * @typedef {Document|ElementName|HTMLElement|DocumentFragment|
1491
+ * JamilihDocumentFragment|JamilihDoc|JamilihDoctype|JamilihTextNode|
1492
+ * JamilihAttributeNode} JamilihFirstArgument
1493
+ */
1494
+
1495
+ /**
1496
+ * Array-form Jamilih input whose tuple positions were widened by operations
1497
+ * such as `Array#map`.
655
1498
  * @typedef {(
656
- * JamilihArray|TextNodeString|HTMLElement|Comment|ProcessingInstruction|
657
- * Text|DocumentFragment|JamilihProcessingInstruction|JamilihDocumentFragment|
658
- * PluginReference
659
- * )[]} JamilihChildren
1499
+ * JamilihFirstArg|JamilihAttributes|JamilihArrayLike|TextNodeString|
1500
+ * ShadowRoot|null
1501
+ * )[]} JamilihArrayLike
660
1502
  */
661
1503
 
662
- // Todo: DocumentType, Comment, ProcessingInstruction, Text
663
- // Todo: JamilihCDATANode, JamilihComment, JamilihProcessingInstruction
664
1504
  /**
665
- * @typedef {Document|ElementName|HTMLElement|DocumentFragment|
666
- * JamilihDocumentFragment|JamilihDoc|JamilihDoctype|JamilihTextNode|
667
- * JamilihAttributeNode} JamilihFirstArgument
1505
+ * @typedef {(
1506
+ * JamilihArray|JamilihArrayLike|TextNodeString|HTMLElement|Comment|
1507
+ * ProcessingInstruction|Text|DocumentFragment|JamilihProcessingInstruction|
1508
+ * JamilihDocumentFragment|PluginReference|JamilihDialectObject
1509
+ * )[]} JamilihChildren
668
1510
  */
669
1511
 
1512
+ // Todo: DocumentType, Comment, ProcessingInstruction, Text
1513
+ // Todo: JamilihCDATANode, JamilihComment, JamilihProcessingInstruction
670
1514
  /**
671
1515
  * This would be clearer with overrides, but using as typedef.
672
1516
  *
@@ -687,7 +1531,7 @@ function _DOMfromJMLOrString (childNodeJML) {
687
1531
  * The sixth last optional argument is null, used to indicate an array of elements
688
1532
  * should be returned.
689
1533
  * @typedef {[
690
- * JamilihOptions|JamilihFirstArgument,
1534
+ * JamilihOptions|JamilihFirstArgument|JamilihDialectObject,
691
1535
  * (JamilihFirstArgument|
692
1536
  * JamilihAttributes|
693
1537
  * JamilihChildren|
@@ -712,8 +1556,8 @@ function _DOMfromJMLOrString (childNodeJML) {
712
1556
 
713
1557
  /**
714
1558
  * @typedef {{
715
- * root: [Map<HTMLElement,any>|WeakMap<HTMLElement,any>, any],
716
- * [key: string]: [Map<HTMLElement,any>|WeakMap<HTMLElement,any>, any]
1559
+ * root: [Map<HTMLElement,UserArg>|WeakMap<HTMLElement,UserArg>, UserArg],
1560
+ * [key: string]: [Map<HTMLElement,UserArg>|WeakMap<HTMLElement,UserArg>, UserArg]
717
1561
  * }} MapWithRoot
718
1562
  */
719
1563
 
@@ -722,10 +1566,17 @@ function _DOMfromJMLOrString (childNodeJML) {
722
1566
  */
723
1567
 
724
1568
  /**
725
- * @typedef {object} JamilihOptions
726
- * @property {TraversalState} [$state]
727
- * @property {JamilihPlugin[]} [$plugins]
728
- * @property {MapWithRoot|[Map<HTMLElement,any>|WeakMap<HTMLElement,any>, any]} [$map]
1569
+ * The optional leading object. `$state` is internal and `$mode` is reserved
1570
+ * (both throw when author-supplied). A templating dialect's own `$`-keys are
1571
+ * accepted here only after they are registered on the globally augmentable
1572
+ * {@link JamilihDialectProperties} interface (see `src/jamilih-dialect.d.ts`);
1573
+ * an unregistered `$`-key — and any genuine non-`$` attribute — stays a
1574
+ * compile error.
1575
+ * @typedef {{
1576
+ * $state?: TraversalState,
1577
+ * $plugins?: JamilihPlugin[],
1578
+ * $Map?: MapWithRoot|[Map<HTMLElement,UserArg>|WeakMap<HTMLElement,UserArg>, UserArg]
1579
+ * }} JamilihOptions
729
1580
  */
730
1581
 
731
1582
  /**
@@ -737,7 +1588,7 @@ function _DOMfromJMLOrString (childNodeJML) {
737
1588
  * @returns {Promise<void>|string|null}
738
1589
  */
739
1590
  function checkPluginValue(elem, att, attVal, opts, state) {
740
- opts.$state = state !== null && state !== void 0 ? state : 'attributeValue';
1591
+ opts.$state = state ?? 'attributeValue';
741
1592
  if (attVal && typeof attVal === 'object') {
742
1593
  const matchingPlugin = getMatchingPlugin(opts, Object.keys(attVal)[0]);
743
1594
  if (matchingPlugin) {
@@ -751,8 +1602,7 @@ function checkPluginValue(elem, att, attVal, opts, state) {
751
1602
  });
752
1603
  }
753
1604
  }
754
- return (/** @type {string} */attVal
755
- );
1605
+ return /** @type {string} */attVal;
756
1606
  }
757
1607
 
758
1608
  /**
@@ -766,27 +1616,213 @@ function getMatchingPlugin(opts, pluginName) {
766
1616
  });
767
1617
  }
768
1618
 
769
- /* eslint-disable jsdoc/valid-types -- pratt parser bug */
770
1619
  /**
771
1620
  * @template T
772
1621
  * @typedef {T[keyof T]} ValueOf
773
1622
  */
774
- /* eslint-enable jsdoc/valid-types -- pratt parser bug */
775
1623
 
776
- /* eslint-disable jsdoc/valid-types -- pratt parser bug */
1624
+ /**
1625
+ * @template {JamilihArray} T
1626
+ * @typedef {Extract<Extract<T[number], {$custom?: {[key: string]: unknown}}>['$custom'], object>} RawCustomFromJamilihArray
1627
+ */
1628
+
1629
+ /**
1630
+ * @template M
1631
+ * @typedef {M extends object
1632
+ * ? string extends keyof M
1633
+ * ? object
1634
+ * : M
1635
+ * : object} SpecificDefineMixin
1636
+ */
1637
+
1638
+ /**
1639
+ * @template D
1640
+ * @typedef {D extends [infer First, infer Second, ...ArbitraryValue[]]
1641
+ * ? (First extends DefineMixin
1642
+ * ? SpecificDefineMixin<First>
1643
+ * : Second extends DefineMixin
1644
+ * ? SpecificDefineMixin<Second>
1645
+ * : object)
1646
+ * : D extends [infer First]
1647
+ * ? First extends DefineMixin
1648
+ * ? SpecificDefineMixin<First>
1649
+ * : object
1650
+ * : D extends DefineMixin
1651
+ * ? SpecificDefineMixin<D>
1652
+ * : object} DefineMixinFromValue
1653
+ */
1654
+
1655
+ /**
1656
+ * @template {JamilihArray} T
1657
+ * @typedef {T[number] extends infer Item
1658
+ * ? Item extends {$define: infer D}
1659
+ * ? DefineMixinFromValue<D>
1660
+ * : never
1661
+ * : never} RawDefineMixinFromJamilihArray
1662
+ */
1663
+
1664
+ /**
1665
+ * @template D
1666
+ * @typedef {D extends [infer First, infer Second, ...ArbitraryValue[]]
1667
+ * ? First extends DefineConstructor
1668
+ * ? First['prototype']
1669
+ * : Second extends DefineConstructor
1670
+ * ? Second['prototype']
1671
+ * : never
1672
+ * : D extends DefineConstructor
1673
+ * ? D['prototype']
1674
+ * : never} ElementFromDefineValue
1675
+ */
1676
+
1677
+ /**
1678
+ * @template {JamilihArray} T
1679
+ * @typedef {T[number] extends infer Item
1680
+ * ? Item extends {$define: infer D}
1681
+ * ? ElementFromDefineValue<D>
1682
+ * : never
1683
+ * : never} ElementFromJamilihDefine
1684
+ */
1685
+
1686
+ /**
1687
+ * @template {JamilihArray} T
1688
+ * @typedef {Extract<T[number], {xmlns: unknown}> extends never ? false : true} HasXmlnsFromJamilihArray
1689
+ */
1690
+
1691
+ /**
1692
+ * @template {JamilihArray} T
1693
+ * @typedef {T extends [infer K, ...ArbitraryValue[]]
1694
+ * ? (HasXmlnsFromJamilihArray<T> extends true
1695
+ * ? Element
1696
+ * : ElementFromJamilihDefine<T> extends never
1697
+ * ? K extends keyof HTMLElementTagNameMap
1698
+ * ? HTMLElementTagNameMap[K]
1699
+ * : HTMLElement
1700
+ * : ElementFromJamilihDefine<T>)
1701
+ * : Element} ElementFromJamilihArray
1702
+ */
1703
+
1704
+ /**
1705
+ * @template A
1706
+ * @template {Element} E
1707
+ * @template X
1708
+ * @typedef {A extends {$custom: infer C}
1709
+ * ? (C extends object
1710
+ * ? Omit<A, '$custom'> & {$custom?: C & ThisType<E & C & X>}
1711
+ * : A)
1712
+ * : A} WithCustomThis
1713
+ */
1714
+
1715
+ /**
1716
+ * @template D
1717
+ * @template {Element} E
1718
+ * @template X
1719
+ * @typedef {D extends [infer First, infer Second, ...infer Rest]
1720
+ * ? (First extends DefineMixin
1721
+ * ? [First & ThisType<E & First & X>, Second, ...Rest]
1722
+ * : Second extends DefineMixin
1723
+ * ? [First, Second & ThisType<E & Second & X>, ...Rest]
1724
+ * : D)
1725
+ * : D extends [infer First]
1726
+ * ? First extends DefineMixin
1727
+ * ? [First & ThisType<E & First & X>]
1728
+ * : D
1729
+ * : D extends DefineMixin
1730
+ * ? D & ThisType<E & D & X>
1731
+ * : D} WithDefineThisValue
1732
+ */
1733
+
1734
+ /**
1735
+ * @template A
1736
+ * @template {Element} E
1737
+ * @template X
1738
+ * @typedef {A extends {$define: infer D}
1739
+ * ? Omit<A, '$define'> & {$define?: WithDefineThisValue<D, E, X>}
1740
+ * : A} WithDefineThis
1741
+ */
1742
+
1743
+ /**
1744
+ * @template A
1745
+ * @typedef {A extends {$custom: infer C}
1746
+ * ? C extends object ? C : object
1747
+ * : object} CustomFromJamilihItem
1748
+ */
1749
+
1750
+ /**
1751
+ * @template A
1752
+ * @typedef {A extends {$define: infer D}
1753
+ * ? DefineMixinFromValue<D>
1754
+ * : object} DefineMixinFromJamilihItem
1755
+ */
1756
+
1757
+ /**
1758
+ * @template {JamilihArray} T
1759
+ * @template {Element} E
1760
+ * @typedef {{[K in keyof T]: WithCustomThis<
1761
+ * WithDefineThis<T[K], E, CustomFromJamilihItem<T[K]>>,
1762
+ * E,
1763
+ * DefineMixinFromJamilihItem<T[K]>
1764
+ * >}} JamilihArrayWithCustomThis
1765
+ */
1766
+
1767
+ /**
1768
+ * @template A
1769
+ * @typedef {A extends (infer Item)[]
1770
+ * ? (Extract<Item, JamilihFirstArg> extends never ? never : A)
1771
+ * : A} ValidateJamilihArrayLike
1772
+ */
1773
+
1774
+ /**
1775
+ * @template A
1776
+ * @typedef {A extends (infer Child)[]
1777
+ * ? A & (Child extends unknown[] ? ValidateJamilihArrayLike<Child> : Child)[]
1778
+ * : A} ValidateJamilihChildContainer
1779
+ */
1780
+
1781
+ /**
1782
+ * @template {JamilihArray} T
1783
+ * @typedef {{[K in keyof T]: ValidateJamilihChildContainer<T[K]>}} ValidateJamilihArrayLikes
1784
+ */
1785
+
1786
+ /**
1787
+ * @template {JamilihArray} T
1788
+ * @typedef {(
1789
+ * RawCustomFromJamilihArray<T> extends never
1790
+ * ? object
1791
+ * : RawCustomFromJamilihArray<T>
1792
+ * )} CustomFromJamilihArray
1793
+ */
1794
+
1795
+ /**
1796
+ * @template {JamilihArray} T
1797
+ * @typedef {(
1798
+ * RawDefineMixinFromJamilihArray<T> extends never
1799
+ * ? object
1800
+ * : RawDefineMixinFromJamilihArray<T>
1801
+ * )} DefineMixinFromJamilihArray
1802
+ */
1803
+
1804
+ /**
1805
+ * @template U
1806
+ * @template W
1807
+ * @typedef {U extends void ? (ExpandoHTMLElement & W) : (U & W)} ResolvedElement
1808
+ */
1809
+
777
1810
  /**
778
1811
  * Creates an XHTML or HTML element (XHTML is preferred, but only in browsers
779
1812
  * that support); any element after element can be omitted, and any subsequent
780
1813
  * type or types added afterwards.
781
1814
  * @template {JamilihArray} T
782
- * @param {T} args
783
- * @returns {T extends [keyof HTMLElementTagNameMap, any?, any?, any?]
784
- * ? HTMLElementTagNameMap[T[0]] : JamilihReturn}
1815
+ * @template {T extends [infer K, ...ArbitraryValue[]] ? (HasXmlnsFromJamilihArray<T> extends true ? Element : ElementFromJamilihDefine<T> extends never ? K extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[K] : K extends string ? HTMLElement : void : ElementFromJamilihDefine<T>) : void} U
1816
+ * @template {ElementFromJamilihArray<T>} E
1817
+ * @template {CustomFromJamilihArray<T>} W
1818
+ * @template {DefineMixinFromJamilihArray<T>} D
1819
+ * @param {JamilihArrayWithCustomThis<T, E> & ValidateJamilihArrayLikes<T>} args
1820
+ * @returns {U extends void ? JamilihReturn : ResolvedElement<U, W & D>}
785
1821
  * The newly created (and possibly already appended)
786
1822
  * element or array of elements
787
1823
  */
1824
+
788
1825
  const jml = function jml(...args) {
789
- /* eslint-enable jsdoc/valid-types -- pratt parser bug */
790
1826
  if (!win) {
791
1827
  throw new Error('No window object');
792
1828
  }
@@ -794,7 +1830,7 @@ const jml = function jml(...args) {
794
1830
  throw new Error('No document object');
795
1831
  }
796
1832
 
797
- /** @type {(Document|DocumentFragment|HTMLElement) & {[key: string]: any}} */
1833
+ /** @type {(Document|DocumentFragment|HTMLElement) & {[key: string]: ElementExpando}} */
798
1834
  let elem = doc.createDocumentFragment();
799
1835
  /**
800
1836
  *
@@ -808,22 +1844,16 @@ const jml = function jml(...args) {
808
1844
  throw new Error('No document object');
809
1845
  }
810
1846
  for (let [att, attVal] of Object.entries(atts)) {
811
- var _ATTR_MAP$get;
812
- att = (_ATTR_MAP$get = ATTR_MAP.get(att)) !== null && _ATTR_MAP$get !== void 0 ? _ATTR_MAP$get : att;
813
-
814
- /**
815
- * @typedef {any} ElementExpando
816
- */
817
-
818
- if (NULLABLES.includes(att)) {
1847
+ att = ATTR_MAP.has(att) ? String(ATTR_MAP.get(att)) : att;
1848
+ if (NULLABLES.has(att)) {
819
1849
  attVal = checkPluginValue(elem, att, /** @type {string|JamilihArray} */attVal, opts);
820
1850
  if (!_isNullish(attVal)) {
821
- /** @type {ElementExpando} */elem[att] = attVal;
1851
+ elem[att] = attVal;
822
1852
  }
823
1853
  continue;
824
- } else if (ATTR_DOM.includes(att)) {
1854
+ }
1855
+ if (ATTR_DOM.has(att)) {
825
1856
  attVal = checkPluginValue(elem, att, /** @type {string|JamilihArray} */attVal, opts);
826
- /** @type {ElementExpando} */
827
1857
  elem[att] = attVal;
828
1858
  continue;
829
1859
  }
@@ -861,13 +1891,13 @@ const jml = function jml(...args) {
861
1891
  if (template) {
862
1892
  if (Array.isArray(template)) {
863
1893
  template = /** @type {HTMLTemplateElement} */
864
- _getType(template[0]) === 'object' ? jml('template', ...
1894
+ _getType(template[0]) === 'object' ? jml('template', ...(
865
1895
  /**
866
1896
  * @type {[
867
1897
  * JamilihAttributes, ...(JamilihArray[]|HTMLElement)[]
868
1898
  * ]}
869
1899
  */
870
- template, doc.body) : jml('template',
1900
+ template), doc.body) : jml('template',
871
1901
  /**
872
1902
  * @type {JamilihArray[]|HTMLElement}
873
1903
  */
@@ -875,7 +1905,7 @@ const jml = function jml(...args) {
875
1905
  } else if (typeof template === 'string') {
876
1906
  template = /** @type {HTMLTemplateElement} */$(template);
877
1907
  }
878
- jml( /** @type {HTMLTemplateElement} */
1908
+ jml(/** @type {HTMLTemplateElement} */
879
1909
  /** @type {HTMLTemplateElement} */template.content.cloneNode(true), shadowRoot);
880
1910
  } else {
881
1911
  if (!content) {
@@ -897,9 +1927,16 @@ const jml = function jml(...args) {
897
1927
  }
898
1928
  case '$state':
899
1929
  {
900
- // Handled internally
1930
+ // Handled internally; only valid on a jml-created `opts` object
1931
+ if (!internalOpts.has(atts)) {
1932
+ throw new TypeError(`\`$state\` is set internally by Jamilih and may not be supplied; args: ${JSON.stringify(args)}`);
1933
+ }
901
1934
  break;
902
1935
  }
1936
+ case '$mode':
1937
+ {
1938
+ throw new TypeError(`\`$mode\` is reserved for future use and not yet implemented; args: ${JSON.stringify(args)}`);
1939
+ }
903
1940
  case 'is':
904
1941
  {
905
1942
  // Currently only in Chrome
@@ -908,6 +1945,9 @@ const jml = function jml(...args) {
908
1945
  }
909
1946
  case '$custom':
910
1947
  {
1948
+ if (attVal !== null && (typeof attVal === 'object' || typeof attVal === 'function') && Object.prototype.propertyIsEnumerable.call(attVal, '__proto__')) {
1949
+ throw new TypeError('`$custom` may not define `__proto__`');
1950
+ }
911
1951
  Object.assign(elem, attVal);
912
1952
  break;
913
1953
  }
@@ -936,7 +1976,7 @@ const jml = function jml(...args) {
936
1976
  } = atts);
937
1977
  }
938
1978
  }
939
- const def = customizedBuiltIn ? /** @type {string} */is : localName;
1979
+ const def = customizedBuiltIn ? (/** @type {string} */is) : localName;
940
1980
  if (window.customElements.get(def)) {
941
1981
  break;
942
1982
  }
@@ -950,7 +1990,7 @@ const jml = function jml(...args) {
950
1990
  if (!doc) {
951
1991
  throw new Error('No document object');
952
1992
  }
953
- const baseClass = typeof options === 'object' && typeof options.extends === 'string' ? /** @type {typeof HTMLElement} */doc.createElement(options.extends).constructor : customizedBuiltIn ? /** @type {typeof HTMLElement} */doc.createElement(localName).constructor : window.HTMLElement;
1993
+ const baseClass = typeof options === 'object' && typeof options.extends === 'string' ? (/** @type {typeof HTMLElement} */doc.createElement(options.extends).constructor) : customizedBuiltIn ? (/** @type {typeof HTMLElement} */doc.createElement(localName).constructor) : window.HTMLElement;
954
1994
 
955
1995
  /**
956
1996
  * Class wrapping base class.
@@ -978,7 +2018,8 @@ const jml = function jml(...args) {
978
2018
  const defineObj = /** @type {DefineObject} */attVal;
979
2019
  if (Array.isArray(defineObj)) {
980
2020
  if (defineObj.length <= 2) {
981
- [cnstrctr, options] = defineObj;
2021
+ [cnstrctr] = defineObj;
2022
+ options = /** @type {DefineOptions|undefined} */defineObj[1];
982
2023
  if (typeof options === 'string') {
983
2024
  // Todo: Allow creating a definition without using it;
984
2025
  // that may be the only reason to have a string here which
@@ -1008,7 +2049,7 @@ const jml = function jml(...args) {
1008
2049
  cnstrctr = getConstructor();
1009
2050
  }
1010
2051
  if (!cnstrctr.toString().startsWith('class')) {
1011
- cnstrctr = getConstructor( /** @type {DefineUserConstructor} */cnstrctr);
2052
+ cnstrctr = getConstructor(/** @type {DefineUserConstructor} */cnstrctr);
1012
2053
  }
1013
2054
  if (!options && customizedBuiltIn) {
1014
2055
  options = {
@@ -1017,7 +2058,7 @@ const jml = function jml(...args) {
1017
2058
  }
1018
2059
  if (mixin) {
1019
2060
  Object.entries(mixin).forEach(([methodName, method]) => {
1020
- /** @type {DefineConstructor} */cnstrctr.prototype[methodName] = method;
2061
+ Reflect.set(/** @type {DefineConstructor} */cnstrctr.prototype, methodName, method);
1021
2062
  });
1022
2063
  }
1023
2064
  // console.log('def', def, '::', typeof options === 'object' ? options : undefined);
@@ -1026,9 +2067,13 @@ const jml = function jml(...args) {
1026
2067
  }
1027
2068
  case '$symbol':
1028
2069
  {
1029
- const [symbol, func] = /** @type {SymbolArray} */attVal;
2070
+ if (!_isHTMLElement(elem)) {
2071
+ throw new TypeError('Element expected for `$symbol`');
2072
+ }
2073
+ const symbolElem = /** @type {ResolvedElement<U, W>} */elem;
2074
+ const [symbol, func] = /** @type {SymbolArray<ResolvedElement<U, W>>} */attVal;
1030
2075
  if (typeof func === 'function') {
1031
- const funcBound = func.bind( /** @type {HTMLElement} */elem);
2076
+ const funcBound = func.bind(symbolElem);
1032
2077
  if (typeof symbol === 'string') {
1033
2078
  // @ts-expect-error
1034
2079
  elem[Symbol.for(symbol)] = funcBound;
@@ -1038,7 +2083,7 @@ const jml = function jml(...args) {
1038
2083
  }
1039
2084
  } else {
1040
2085
  const obj = func;
1041
- obj.elem = /** @type {HTMLElement} */elem;
2086
+ obj.elem = symbolElem;
1042
2087
  if (typeof symbol === 'string') {
1043
2088
  // @ts-expect-error
1044
2089
  elem[Symbol.for(symbol)] = obj;
@@ -1051,7 +2096,7 @@ const jml = function jml(...args) {
1051
2096
  }
1052
2097
  case '$data':
1053
2098
  {
1054
- setMap( /** @type {true|string[]|Map<any, any>|WeakMap<any, any>|DataAttributeObject} */
2099
+ setMap(/** @type {true|string[]|Map<HTMLElement, unknown>|WeakMap<HTMLElement, unknown>|DataAttributeObject} */
1055
2100
  attVal);
1056
2101
  break;
1057
2102
  }
@@ -1059,58 +2104,55 @@ const jml = function jml(...args) {
1059
2104
  {
1060
2105
  // Attribute node
1061
2106
  const attr = /** @type {JamilihAttributeNodeValue} */attVal;
1062
- const node = attr.length === 3 ? doc.createAttributeNS(attr[0], attr[1]) : doc.createAttribute( /** @type {string} */attr[0]);
1063
- node.value = /** @type {string} */attr[attr.length - 1];
2107
+ const node = attr.length === 3 ? doc.createAttributeNS(attr[0], attr[1]) : doc.createAttribute(/** @type {string} */attr[0]);
2108
+ node.value = /** @type {string} */attr.at(-1);
1064
2109
  nodes[nodes.length] = node;
1065
2110
  break;
1066
2111
  }
1067
2112
  case '$text':
1068
2113
  {
1069
2114
  // Todo: Also allow as jml(['a text node']) (or should that become a fragment)?
1070
- const node = doc.createTextNode( /** @type {string} */attVal);
2115
+ const node = doc.createTextNode(/** @type {string} */attVal);
1071
2116
  nodes[nodes.length] = node;
1072
2117
  break;
1073
2118
  }
1074
2119
  case '$document':
1075
2120
  {
1076
2121
  // Todo: Conditionally create XML document
1077
- const docNode = doc.implementation.createHTMLDocument();
1078
2122
  if (!attVal) {
1079
2123
  throw new Error('Bad attribute value');
1080
2124
  }
2125
+ const docNode = doc.implementation.createHTMLDocument();
1081
2126
  const jamlihDoc = /** @type {JamilihDocument} */attVal;
1082
2127
  if (jamlihDoc.childNodes) {
1083
2128
  // Remove any extra nodes created by createHTMLDocument().
1084
2129
  const j = jamlihDoc.childNodes.length;
1085
- while (docNode.childNodes[j]) {
2130
+ while (Object.hasOwn(docNode.childNodes, j)) {
1086
2131
  const cn = docNode.childNodes[j];
1087
2132
  cn.remove();
1088
2133
  // `j` should stay the same as removing will cause node to be present
1089
2134
  }
1090
-
1091
2135
  jamlihDoc.childNodes.forEach(_childrenToJML(docNode));
1092
2136
  } else {
1093
2137
  if (jamlihDoc.$DOCTYPE) {
1094
- var _docNode$firstChild;
1095
2138
  const dt = {
1096
2139
  $DOCTYPE: jamlihDoc.$DOCTYPE
1097
2140
  };
1098
2141
  const doctype = jml(dt);
1099
- (_docNode$firstChild = docNode.firstChild) === null || _docNode$firstChild === void 0 ? void 0 : _docNode$firstChild.replaceWith(doctype);
2142
+ docNode.firstChild?.replaceWith(doctype);
1100
2143
  }
1101
2144
  const html = docNode.querySelector('html');
1102
- const head = html === null || html === void 0 ? void 0 : html.querySelector('head');
1103
- const body = html === null || html === void 0 ? void 0 : html.querySelector('body');
2145
+ const head = html?.querySelector('head');
2146
+ const body = html?.querySelector('body');
1104
2147
  if (jamlihDoc.title || jamlihDoc.head) {
1105
2148
  const meta = doc.createElement('meta');
1106
2149
  // eslint-disable-next-line unicorn/text-encoding-identifier-case -- HTML
1107
2150
  meta.setAttribute('charset', 'utf-8');
1108
- head === null || head === void 0 ? void 0 : head.append(meta);
2151
+ head?.append(meta);
1109
2152
  if (jamlihDoc.title) {
1110
2153
  docNode.title = jamlihDoc.title; // Appends after meta
1111
2154
  }
1112
-
1113
- if (jamlihDoc.head && head) {
2155
+ if (head && jamlihDoc.head) {
1114
2156
  // each child of `head` is:
1115
2157
  // (JamilihArray|TextNodeString|HTMLElement|Comment|ProcessingInstruction|
1116
2158
  // Text|DocumentFragment|JamilihProcessingInstruction|JamilihDocumentFragment)
@@ -1124,10 +2166,20 @@ const jml = function jml(...args) {
1124
2166
  jamlihDoc.head.forEach(_appendJML(head));
1125
2167
  }
1126
2168
  }
1127
- if (jamlihDoc.body && body) {
2169
+ if (body && jamlihDoc.body) {
1128
2170
  jamlihDoc.body.forEach(_appendJMLOrText(body));
1129
2171
  }
1130
2172
  }
2173
+ if (jamlihDoc.xmlDeclaration) {
2174
+ const {
2175
+ version,
2176
+ encoding,
2177
+ standalone
2178
+ } = jamlihDoc.xmlDeclaration;
2179
+ const xmlDeclarationData = `${version ? ` version="${version}"` : ''}${encoding ? ` encoding="${encoding}"` : ''}${standalone ? ` standalone="yes"` : ''}`.slice(1);
2180
+ const xmlDeclaration = doc.createProcessingInstruction('xml', xmlDeclarationData);
2181
+ docNode.insertBefore(xmlDeclaration, docNode.firstChild);
2182
+ }
1131
2183
  nodes[nodes.length] = docNode;
1132
2184
  break;
1133
2185
  }
@@ -1141,17 +2193,18 @@ const jml = function jml(...args) {
1141
2193
  case '$on':
1142
2194
  {
1143
2195
  // Events
2196
+ const onElem = /** @type {ResolvedElement<U, W>} */elem;
1144
2197
  // Allow for no-op by defaulting to `{}`
1145
- for (let [p2, val] of Object.entries( /** @type {OnAttributeObject} */attVal || {})) {
2198
+ // eslint-disable-next-line prefer-const, unicorn/no-unreadable-for-of-expression -- Ok as mixed
2199
+ for (let [p2, val] of Object.entries(/** @type {OnAttributeObject<ResolvedElement<U, W>>} */attVal || {})) {
1146
2200
  if (typeof val === 'function') {
1147
2201
  val = [val, false];
1148
2202
  }
1149
2203
  if (typeof val[0] !== 'function') {
1150
2204
  throw new TypeError(`Expect a function for \`$on\`; args: ${JSON.stringify(args)}`);
1151
2205
  }
1152
- _addEvent( /** @type {HTMLElement} */elem, p2, val[0], val[1]); // element, event name, handler, capturing
2206
+ _addEvent(onElem, p2, val[0], val[1]); // element, event name, handler, capturing
1153
2207
  }
1154
-
1155
2208
  break;
1156
2209
  }
1157
2210
  case 'className':
@@ -1174,7 +2227,11 @@ const jml = function jml(...args) {
1174
2227
  const pastInitialProp = startProp !== '';
1175
2228
  Object.keys(atVal).forEach(key => {
1176
2229
  const value = atVal[key];
1177
- prop = pastInitialProp ? startProp + key.replace(hyphenForCamelCase, _upperCase).replace(/^([a-z])/u, _upperCase) : startProp + key.replace(hyphenForCamelCase, _upperCase);
2230
+ prop = startProp + (pastInitialProp
2231
+ // eslint-disable-next-line unicorn/no-unsafe-string-replacement -- Function
2232
+ ? key.replaceAll(hyphenForCamelCase, _upperCase).replace(/^([a-z])/u, _upperCase)
2233
+ // eslint-disable-next-line unicorn/no-unsafe-string-replacement -- Function
2234
+ : key.replaceAll(hyphenForCamelCase, _upperCase));
1178
2235
  if (value === null || typeof value !== 'object') {
1179
2236
  if (!_isNullish(value)) {
1180
2237
  elem.dataset[prop] = value;
@@ -1185,12 +2242,15 @@ const jml = function jml(...args) {
1185
2242
  recurse(value, prop);
1186
2243
  });
1187
2244
  };
1188
- recurse( /** @type {DatasetAttributeObject} */attVal, '');
2245
+ recurse(/** @type {DatasetAttributeObject} */attVal, '');
1189
2246
  break;
1190
- // Todo: Disable this by default unless configuration explicitly allows (for security)
1191
2247
  }
1192
2248
  // #if IS_REMOVE
1193
2249
  // Don't remove this `if` block (for sake of no-innerHTML build)
2250
+ // Security: this assigns `innerHTML` directly, so its value must be
2251
+ // trusted or sanitized by the caller. The `jamilih` package also
2252
+ // publishes a `jml-noinnerh` build with this sink removed, and
2253
+ // `validateJamilih({allowInnerHTML: false})` rejects the key.
1194
2254
  case 'innerHTML':
1195
2255
  if (!_isNullish(attVal)) {
1196
2256
  // eslint-disable-next-line no-unsanitized/property
@@ -1235,7 +2295,8 @@ const jml = function jml(...args) {
1235
2295
  elem.style.cssFloat = styleVal;
1236
2296
  elem.style.styleFloat = styleVal; // Harmless though we could make conditional on older IE instead
1237
2297
  } else {
1238
- elem.style[p2.replace(hyphenForCamelCase, _upperCase)] = styleVal;
2298
+ // eslint-disable-next-line unicorn/no-unsafe-string-replacement -- Function
2299
+ elem.style[p2.replaceAll(hyphenForCamelCase, _upperCase)] = styleVal;
1239
2300
  }
1240
2301
  }
1241
2302
  }
@@ -1259,14 +2320,21 @@ const jml = function jml(...args) {
1259
2320
  if (matchingPlugin) {
1260
2321
  matchingPlugin.set({
1261
2322
  opts,
1262
- element: /** @type {HTMLElement} */nodes[0],
2323
+ element: (/** @type {HTMLElement} */nodes[0]),
1263
2324
  attribute: {
1264
2325
  name: pluginName,
1265
- value: /** @type {PluginReference} */attVal
2326
+ value: (/** @type {PluginReference} */attVal)
1266
2327
  }
1267
2328
  });
1268
2329
  break;
1269
2330
  }
2331
+ if (att.startsWith('$')) {
2332
+ // Unrecognized `$`-prefixed key: reserved for templating dialects
2333
+ // layered on Jamilih (and for future Jamilih use); base Jamilih
2334
+ // ignores it rather than attempting `setAttribute` (which would
2335
+ // throw on the invalid `$` name character).
2336
+ break;
2337
+ }
1270
2338
  attVal = checkPluginValue(elem, att, /** @type {string} */attVal, opts);
1271
2339
  elem.setAttribute(att, attVal);
1272
2340
  break;
@@ -1289,13 +2357,30 @@ const jml = function jml(...args) {
1289
2357
  let argStart = 0;
1290
2358
  if (_getType(args[0]) === 'object' && Object.keys(args[0]).some(key => possibleOptions.includes(key))) {
1291
2359
  opts = /** @type {JamilihOptions} */args[0];
2360
+ if (!internalOpts.has(opts)) {
2361
+ // `$mode` is reserved for a future SVG/XML mode and does nothing yet;
2362
+ // `$state` is set internally and would corrupt root detection.
2363
+ if (Object.hasOwn(opts, '$mode')) {
2364
+ throw new TypeError(`\`$mode\` is reserved for future use and not yet implemented; args: ${JSON.stringify(args)}`);
2365
+ }
2366
+ if (Object.hasOwn(opts, '$state')) {
2367
+ throw new TypeError(`\`$state\` is set internally by Jamilih and may not be supplied; args: ${JSON.stringify(args)}`);
2368
+ }
2369
+ // The options object may carry only `$`-prefixed keys (recognized
2370
+ // options or dialect magic Jamilih ignores); a plain key is a
2371
+ // misplaced attribute with no element to receive it.
2372
+ if (Object.keys(opts).some(k => !k.startsWith('$'))) {
2373
+ throw new TypeError(`Attributes may not be supplied before an element; args: ${JSON.stringify(args)}`);
2374
+ }
2375
+ internalOpts.add(opts);
2376
+ }
1292
2377
  if (opts.$state === undefined) {
1293
2378
  isRoot = true;
1294
2379
  opts.$state = 'root';
1295
2380
  }
1296
- if (Array.isArray(opts.$map)) {
1297
- opts.$map = {
1298
- root: opts.$map
2381
+ if (Array.isArray(opts.$Map)) {
2382
+ opts.$Map = {
2383
+ root: opts.$Map
1299
2384
  };
1300
2385
  }
1301
2386
  if ('$plugins' in opts) {
@@ -1319,17 +2404,43 @@ const jml = function jml(...args) {
1319
2404
  opts = {
1320
2405
  $state: undefined
1321
2406
  };
2407
+ internalOpts.add(opts);
2408
+ // A user-supplied plain object in first-argument position that is *not*
2409
+ // an options object (handled above). It may only be a node-producing
2410
+ // first-arg object (`#`, `$text`, `$document`, `$DOCTYPE`, `$attribute`)
2411
+ // or a purely `$`-prefixed object (dialect magic Jamilih skips); a
2412
+ // genuine attribute there has no element to attach to.
2413
+ const leading = /** @type {Record<string, unknown>} */args[0];
2414
+ if (_getType(args[0]) === 'object' && !internalOpts.has(leading)) {
2415
+ if (Object.hasOwn(leading, '$mode')) {
2416
+ throw new TypeError(`\`$mode\` is reserved for future use and not yet implemented; args: ${JSON.stringify(args)}`);
2417
+ }
2418
+ if (Object.hasOwn(leading, '$state')) {
2419
+ throw new TypeError(`\`$state\` is set internally by Jamilih and may not be supplied; args: ${JSON.stringify(args)}`);
2420
+ }
2421
+ const leadingKeys = Object.keys(leading);
2422
+ const firstArgNodeObject = leadingKeys.some(k => {
2423
+ return ['#', '$text', '$document', '$DOCTYPE', '$attribute'].includes(k);
2424
+ });
2425
+ if (!firstArgNodeObject) {
2426
+ if (leadingKeys.every(k => k.startsWith('$'))) {
2427
+ argStart = 1;
2428
+ } else {
2429
+ throw new TypeError(`Attributes may not be supplied before an element; args: ${JSON.stringify(args)}`);
2430
+ }
2431
+ }
2432
+ }
1322
2433
  }
1323
2434
  const argc = args.length;
1324
- const defaultMap = opts.$map && /** @type {MapWithRoot} */opts.$map.root;
2435
+ const defaultMap = opts.$Map && /** @type {MapWithRoot} */opts.$Map.root;
1325
2436
 
1326
2437
  /**
1327
- * @param {true|string[]|Map<any, any>|WeakMap<any, any>|DataAttributeObject} dataVal
2438
+ * @param {true|string[]|Map<HTMLElement, UserArg>|WeakMap<HTMLElement, UserArg>|DataAttributeObject|[Map<HTMLElement, UserArg>|WeakMap<HTMLElement, UserArg>|undefined, DataAttributeObject|UserArg]} dataVal
1328
2439
  * @returns {void}
1329
2440
  */
1330
2441
  const setMap = dataVal => {
1331
2442
  let map, obj;
1332
- const defMap = /** @type {[Map<HTMLElement, any> | WeakMap<HTMLElement, any>, any]} */defaultMap;
2443
+ const defMap = /** @type {[Map<HTMLElement, UserArg> | WeakMap<HTMLElement, UserArg>, UserArg]} */defaultMap;
1333
2444
  // Boolean indicating use of default map and object
1334
2445
  if (dataVal === true) {
1335
2446
  [map, obj] = defMap;
@@ -1337,12 +2448,11 @@ const jml = function jml(...args) {
1337
2448
  // Array of strings mapping to default
1338
2449
  if (typeof dataVal[0] === 'string') {
1339
2450
  dataVal.forEach(dVal => {
1340
- setMap( /** @type {MapWithRoot} */opts.$map[dVal]);
2451
+ setMap(/** @type {MapWithRoot} */opts.$Map[dVal]);
1341
2452
  });
1342
2453
  return;
1343
2454
  // Array of Map and non-map data object
1344
2455
  }
1345
-
1346
2456
  map = dataVal[0] || defMap[0];
1347
2457
  obj = dataVal[1] || defMap[1];
1348
2458
  // Map
@@ -1354,8 +2464,8 @@ const jml = function jml(...args) {
1354
2464
  map = defMap[0];
1355
2465
  obj = dataVal;
1356
2466
  }
1357
- /** @type {Map<HTMLElement, any> | WeakMap<HTMLElement, any>} */
1358
- map.set( /** @type {HTMLElement} */
2467
+ /** @type {Map<HTMLElement, UserArg> | WeakMap<HTMLElement, UserArg>} */
2468
+ map.set(/** @type {HTMLElement} */
1359
2469
  elem, obj);
1360
2470
  };
1361
2471
  for (let i = argStart; i < argc; i++) {
@@ -1366,18 +2476,16 @@ const jml = function jml(...args) {
1366
2476
  // null always indicates a place-holder (only needed for last argument if want array returned)
1367
2477
  if (i === argc - 1) {
1368
2478
  // Casting needing unless changing `jml()` signature with overloads
1369
- return (/** @type {ArbitraryValue} */nodes.length <= 1 ? nodes[0]
1370
- // eslint-disable-next-line unicorn/no-array-callback-reference
1371
- : nodes.reduce(_fragReducer, doc.createDocumentFragment())
1372
- ); // nodes;
2479
+ return /** @type {U extends void ? JamilihReturn : ResolvedElement<U, W & D>} */nodes.length <= 1 ? nodes[0]
2480
+ // eslint-disable-next-line unicorn/no-array-callback-reference
2481
+ : nodes.reduce(_fragReducer, doc.createDocumentFragment()); // nodes;
1373
2482
  }
1374
-
1375
2483
  throw new TypeError(`\`null\` values not allowed except as final Jamilih argument; index ${i} on args: ${JSON.stringify(args)}`);
1376
2484
  case 'string':
1377
2485
  // Strings normally indicate elements
1378
2486
  switch (arg) {
1379
2487
  case '!':
1380
- nodes[nodes.length] = doc.createComment( /** @type {string} */args[++i]);
2488
+ nodes[nodes.length] = doc.createComment(/** @type {string} */args[++i]);
1381
2489
  break;
1382
2490
  case '?':
1383
2491
  {
@@ -1387,9 +2495,9 @@ const jml = function jml(...args) {
1387
2495
  if (val && typeof val === 'object') {
1388
2496
  const procValues = [];
1389
2497
  for (const [p, procInstVal] of Object.entries(val)) {
1390
- procValues.push(p + '=' + '"' +
2498
+ procValues.push(p + '="' +
1391
2499
  // https://www.w3.org/TR/xml-stylesheet/#NT-PseudoAttValue
1392
- procInstVal.replace(/"/gu, '&quot;') + '"');
2500
+ procInstVal.replaceAll('"', '&quot;') + '"');
1393
2501
  }
1394
2502
  procValue = procValues.join(' ');
1395
2503
  }
@@ -1426,12 +2534,11 @@ const jml = function jml(...args) {
1426
2534
  // CDATA valid in XML only, so we'll just treat as text for mutual compatibility
1427
2535
  // Todo: config (or detection via some kind of doc.documentType property?) of whether in XML
1428
2536
  try {
1429
- nodes[nodes.length] = doc.createCDATASection( /** @type {string} */args[++i]);
2537
+ nodes[nodes.length] = doc.createCDATASection(/** @type {string} */args[++i]);
1430
2538
  } catch (e2) {
1431
- nodes[nodes.length] = doc.createTextNode( /** @type {string} */
2539
+ nodes[nodes.length] = doc.createTextNode(/** @type {string} */
1432
2540
  args[i]); // i already incremented
1433
2541
  }
1434
-
1435
2542
  break;
1436
2543
  case '':
1437
2544
  nodes[nodes.length] = elem = doc.createDocumentFragment();
@@ -1450,13 +2557,12 @@ const jml = function jml(...args) {
1450
2557
  /* c8 ignore next 4 */
1451
2558
  elem = doc.createElementNS
1452
2559
  // Should create separate file for this
1453
- /* eslint-disable object-shorthand -- Casting */ ? /** @type {HTMLElement} */doc.createElementNS(NS_HTML, elStr, {
1454
- is: /** @type {string} */is
2560
+ ? (/** @type {HTMLElement} */doc.createElementNS(NS_HTML, elStr, {
2561
+ is: (/** @type {string} */is)
1455
2562
  })
1456
- /* c8 ignore next 1 */ : doc.createElement(elStr, {
1457
- is: /** @type {string} */is
2563
+ /* c8 ignore next 1 */) : doc.createElement(elStr, {
2564
+ is: (/** @type {string} */is)
1458
2565
  });
1459
- /* eslint-enable object-shorthand -- Casting */
1460
2566
  } else /* c8 ignore next */if (doc.createElementNS) {
1461
2567
  elem = doc.createElementNS(NS_HTML, elStr);
1462
2568
  /* c8 ignore next 3 */
@@ -1484,25 +2590,26 @@ const jml = function jml(...args) {
1484
2590
  // elem.setAttribute('xmlns', atts.xmlns); // Doesn't work
1485
2591
  // Can't set namespaceURI dynamically, renameNode() is not supported, and setAttribute() doesn't work to change the namespace, so we resort to this hack
1486
2592
  const xmlnsObj = /** @type {XmlnsAttributeObject} */atts;
1487
- const replacer = xmlnsObj.xmlns && typeof xmlnsObj.xmlns === 'object' ? _replaceDefiner(xmlnsObj.xmlns) : ' xmlns="' + xmlnsObj.xmlns + '"';
2593
+ const replacer = xmlnsObj.xmlns && typeof xmlnsObj.xmlns === 'object' ? _replaceDefiner(xmlnsObj.xmlns) : ' xmlns="' + escapeReplacer(xmlnsObj.xmlns) + '"';
1488
2594
  // try {
1489
2595
  // Also fix DOMParser to work with text/html
1490
2596
  elem = nodes[nodes.length - 1] =
1491
2597
  // Why doesn't `HTMLWindow` have `DOMParser`?
1492
- new /** @type {import('jsdom').DOMWindow} */win.DOMParser().parseFromString(new /** @type {import('jsdom').DOMWindow} */win.XMLSerializer().serializeToString(elem)
2598
+ new /** @type {import('jsdom').DOMWindow} */win.DOMParser().parseFromString(new /** @type {import('jsdom').DOMWindow} */win.XMLSerializer().serializeToString(elem).
1493
2599
  // Mozilla adds XHTML namespace
1494
- .replace(' xmlns="' + NS_HTML + '"',
2600
+ replace(' xmlns="' + escapeReplacer(NS_HTML) + '"',
1495
2601
  // Needed to cast here, despite either overload working
2602
+ // eslint-disable-next-line unicorn/no-unsafe-string-replacement -- Escaped
1496
2603
  /** @type {string} */
1497
2604
  replacer), 'application/xml').documentElement;
1498
2605
  // Todo: Report to plugins
1499
2606
  opts.$state = 'element';
1500
2607
  // }catch(e) {alert(elem.outerHTML);throw e;}
1501
2608
  }
1502
-
1503
- _checkAtts( /** @type {JamilihAttributes} */atts);
2609
+ _checkAtts(/** @type {JamilihAttributes} */atts);
1504
2610
  break;
1505
2611
  }
2612
+ case 'processing-instruction':
1506
2613
  case 'document':
1507
2614
  case 'fragment':
1508
2615
  case 'element':
@@ -1520,7 +2627,7 @@ const jml = function jml(...args) {
1520
2627
  // parent
1521
2628
  const elsl = nodes.length;
1522
2629
  for (let k = 0; k < elsl; k++) {
1523
- _appendNode( /** @type {Document|DocumentFragment|HTMLElement} */arg, nodes[k]);
2630
+ _appendNode(/** @type {Document|DocumentFragment|HTMLElement} */arg, nodes[k]);
1524
2631
  }
1525
2632
  } else {
1526
2633
  nodes[nodes.length] = /** @type {Document|DocumentFragment|HTMLElement} */arg;
@@ -1531,12 +2638,19 @@ const jml = function jml(...args) {
1531
2638
  // Arrays or arrays of arrays indicate child nodes
1532
2639
  const child = /** @type {JamilihChildren} */arg;
1533
2640
  const cl = child.length;
2641
+ /**
2642
+ * @param {number} childIndex
2643
+ * @returns {TypeError}
2644
+ */
2645
+ const getBadChildrenError = childIndex => {
2646
+ return new TypeError(`Bad children (parent array: ${JSON.stringify(args)}; index ${childIndex} of child: ${JSON.stringify(child)})`);
2647
+ };
1534
2648
  for (let j = 0; j < cl; j++) {
1535
2649
  // Go through children array container to handle elements
1536
2650
  const childContent = child[j];
1537
2651
  const childContentType = typeof childContent;
1538
2652
  if (childContent === null || _isNullish(childContent)) {
1539
- throw new TypeError(`Bad children (parent array: ${JSON.stringify(args)}; index ${j} of child: ${JSON.stringify(child)})`);
2653
+ throw getBadChildrenError(j);
1540
2654
  }
1541
2655
  switch (childContentType) {
1542
2656
  // Todo: determine whether null or function should have special handling or be converted to text
@@ -1548,12 +2662,17 @@ const jml = function jml(...args) {
1548
2662
  default:
1549
2663
  // bigint, symbol, function
1550
2664
  if (typeof childContent !== 'object') {
1551
- throw new TypeError(`Bad children (parent array: ${JSON.stringify(args)}; index ${j} of child: ${JSON.stringify(child)})`);
2665
+ throw getBadChildrenError(j);
1552
2666
  }
1553
2667
  if (Array.isArray(childContent)) {
1554
2668
  // Arrays representing child elements
2669
+ const childHeadType = typeof childContent[0];
2670
+ if (childContent.length === 0 || childHeadType !== 'string' && childHeadType !== 'object' || childContent[0] === null) {
2671
+ throw getBadChildrenError(j);
2672
+ }
1555
2673
  opts.$state = 'children';
1556
- _appendNode(elem, jml(opts, ...childContent));
2674
+ const childArgs = /** @type {JamilihArray} */[opts, ...childContent];
2675
+ _appendNode(elem, jml(...childArgs));
1557
2676
  } else if ('#' in childContent) {
1558
2677
  // Fragment
1559
2678
  opts.$state = 'fragmentChildren';
@@ -1564,6 +2683,15 @@ const jml = function jml(...args) {
1564
2683
  if (!('nodeType' in childContent)) {
1565
2684
  newChildContent = /** @type {string} */
1566
2685
  checkPluginValue(elem, null, childContent, opts, 'children');
2686
+ const pluginClaimed = newChildContent !== undefined && /** @type {unknown} */newChildContent !== childContent;
2687
+ const childKeys = Object.keys(childContent);
2688
+ if (
2689
+ // No plugin claimed the object, and every key is `$`-prefixed
2690
+ // magic Jamilih does not know: treat as a no-op
2691
+ // (templating-dialect placeholder).
2692
+ !pluginClaimed && childKeys.length > 0 && childKeys.every(k => k.startsWith('$'))) {
2693
+ break;
2694
+ }
1567
2695
  }
1568
2696
  _appendNode(elem, /** @type {string|HTMLElement|DocumentFragment|Comment} */
1569
2697
  newChildContent || childContent);
@@ -1578,13 +2706,12 @@ const jml = function jml(...args) {
1578
2706
  }
1579
2707
  }
1580
2708
  const ret = nodes[0] || elem;
1581
- if (isRoot && opts.$map && /** @type {MapWithRoot} */opts.$map.root) {
2709
+ if (isRoot && opts.$Map && /** @type {MapWithRoot} */opts.$Map.root) {
1582
2710
  setMap(true);
1583
2711
  }
1584
2712
 
1585
2713
  // Casting needing unless changing `jml()` signature with overloads
1586
- return (/** @type {ArbitraryValue} */ret
1587
- );
2714
+ return /** @type {U extends void ? JamilihReturn : ResolvedElement<U, W & D>} */ret;
1588
2715
  };
1589
2716
 
1590
2717
  /**
@@ -1654,8 +2781,8 @@ class DOMException extends Error {
1654
2781
 
1655
2782
  /**
1656
2783
  * @typedef {JamilihArray|JamilihDoctype|
1657
- * JamilihCDATANode|JamilihEntityReference|JamilihProcessingInstruction|
1658
- * JamilihComment|JamilihDocumentFragment} JamilihChildType
2784
+ * JamilihCDATANode|JamilihEntityReference|JamilihProcessingInstruction|
2785
+ * JamilihComment|JamilihDocumentFragment} JamilihChildType
1659
2786
  */
1660
2787
 
1661
2788
  /**
@@ -1663,15 +2790,15 @@ class DOMException extends Error {
1663
2790
  */
1664
2791
 
1665
2792
  /**
1666
- * Converts a DOM object or a string of HTML into a Jamilih object (or string).
1667
- * @param {string|HTMLElement|Node|Entity} nde If a string, will parse as document
1668
- * @param {ToJmlConfig} [config] Configuration object
1669
- * @throws {TypeError}
1670
- * @returns {JamilihType|string} Array containing the elements which represent
1671
- * a Jamilih object, or, if `stringOutput` is true, it will be the stringified
1672
- * version of such an object
1673
- */
1674
- jml.toJML = function (nde, {
2793
+ * Converts a DOM object or a string of HTML into a Jamilih object (or string).
2794
+ * @param {string|HTMLElement|Node|Entity} nde If a string, will parse as document
2795
+ * @param {ToJmlConfig} [config] Configuration object
2796
+ * @throws {TypeError}
2797
+ * @returns {JamilihType|string} Array containing the elements which represent
2798
+ * a Jamilih object, or, if `stringOutput` is true, it will be the stringified
2799
+ * version of such an object
2800
+ */
2801
+ const toJML = function (nde, {
1675
2802
  stringOutput = false,
1676
2803
  reportInvalidState = true,
1677
2804
  stripWhitespace = false
@@ -1682,12 +2809,11 @@ jml.toJML = function (nde, {
1682
2809
  if (typeof nde === 'string') {
1683
2810
  nde = new /** @type {import('jsdom').DOMWindow} */win.DOMParser().parseFromString(nde, 'text/html'); // todo: Give option for XML once implemented and change JSDoc to allow for Element
1684
2811
  }
1685
-
1686
2812
  const dom = /** @type {HTMLElement|Node|Entity} */nde;
1687
2813
 
1688
2814
  /**
1689
2815
  * @todo Find more specific type than `any`
1690
- * @typedef {{[key: (number|string)]: any}} IndexableObject
2816
+ * @typedef {{[key: (number|string)]: UserArg}} IndexableObject
1691
2817
  */
1692
2818
 
1693
2819
  const ret = /** @type {IndexableObject} */[];
@@ -1701,12 +2827,14 @@ jml.toJML = function (nde, {
1701
2827
  */
1702
2828
  function invalidStateError(msg) {
1703
2829
  // These are probably only necessary if working with text/html
1704
- if (reportInvalidState) {
1705
- // INVALID_STATE_ERR per section 9.3 XHTML 5: http://www.w3.org/TR/html5/the-xhtml-syntax.html
1706
- const e = new DOMException(msg, 'INVALID_STATE_ERR');
1707
- e.code = 11;
1708
- throw e;
2830
+ if (!reportInvalidState) {
2831
+ return;
1709
2832
  }
2833
+
2834
+ // INVALID_STATE_ERR per section 9.3 XHTML 5: https://www.w3.org/TR/html5/the-xhtml-syntax.html
2835
+ const e = new DOMException(msg, 'INVALID_STATE_ERR');
2836
+ e.code = 11;
2837
+ throw e;
1710
2838
  }
1711
2839
 
1712
2840
  /**
@@ -1793,8 +2921,13 @@ jml.toJML = function (nde, {
1793
2921
  namespaces = {
1794
2922
  ...namespaces
1795
2923
  };
1796
- const xmlChars = /^([\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]|[\uD800-\uDBFF][\uDC00-\uDFFF])*$/u; // eslint-disable-line no-control-regex
1797
- if ([2, 3, 4, 7, 8].includes(type) && /** @type {Node} */nodeOrEntity.nodeValue && !xmlChars.test( /** @type {Node} */nodeOrEntity.nodeValue)) {
2924
+ const xmlChars = /^([\u{9}\u{A}\u{D}\u{20}-\u{D7FF}\u{E000}-\u{FFFD}]|[\uD800-\uDBFF][\uDC00-\uDFFF])*$/u; // eslint-disable-line no-control-regex
2925
+
2926
+ // eslint-disable-next-line prefer-destructuring -- TS
2927
+ const nodeValue = /** @type {Node} */nodeOrEntity.nodeValue;
2928
+ if ([2, 3, 4, 7, 8].includes(type) &&
2929
+ // eslint-disable-next-line unicorn/prefer-simple-condition-first -- Safer
2930
+ nodeValue && !xmlChars.test(nodeValue)) {
1798
2931
  invalidStateError('Node has bad XML character value');
1799
2932
  }
1800
2933
 
@@ -1823,7 +2956,6 @@ jml.toJML = function (nde, {
1823
2956
  parentIdx = tmpParentIdx;
1824
2957
  parentIdx++; // Increment index in parent container of this element
1825
2958
  }
1826
-
1827
2959
  switch (type) {
1828
2960
  case 1:
1829
2961
  {
@@ -1897,10 +3029,10 @@ jml.toJML = function (nde, {
1897
3029
  }
1898
3030
  case 4:
1899
3031
  {
1900
- var _node$nodeValue;
1901
3032
  // CDATA
1902
3033
  const node = /** @type {CDATASection} */nodeOrEntity;
1903
- if ((_node$nodeValue = node.nodeValue) !== null && _node$nodeValue !== void 0 && _node$nodeValue.includes(']]' + '>')) {
3034
+ // eslint-disable-next-line unicorn/no-useless-concat -- Safer if pasting
3035
+ if (node.nodeValue?.includes(']]' + '>')) {
1904
3036
  invalidStateError('CDATA cannot end with closing ]]>');
1905
3037
  }
1906
3038
  set(['![', node.nodeValue]);
@@ -1983,8 +3115,8 @@ jml.toJML = function (nde, {
1983
3115
  name: /** @type {DocumentType} */node.name
1984
3116
  }
1985
3117
  };
1986
- const pubIdChar = /^(\u0020|\u000D|\u000A|[a-zA-Z0-9]|[-'()+,./:=?;!*#@$_%])*$/u; // eslint-disable-line no-control-regex
1987
- if (!pubIdChar.test( /** @type {DocumentType} */node.publicId)) {
3118
+ const pubIdChar = /^(\u{20}|\u{D}|\u{A}|[a-zA-Z0-9]|[-'()+,./:=?;!*#@$_%])*$/u; // eslint-disable-line no-control-regex
3119
+ if (!pubIdChar.test(/** @type {DocumentType} */node.publicId)) {
1988
3120
  invalidStateError('A publicId must have valid characters.');
1989
3121
  }
1990
3122
  addExternalID(start.$DOCTYPE, node);
@@ -2025,36 +3157,37 @@ jml.toJML = function (nde, {
2025
3157
  }
2026
3158
  return ret[0];
2027
3159
  };
3160
+ jml.toJML = toJML;
2028
3161
 
2029
3162
  /**
2030
3163
  * @param {string|HTMLElement} dom
2031
3164
  * @param {ToJmlConfig} [config]
2032
3165
  * @returns {string}
2033
3166
  */
2034
- jml.toJMLString = function (dom, config) {
2035
- return (/** @type {string} */
2036
- jml.toJML(dom, Object.assign(config || {}, {
2037
- stringOutput: true
2038
- }))
2039
- );
3167
+ const toJMLString = function (dom, config) {
3168
+ return /** @type {string} */toJML(dom, Object.assign(config || {}, {
3169
+ stringOutput: true
3170
+ }));
2040
3171
  };
3172
+ jml.toJMLString = toJMLString;
2041
3173
 
2042
3174
  /**
2043
3175
  *
2044
3176
  * @param {JamilihArray} args
2045
3177
  * @returns {JamilihReturn}
2046
3178
  */
2047
- jml.toDOM = function (...args) {
3179
+ const toDOM = function (...args) {
2048
3180
  // Alias for jml()
2049
3181
  return jml(...args);
2050
3182
  };
3183
+ jml.toDOM = toDOM;
2051
3184
 
2052
3185
  /**
2053
3186
  *
2054
3187
  * @param {JamilihArray} args
2055
3188
  * @returns {string}
2056
3189
  */
2057
- jml.toHTML = function (...args) {
3190
+ const toHTML = function (...args) {
2058
3191
  // Todo: Replace this with version of jml() that directly builds a string
2059
3192
  const ret = jml(...args);
2060
3193
  switch (ret.nodeType) {
@@ -2064,33 +3197,34 @@ jml.toHTML = function (...args) {
2064
3197
  // Todo: deal with serialization of properties like 'selected',
2065
3198
  // 'checked', 'value', 'defaultValue', 'for', 'dataset', 'on*',
2066
3199
  // 'style'! (i.e., need to build a string ourselves)
2067
- return (/** @type {HTMLElement} */ret.outerHTML
2068
- );
3200
+ return /** @type {HTMLElement} */ret.outerHTML;
2069
3201
  }
2070
3202
  case 2:
2071
3203
  {
2072
3204
  // ATTR
2073
- return `${
2074
- /** @type {Attr} */ret.name}="${
2075
- /** @type {Attr} */ret.value.replace(/"/gu, '&quot;')}"`;
3205
+ return `${ /** @type {Attr} */ret.name}="${ /** @type {Attr} */ret.value.replaceAll('"', '&quot;')}"`;
2076
3206
  }
2077
3207
  case 3:
2078
3208
  {
2079
3209
  // TEXT
2080
3210
  // Fallthrough
2081
3211
  // } case 4: { // CDATA
3212
+ // eslint-disable-next-line prefer-destructuring -- TS
3213
+ const nodeValue = /** @type {Text|CDATASection} */ret.nodeValue;
2082
3214
  /* c8 ignore next 3 */
2083
- if (!ret.nodeValue) {
3215
+ if (!nodeValue) {
2084
3216
  throw new TypeError('Unexpected null Text node');
2085
3217
  }
2086
- return (/** @type {Text|CDATASection} */ret.nodeValue
2087
- );
3218
+ return nodeValue;
2088
3219
  // case 5: // Entity Reference Node
2089
3220
  // No 6: Entity Node
2090
3221
  // No 12: Notation Node
2091
- // } case 7: { // PROCESSING INSTRUCTION
2092
- // const node = /** @type {ProcessingInstruction} */ (ret);
2093
- // return `<?${node.target} ${node.data}?>`;
3222
+ }
3223
+ case 7:
3224
+ {
3225
+ // PROCESSING INSTRUCTION
3226
+ const node = /** @type {ProcessingInstruction} */ret;
3227
+ return `<?${node.target} ${node.data}?>`;
2094
3228
  // } case 8: { // Comment
2095
3229
  // return `<!--${ret.nodeValue}-->`;
2096
3230
  }
@@ -2100,7 +3234,7 @@ jml.toHTML = function (...args) {
2100
3234
  // DOCUMENT FRAGMENT
2101
3235
  const node = /** @type {DocumentFragment} */ret;
2102
3236
  return [...node.childNodes].map(childNode => {
2103
- return jml.toHTML( /** @type {JamilihFirstArgument} */childNode);
3237
+ return toHTML(/** @type {JamilihFirstArgument} */childNode);
2104
3238
  }).join('');
2105
3239
  }
2106
3240
  case 10:
@@ -2114,47 +3248,52 @@ jml.toHTML = function (...args) {
2114
3248
  throw new Error('Unexpected node type');
2115
3249
  }
2116
3250
  };
3251
+ jml.toHTML = toHTML;
2117
3252
 
2118
3253
  /**
2119
3254
  *
2120
3255
  * @param {JamilihArray} args
2121
3256
  * @returns {string}
2122
3257
  */
2123
- jml.toDOMString = function (...args) {
3258
+ const toDOMString = function (...args) {
2124
3259
  // Alias for jml.toHTML for parity with jml.toJMLString
2125
- return jml.toHTML(...args);
3260
+ return toHTML(...args);
2126
3261
  };
3262
+ jml.toDOMString = toDOMString;
2127
3263
 
2128
3264
  /**
2129
3265
  *
2130
3266
  * @param {JamilihArray} args
2131
3267
  * @returns {string}
2132
3268
  */
2133
- jml.toXML = function (...args) {
3269
+ const toXML = function (...args) {
2134
3270
  if (!win) {
2135
3271
  throw new Error('No window object set');
2136
3272
  }
2137
3273
  const ret = jml(...args);
2138
3274
  return new /** @type {import('jsdom').DOMWindow} */win.XMLSerializer().serializeToString(ret);
2139
3275
  };
3276
+ jml.toXML = toXML;
2140
3277
 
2141
3278
  /**
2142
3279
  *
2143
3280
  * @param {JamilihArray} args
2144
3281
  * @returns {string}
2145
3282
  */
2146
- jml.toXMLDOMString = function (...args) {
3283
+ const toXMLDOMString = function (...args) {
2147
3284
  // Alias for jml.toXML for parity with jml.toJMLString
2148
- return jml.toXML(...args);
3285
+ return toXML(...args);
2149
3286
  };
3287
+ jml.toXMLDOMString = toXMLDOMString;
2150
3288
 
2151
3289
  /**
2152
3290
  * Element-aware wrapper for `Map`.
3291
+ * @template V
2153
3292
  */
2154
3293
  class JamilihMap extends Map {
2155
3294
  /**
2156
3295
  * @param {?(string|HTMLElement)} element
2157
- * @returns {ArbitraryValue}
3296
+ * @returns {V}
2158
3297
  */
2159
3298
  get(element) {
2160
3299
  const elem = typeof element === 'string' ? $(element) : element;
@@ -2162,104 +3301,109 @@ class JamilihMap extends Map {
2162
3301
  }
2163
3302
  /**
2164
3303
  * @param {string|HTMLElement} element
2165
- * @param {ArbitraryValue} value
2166
- * @returns {ArbitraryValue}
3304
+ * @param {V} value
3305
+ * @returns {this}
2167
3306
  */
2168
3307
  set(element, value) {
2169
3308
  const elem = typeof element === 'string' ? $(element) : element;
2170
- return super.set.call(this, elem, value);
3309
+ super.set.call(this, elem, value);
3310
+ return this;
2171
3311
  }
2172
3312
  /**
2173
3313
  * @param {string|HTMLElement} element
2174
3314
  * @param {string} methodName
2175
- * @param {...ArbitraryValue} args
2176
- * @returns {ArbitraryValue}
3315
+ * @param {...UserArg} args
3316
+ * @returns {StoredValue}
2177
3317
  */
2178
3318
  invoke(element, methodName, ...args) {
2179
3319
  const elem = typeof element === 'string' ? $(element) : element;
2180
- return this.get(elem)[methodName](elem, ...args);
3320
+ return /** @type {UserArg} */this.get(elem)[methodName](elem, ...args);
2181
3321
  }
2182
3322
  }
2183
3323
 
2184
3324
  /**
2185
3325
  * Element-aware wrapper for `WeakMap`.
2186
- * @extends {WeakMap<any>}
3326
+ * @template V
2187
3327
  */
2188
3328
  class JamilihWeakMap extends WeakMap {
2189
3329
  /**
2190
- * @param {HTMLElement} element
2191
- * @returns {ArbitraryValue}
3330
+ * @param {?(string|object|symbol)} element
3331
+ * @returns {V}
2192
3332
  */
2193
3333
  get(element) {
2194
3334
  const elem = typeof element === 'string' ? $(element) : element;
2195
3335
  if (!elem) {
2196
3336
  throw new Error("Can't find the element");
2197
3337
  }
2198
- return super.get.call(this, elem);
3338
+ return super.get.call(this, /** @type {object} */elem);
2199
3339
  }
2200
3340
  /**
2201
- * @param {HTMLElement} element
2202
- * @param {ArbitraryValue} value
2203
- * @returns {ArbitraryValue}
3341
+ * @param {?(string|object|symbol)} element
3342
+ * @param {V} value
3343
+ * @returns {this}
2204
3344
  */
2205
3345
  set(element, value) {
2206
3346
  const elem = typeof element === 'string' ? $(element) : element;
2207
3347
  if (!elem) {
2208
3348
  throw new Error("Can't find the element");
2209
3349
  }
2210
- return super.set.call(this, elem, value);
3350
+ super.set.call(this, /** @type {object} */elem, value);
3351
+ return this;
2211
3352
  }
2212
3353
  /**
2213
3354
  * @param {string|HTMLElement} element
2214
3355
  * @param {string} methodName
2215
- * @param {...ArbitraryValue} args
2216
- * @returns {ArbitraryValue}
3356
+ * @param {...UserArg} args
3357
+ * @returns {StoredValue}
2217
3358
  */
2218
3359
  invoke(element, methodName, ...args) {
2219
3360
  const elem = typeof element === 'string' ? $(element) : element;
2220
3361
  if (!elem) {
2221
3362
  throw new Error("Can't find the element");
2222
3363
  }
2223
- return this.get(elem)[methodName](elem, ...args);
3364
+ return /** @type {UserArg} */this.get(elem)[methodName](elem, ...args);
2224
3365
  }
2225
3366
  }
2226
3367
  jml.Map = JamilihMap;
2227
3368
  jml.WeakMap = JamilihWeakMap;
2228
3369
 
2229
3370
  /**
2230
- * @typedef {[JamilihWeakMap|JamilihMap, HTMLElement]} MapAndElementArray
3371
+ * @template V
3372
+ * @typedef {[JamilihWeakMap<V>|JamilihMap<V>, HTMLElement]} MapAndElementArray
2231
3373
  */
2232
3374
 
2233
3375
  /**
2234
- * @param {{[key: string]: any}} obj
3376
+ * @template V
3377
+ * @param {V} obj
2235
3378
  * @param {JamilihArrayPostOptions} args
2236
- * @returns {MapAndElementArray}
3379
+ * @returns {MapAndElementArray<V>}
2237
3380
  */
2238
3381
  jml.weak = function (obj, ...args) {
2239
3382
  const map = new JamilihWeakMap();
2240
3383
  const elem = jml({
2241
- $map: [map, obj]
3384
+ $Map: [map, obj]
2242
3385
  }, ...args);
2243
- return [map, /** @type {HTMLElement} */elem];
3386
+ return [map, (/** @type {HTMLElement} */elem)];
2244
3387
  };
2245
3388
 
2246
3389
  /**
2247
- * @param {ArbitraryValue} obj
3390
+ * @template V
3391
+ * @param {V} obj
2248
3392
  * @param {JamilihArrayPostOptions} args
2249
- * @returns {MapAndElementArray}
3393
+ * @returns {MapAndElementArray<V>}
2250
3394
  */
2251
3395
  jml.strong = function (obj, ...args) {
2252
3396
  const map = new JamilihMap();
2253
3397
  const elem = jml({
2254
- $map: [map, obj]
3398
+ $Map: [map, obj]
2255
3399
  }, ...args);
2256
- return [map, /** @type {HTMLElement} */elem];
3400
+ return [map, (/** @type {HTMLElement} */elem)];
2257
3401
  };
2258
3402
 
2259
3403
  /**
2260
3404
  * @param {string|HTMLElement} element If a string, will be interpreted as a selector
2261
3405
  * @param {symbol|string} sym If a string, will be used with `Symbol.for`
2262
- * @returns {ArbitraryValue} The value associated with the symbol
3406
+ * @returns {SymbolResult} The value associated with the symbol
2263
3407
  */
2264
3408
  jml.symbol = jml.sym = jml.for = function (element, sym) {
2265
3409
  const elem = typeof element === 'string' ? $(element) : element;
@@ -2269,16 +3413,16 @@ jml.symbol = jml.sym = jml.for = function (element, sym) {
2269
3413
  };
2270
3414
 
2271
3415
  /**
2272
- * @typedef {((elem: HTMLElement, ...args: any[]) => void)|{[key: string]: (elem: HTMLElement, ...args: any[]) => void}} MapCommand
3416
+ * @typedef {((elem: HTMLElement, ...args: UserArg[]) => void)|{[key: string]: (elem: HTMLElement, ...args: UserArg[]) => void}} MapCommand
2273
3417
  */
2274
3418
 
2275
3419
  /**
2276
3420
  * @param {?(string|HTMLElement)} elem If a string, will be interpreted as a selector
2277
3421
  * @param {symbol|string|Map<HTMLElement, MapCommand>|WeakMap<HTMLElement, MapCommand>} symOrMap If a string, will be used with `Symbol.for`
2278
- * @param {string|any} methodName Can be `any` if the symbol or map directly
3422
+ * @param {string|UserArg} methodName Can be `UserArg` if the symbol or map directly
2279
3423
  * points to a function (it is then used as the first argument).
2280
- * @param {ArbitraryValue[]} args
2281
- * @returns {ArbitraryValue}
3424
+ * @param {UserArg[]} args
3425
+ * @returns {StoredValue}
2282
3426
  */
2283
3427
  jml.command = function (elem, symOrMap, methodName, ...args) {
2284
3428
  elem = typeof elem === 'string' ? $(elem) : elem;
@@ -2291,8 +3435,7 @@ jml.command = function (elem, symOrMap, methodName, ...args) {
2291
3435
  if (typeof func === 'function') {
2292
3436
  return func(methodName, ...args); // Already has `this` bound to `elem`
2293
3437
  }
2294
-
2295
- return func[methodName](...args);
3438
+ return /** @type {UserArg} */func[methodName](...args);
2296
3439
  }
2297
3440
  func = /** @type {Map<HTMLElement, MapCommand>|WeakMap<HTMLElement, MapCommand>} */symOrMap.get(elem);
2298
3441
  if (!func) {
@@ -2308,52 +3451,55 @@ jml.command = function (elem, symOrMap, methodName, ...args) {
2308
3451
  /**
2309
3452
  * Expects properties `document`, `XMLSerializer`, and `DOMParser`.
2310
3453
  * Also updates `body` with `document.body`.
2311
- * @param {import('jsdom').DOMWindow|HTMLWindow|undefined} wind
3454
+ * @param {import('jsdom').DOMWindow|HTMLWindow|typeof globalThis|undefined} wind
2312
3455
  * @returns {void}
2313
3456
  */
2314
- jml.setWindow = wind => {
2315
- var _win2;
3457
+ const setWindow = wind => {
2316
3458
  win = wind;
2317
- doc = (_win2 = win) === null || _win2 === void 0 ? void 0 : _win2.document;
3459
+ doc = win?.document;
2318
3460
  if (doc && doc.body) {
2319
3461
  // eslint-disable-next-line prefer-destructuring -- Needed for typing
2320
3462
  body = /** @type {HTMLBodyElement} */doc.body;
2321
3463
  }
2322
3464
  };
3465
+ jml.setWindow = setWindow;
2323
3466
 
2324
3467
  /**
2325
- * @returns {import('jsdom').DOMWindow|HTMLWindow}
3468
+ * @returns {import('jsdom').DOMWindow|HTMLWindow|typeof globalThis}
2326
3469
  */
2327
- jml.getWindow = () => {
3470
+ const getWindow = () => {
2328
3471
  if (!win) {
2329
3472
  throw new Error('No window object set');
2330
3473
  }
2331
3474
  return win;
2332
3475
  };
3476
+ jml.getWindow = getWindow;
3477
+ jml.validateJamilih = validateJamilih;
2333
3478
 
2334
3479
  /**
2335
3480
  * Does not run Jamilih so can be further processed.
2336
- * @param {ArbitraryValue[]} array
2337
- * @param {ArbitraryValue} glu
2338
- * @returns {ArbitraryValue[]}
3481
+ * @template T
3482
+ * @param {T[]} array
3483
+ * @param {T} glu
3484
+ * @returns {T[]}
2339
3485
  */
2340
3486
  function glue(array, glu) {
2341
3487
  return [...array].reduce((arr, item) => {
2342
3488
  arr.push(item, glu);
2343
3489
  return arr;
2344
- }, []).slice(0, -1);
3490
+ }, /** @type {T[]} */[]).slice(0, -1);
2345
3491
  }
2346
3492
 
2347
3493
  /**
2348
3494
  * @type {HTMLBodyElement}
2349
3495
  */
2350
- let body; // eslint-disable-line import/no-mutable-exports
3496
+ let body; // // eslint-disable-line import/no-mutable-exports
2351
3497
 
2352
3498
  /* c8 ignore next 4 */
2353
3499
  if (doc && doc.body) {
2354
3500
  // eslint-disable-next-line prefer-destructuring -- Needed for type
2355
3501
  body = /** @type {HTMLBodyElement} */doc.body;
2356
3502
  }
2357
- const nbsp = '\u00A0'; // Very commonly needed in templates
3503
+ const nbsp = '\u{A0}'; // Very commonly needed in templates
2358
3504
 
2359
- export { $, $$, DOMException, body, jml as default, glue, jml, nbsp };
3505
+ export { $, $$, DOMException, body, getWindow, glue, isValidJamilih, jml, nbsp, setWindow, toDOM, toDOMString, toHTML, toJML, toJMLString, toXML, toXMLDOMString, validateJamilih };