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.
- package/.nyc_output/out.json +8980 -1849
- package/CHANGES.md +11 -1
- package/README.md +6 -11
- package/_locales/en-US/messages.json +153 -6
- package/_locales/hu-HU/messages.json +153 -6
- package/_locales/pt-BR/messages.json +153 -6
- package/_locales/sv-SE/messages.json +153 -6
- package/babel.config.json +0 -1
- package/browser_action/characterSelection.js +18 -11
- package/browser_action/charrefConverters.js +286 -79
- package/browser_action/chartBuild.js +97 -27
- package/browser_action/encodingBehaviors.js +10 -7
- package/browser_action/entityBehaviors.js +42 -16
- package/browser_action/index-es.html +13 -1
- package/browser_action/index-instrumented.html +13 -1
- package/browser_action/index-pages.html +13 -1
- package/browser_action/index.html +13 -1
- package/browser_action/index.iife.min.js +9 -1
- package/browser_action/index.iife.min.js.map +1 -1
- package/browser_action/index.instrumented.iife.min.js +1 -1
- package/browser_action/index.instrumented.iife.min.js.map +1 -1
- package/browser_action/index.js +5 -5
- package/browser_action/preferences/prefDefaults.js +12 -3
- package/browser_action/service-worker/sw-activateCallback.js +1 -2
- package/browser_action/service-worker/sw-resources.json +6 -4
- package/browser_action/templateUtils/elements.js +12 -1
- package/browser_action/templateUtils/fill.js +6 -0
- package/browser_action/templateUtils/validation.js +4 -1
- package/browser_action/templates/chartBuild.js +167 -102
- package/browser_action/templates/index.js +227 -53
- package/browser_action/templatesElementCustomization/widgets.js +40 -15
- package/browser_action/unicode/UnicodeConverter.js +99 -73
- package/browser_action/unicode/charrefunicodeDb.js +100 -50
- package/browser_action/unicode/getScriptInfoForCodePoint.js +13 -4
- package/browser_action/unicode/hangul.js +27 -20
- package/browser_action/unicode/lastScriptNames.json +1 -1
- package/browser_action/unicode/parseUnihanFromTextFileStrings.js +44 -25
- package/browser_action/unicode/unicodeFieldInfo.js +112 -90
- package/browser_action/unicode/unicodeScripts.js +409 -35
- package/browser_action/unicode/unihan.js +4 -3
- package/browser_action/unicode/unihanDbPopulate.js +7 -2
- package/browser_action/unicode/unihanFields.js +34 -0
- package/browser_action/unicodecharref.js +353 -190
- package/browser_action/utils/DOMUtils.js +95 -34
- package/browser_action/utils/FetchUtils.js +7 -6
- package/browser_action/utils/TextUtils.js +4 -4
- package/browser_action/utils/TypedArrayUtils.js +9 -5
- package/browser_action/utils/semicolonSeparatedToArray.js +3 -3
- package/browser_action/utils/setupServiceWorker.js +2 -2
- package/eslint.config.js +11 -0
- package/icons/openWindow16.png +0 -0
- package/icons/openWindow24.png +0 -0
- package/lib/background.html +7 -0
- package/lib/background.js +2 -2
- package/package.json +79 -70
- package/pnpm-workspace.yaml +8 -0
- package/server.js +66 -51
- package/sw.js +97 -56
- package/tools/entities-import.js +4 -1
- package/tools/findEsResources.js +33 -10
- package/tools/fix-sw-resources-vendor-paths.js +58 -0
- package/tools/list-locales.js +2 -2
- package/tools/parseUnicodeCharts.js +126 -60
- package/tools/ucd-import.js +1 -10
- package/tools/unicode-charts.html +49 -20
- package/tools/unihan-import.js +45 -15
- package/tools/write-sw-version.js +16 -0
- package/tsconfig.json +23 -0
- package/typings/__coverage__.d.ts +5 -0
- package/typings/apple-system-profiler.d.ts +17 -0
- package/typings/commands.d.ts +19 -0
- package/typings/json-6.d.ts +4 -0
- package/typings/rollup-plugin-istanbul.d.ts +3 -0
- package/vendor/camelcase/index.d.ts +154 -0
- package/vendor/camelcase/index.js +137 -23
- package/vendor/fflate/esm/browser.d.ts +1539 -0
- package/vendor/fflate/esm/browser.js +82 -55
- package/vendor/intl-dom/dist/Formatter.d.ts +120 -0
- package/vendor/intl-dom/dist/Formatter.d.ts.map +1 -0
- package/vendor/intl-dom/dist/collation.d.ts +42 -0
- package/vendor/intl-dom/dist/collation.d.ts.map +1 -0
- package/vendor/intl-dom/dist/defaultAllSubstitutions.d.ts +52 -0
- package/vendor/intl-dom/dist/defaultAllSubstitutions.d.ts.map +1 -0
- package/vendor/intl-dom/dist/defaultInsertNodes.d.ts +96 -0
- package/vendor/intl-dom/dist/defaultInsertNodes.d.ts.map +1 -0
- package/vendor/intl-dom/dist/defaultKeyCheckerConverter.d.ts +17 -0
- package/vendor/intl-dom/dist/defaultKeyCheckerConverter.d.ts.map +1 -0
- package/vendor/intl-dom/dist/defaultLocaleResolver.d.ts +285 -0
- package/vendor/intl-dom/dist/defaultLocaleResolver.d.ts.map +1 -0
- package/vendor/intl-dom/dist/findLocaleStrings.d.ts +88 -0
- package/vendor/intl-dom/dist/findLocaleStrings.d.ts.map +1 -0
- package/vendor/intl-dom/dist/getDOMForLocaleString.d.ts +67 -0
- package/vendor/intl-dom/dist/getDOMForLocaleString.d.ts.map +1 -0
- package/vendor/intl-dom/dist/getMessageForKeyByStyle.d.ts +73 -0
- package/vendor/intl-dom/dist/getMessageForKeyByStyle.d.ts.map +1 -0
- package/vendor/intl-dom/dist/getStringFromMessageAndDefaults.d.ts +26 -0
- package/vendor/intl-dom/dist/getStringFromMessageAndDefaults.d.ts.map +1 -0
- package/vendor/intl-dom/dist/i18n.d.ts +113 -0
- package/vendor/intl-dom/dist/i18n.d.ts.map +1 -0
- package/vendor/intl-dom/dist/index.d.ts +92 -0
- package/vendor/intl-dom/dist/index.d.ts.map +1 -0
- package/vendor/intl-dom/dist/index.esm.d.ts +34 -0
- package/vendor/intl-dom/dist/index.esm.js +854 -896
- package/vendor/intl-dom/dist/index.esm.min.js +4 -0
- package/vendor/intl-dom/dist/index.esm.min.js.map +1 -0
- package/vendor/intl-dom/dist/index.umd.js +2953 -0
- package/vendor/intl-dom/dist/index.umd.min.js +4 -0
- package/vendor/intl-dom/dist/index.umd.min.js.map +1 -0
- package/vendor/intl-dom/dist/promiseChainForValues.d.ts +34 -0
- package/vendor/intl-dom/dist/promiseChainForValues.d.ts.map +1 -0
- package/vendor/intl-dom/dist/shared.d.ts +20 -0
- package/vendor/intl-dom/dist/shared.d.ts.map +1 -0
- package/vendor/intl-dom/dist/utils.d.ts +53 -0
- package/vendor/intl-dom/dist/utils.d.ts.map +1 -0
- package/vendor/jamilih/dist/jml.d.mts +468 -0
- package/vendor/jamilih/dist/{jml-es.js → jml.mjs} +1489 -343
- package/vendor/jquery/dist/jquery.js +1677 -2713
- package/vendor/json-6/dist/index.mjs +2 -0
- package/vendor/miller-columns/CHANGES.md +185 -0
- package/vendor/miller-columns/LICENSE-MIT.txt +21 -0
- package/vendor/miller-columns/README.md +249 -0
- package/vendor/miller-columns/demos/index.html +69 -0
- package/vendor/miller-columns/demos/index.js +72 -0
- package/vendor/miller-columns/dist/index-es.js +748 -0
- package/vendor/miller-columns/dist/index-es.min.d.ts +25 -0
- package/vendor/miller-columns/dist/index-es.min.js +7 -1
- package/vendor/miller-columns/dist/index-umd.js +757 -0
- package/vendor/miller-columns/dist/index-umd.min.js +7 -0
- package/vendor/miller-columns/dist/index.d.ts +25 -0
- package/vendor/miller-columns/dist/millerColumns.d.ts +10 -0
- package/vendor/miller-columns/eslint.config.js +46 -0
- package/vendor/miller-columns/fix-declarations.js +18 -0
- package/vendor/miller-columns/miller-columns.css +24 -1
- package/vendor/miller-columns/package.json +73 -0
- package/vendor/miller-columns/pnpm-workspace.yaml +2 -0
- package/vendor/miller-columns/src/index.js +642 -0
- package/vendor/miller-columns/src/millerColumns.ts +12 -0
- package/vendor/miller-columns/tsconfig-prod.json +23 -0
- package/vendor/miller-columns/tsconfig.json +21 -0
- package/vendor/simple-prefs/dist/index.esm.d.ts +1 -0
- package/vendor/simple-prefs/dist/index.esm.js +164 -216
- package/vendor/simple-prefs/dist/simple-prefs.d.ts +117 -0
- package/lgtm.yml +0 -5
|
@@ -1,40 +1,830 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
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 ||
|
|
859
|
+
let doc = typeof document !== 'undefined' && document || win?.document;
|
|
70
860
|
|
|
71
861
|
// STATIC PROPERTIES
|
|
72
862
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 [...
|
|
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 =
|
|
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
|
-
* @
|
|
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:
|
|
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|
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* "
|
|
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
|
-
* @
|
|
286
|
-
* @
|
|
287
|
-
|
|
288
|
-
|
|
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(...
|
|
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|
|
|
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
|
-
* @
|
|
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
|
|
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
|
|
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]:
|
|
1309
|
+
* @typedef {{[key: string]: unknown}} DefineMixin
|
|
496
1310
|
*/
|
|
497
1311
|
|
|
498
1312
|
/**
|
|
499
1313
|
* @typedef {{
|
|
500
1314
|
* new (): HTMLElement;
|
|
501
|
-
* prototype: HTMLElement
|
|
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
|
-
* @
|
|
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 {
|
|
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|
|
|
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:
|
|
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<
|
|
1381
|
+
* $data?: true|string[]|Map<HTMLElement, UserArg>|WeakMap<HTMLElement, UserArg>|DataAttributeObject|
|
|
553
1382
|
* [undefined, DataAttributeObject]|
|
|
554
|
-
* [Map<
|
|
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]:
|
|
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
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
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 {
|
|
666
|
-
*
|
|
667
|
-
*
|
|
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,
|
|
716
|
-
* [key: string]: [Map<HTMLElement,
|
|
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
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
* @
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
* @
|
|
783
|
-
* @
|
|
784
|
-
*
|
|
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]:
|
|
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
|
-
|
|
812
|
-
|
|
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
|
-
|
|
1851
|
+
elem[att] = attVal;
|
|
822
1852
|
}
|
|
823
1853
|
continue;
|
|
824
|
-
}
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
1063
|
-
node.value = /** @type {string} */attr
|
|
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(
|
|
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
|
|
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
|
-
|
|
2142
|
+
docNode.firstChild?.replaceWith(doctype);
|
|
1100
2143
|
}
|
|
1101
2144
|
const html = docNode.querySelector('html');
|
|
1102
|
-
const head = html
|
|
1103
|
-
const body = html
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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.$
|
|
1297
|
-
opts.$
|
|
1298
|
-
root: opts.$
|
|
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.$
|
|
2435
|
+
const defaultMap = opts.$Map && /** @type {MapWithRoot} */opts.$Map.root;
|
|
1325
2436
|
|
|
1326
2437
|
/**
|
|
1327
|
-
* @param {true|string[]|Map<
|
|
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,
|
|
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(
|
|
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,
|
|
1358
|
-
map.set(
|
|
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
|
|
1370
|
-
|
|
1371
|
-
|
|
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(
|
|
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.
|
|
2500
|
+
procInstVal.replaceAll('"', '"') + '"');
|
|
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(
|
|
2537
|
+
nodes[nodes.length] = doc.createCDATASection(/** @type {string} */args[++i]);
|
|
1430
2538
|
} catch (e2) {
|
|
1431
|
-
nodes[nodes.length] = doc.createTextNode(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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.$
|
|
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
|
|
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
|
-
|
|
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)]:
|
|
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
|
-
|
|
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 = /^([\
|
|
1797
|
-
|
|
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
|
-
|
|
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 = /^(\
|
|
1987
|
-
if (!pubIdChar.test(
|
|
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
|
-
|
|
2035
|
-
return
|
|
2036
|
-
|
|
2037
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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, '"')}"`;
|
|
3205
|
+
return `${ /** @type {Attr} */ret.name}="${ /** @type {Attr} */ret.value.replaceAll('"', '"')}"`;
|
|
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 (!
|
|
3215
|
+
if (!nodeValue) {
|
|
2084
3216
|
throw new TypeError('Unexpected null Text node');
|
|
2085
3217
|
}
|
|
2086
|
-
return
|
|
2087
|
-
);
|
|
3218
|
+
return nodeValue;
|
|
2088
3219
|
// case 5: // Entity Reference Node
|
|
2089
3220
|
// No 6: Entity Node
|
|
2090
3221
|
// No 12: Notation Node
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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
|
|
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
|
-
|
|
3258
|
+
const toDOMString = function (...args) {
|
|
2124
3259
|
// Alias for jml.toHTML for parity with jml.toJMLString
|
|
2125
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
3283
|
+
const toXMLDOMString = function (...args) {
|
|
2147
3284
|
// Alias for jml.toXML for parity with jml.toJMLString
|
|
2148
|
-
return
|
|
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 {
|
|
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 {
|
|
2166
|
-
* @returns {
|
|
3304
|
+
* @param {V} value
|
|
3305
|
+
* @returns {this}
|
|
2167
3306
|
*/
|
|
2168
3307
|
set(element, value) {
|
|
2169
3308
|
const elem = typeof element === 'string' ? $(element) : element;
|
|
2170
|
-
|
|
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 {...
|
|
2176
|
-
* @returns {
|
|
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
|
-
* @
|
|
3326
|
+
* @template V
|
|
2187
3327
|
*/
|
|
2188
3328
|
class JamilihWeakMap extends WeakMap {
|
|
2189
3329
|
/**
|
|
2190
|
-
* @param {
|
|
2191
|
-
* @returns {
|
|
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 {
|
|
2202
|
-
* @param {
|
|
2203
|
-
* @returns {
|
|
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
|
-
|
|
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 {...
|
|
2216
|
-
* @returns {
|
|
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
|
-
* @
|
|
3371
|
+
* @template V
|
|
3372
|
+
* @typedef {[JamilihWeakMap<V>|JamilihMap<V>, HTMLElement]} MapAndElementArray
|
|
2231
3373
|
*/
|
|
2232
3374
|
|
|
2233
3375
|
/**
|
|
2234
|
-
* @
|
|
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
|
-
$
|
|
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
|
-
* @
|
|
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
|
-
$
|
|
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 {
|
|
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:
|
|
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|
|
|
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 {
|
|
2281
|
-
* @returns {
|
|
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
|
-
|
|
2315
|
-
var _win2;
|
|
3457
|
+
const setWindow = wind => {
|
|
2316
3458
|
win = wind;
|
|
2317
|
-
doc =
|
|
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
|
-
|
|
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
|
-
* @
|
|
2337
|
-
* @param {
|
|
2338
|
-
* @
|
|
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 = '\
|
|
3503
|
+
const nbsp = '\u{A0}'; // Very commonly needed in templates
|
|
2358
3504
|
|
|
2359
|
-
export { $, $$, DOMException, body,
|
|
3505
|
+
export { $, $$, DOMException, body, getWindow, glue, isValidJamilih, jml, nbsp, setWindow, toDOM, toDOMString, toHTML, toJML, toJMLString, toXML, toXMLDOMString, validateJamilih };
|