jj 2.9.0 → 3.0.0-rc.3

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/lib/bundle.cjs CHANGED
@@ -23,7 +23,6 @@ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot
23
23
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
24
24
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
25
25
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
26
- var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
27
26
 
28
27
  // src/index.ts
29
28
  var src_exports = {};
@@ -33,159 +32,48 @@ __export(src_exports, {
33
32
  JJE: () => JJE,
34
33
  JJET: () => JJET,
35
34
  JJHE: () => JJHE,
35
+ JJME: () => JJME,
36
36
  JJN: () => JJN,
37
37
  JJSE: () => JJSE,
38
38
  JJSR: () => JJSR,
39
39
  JJT: () => JJT,
40
- ShadowMaster: () => ShadowMaster,
41
- addLinkPre: () => addLinkPre,
42
40
  attr2prop: () => attr2prop,
43
- createLinkPre: () => createLinkPre,
44
- cssToStyle: () => cssToStyle,
45
- doc: () => doc,
46
- fetchCss: () => fetchCss,
47
- fetchHtml: () => fetchHtml,
41
+ customEvent: () => customEvent,
42
+ defineComponent: () => defineComponent,
48
43
  fetchStyle: () => fetchStyle,
49
- fetchText: () => fetchText,
50
- fileExt: () => fileExt,
51
- h: () => h,
52
- keb2cam: () => keb2cam,
53
- keb2pas: () => keb2pas,
54
- nextAnimationFrame: () => nextAnimationFrame,
55
- pas2keb: () => pas2keb,
56
- registerComponent: () => registerComponent,
57
- sleep: () => sleep
44
+ fetchTemplate: () => fetchTemplate
58
45
  });
59
46
  module.exports = __toCommonJS(src_exports);
60
47
 
61
- // node_modules/jty/lib/misc.js
62
- function isDef(x) {
63
- return x !== void 0;
64
- }
65
- function isFn(x) {
66
- return typeof x === "function";
67
- }
68
-
69
- // node_modules/jty/lib/number.js
70
- var { isNaN, isFinite, isInteger } = Number;
71
- function isNum(x) {
72
- return typeof x === "number" && !isNaN(x);
73
- }
74
- function inRange(x, min, max) {
75
- if (!isNum(x)) {
76
- throw new TypeError(`inRange(): "x" must be a number. Got ${x} (${typeof x})`);
77
- }
78
- if (isDef(min)) {
79
- if (!isNum(min)) {
80
- throw new TypeError(`inRange(): "min" must be a number. Got ${min} (${typeof min})`);
81
- }
82
- if (isDef(max)) {
83
- if (!isNum(max)) {
84
- throw new TypeError(`inRange(): "max" must be a number. Got ${max} (${typeof max})`);
85
- }
86
- if (min > max) {
87
- return max <= x && x <= min;
88
- }
89
- return min <= x && x <= max;
90
- }
91
- return x >= min;
92
- } else if (isDef(max)) {
93
- if (!isNum(max)) {
94
- throw new TypeError(`inRange(): "max" must be a number. Got ${max} (${typeof max})`);
95
- }
96
- return x <= max;
97
- }
98
- throw new TypeError(`inRange(): expected at least min or max to be defined. Got min=${min} and max=${max}`);
99
- }
100
-
101
- // node_modules/jty/lib/array.js
102
- var { isArray } = Array;
103
- function isArr(x, minLen = 0, maxLen) {
104
- return isArray(x) && inRange(x.length, minLen, maxLen);
105
- }
106
-
107
- // node_modules/jty/lib/object.js
108
- var { hasOwnProperty } = Object;
109
- function isObj(x) {
110
- return Boolean(x) && typeof x === "object";
111
- }
112
- function isA(x, classConstructor) {
113
- if (!isFn(classConstructor)) {
114
- throw new TypeError(`Expected a constructor function. Got ${classConstructor} (${typeof classConstructor})`);
115
- }
116
- return x instanceof classConstructor;
48
+ // src/internal.ts
49
+ var NS = "http://www.w3.org/";
50
+ var MATHML_NS = NS + "1998/Math/MathML";
51
+ var SVG_NS = NS + "2000/svg";
52
+ function errMsg(varName, expected, received, extra) {
53
+ const ret = `Expected '${varName}' to be ${expected}. Got ${received} (${typeof received})`;
54
+ return extra ? `${ret}.
55
+ ${extra}` : ret;
117
56
  }
118
- function hasProp(x, ...propNames) {
119
- if (!isObj(x)) {
120
- return false;
121
- }
122
- for (let propName of propNames) {
123
- if (!(propName in x)) {
124
- return false;
125
- }
126
- }
127
- return true;
57
+ function typeErr(varName, expected, received, extra) {
58
+ return new TypeError(errMsg(varName, expected, received, extra));
128
59
  }
129
-
130
- // node_modules/jty/lib/string.js
131
60
  function isStr(x) {
132
61
  return typeof x === "string";
133
62
  }
134
-
135
- // node_modules/jty/lib/same.js
136
- var { hasOwnProperty: hasOwnProperty2 } = Object;
137
- var { isArray: isArray2 } = Array;
138
-
139
- // src/internal.ts
140
- function errMsg(varName, expected, received) {
141
- return `Expected '${varName}' to be ${expected}. Got ${received} (${typeof received})`;
142
- }
143
- function typeErr(varName, expected, received) {
144
- return new TypeError(errMsg(varName, expected, received));
145
- }
146
-
147
- // src/util.ts
148
- function fileExt(path) {
149
- if (!isStr(path)) {
150
- throw typeErr("path", "a string", path);
151
- }
152
- const lastDotIndex = path.lastIndexOf(".");
153
- if (lastDotIndex === -1) {
154
- return "";
155
- }
156
- const ext = path.slice(lastDotIndex + 1);
157
- if (ext.indexOf("/") !== -1) {
158
- return "";
159
- }
160
- return ext.toLowerCase().trim();
161
- }
162
- function nextAnimationFrame() {
163
- return new Promise((resolve) => requestAnimationFrame(resolve));
164
- }
165
- function sleep(ms = 0) {
166
- return new Promise((resolve) => setTimeout(resolve, ms));
63
+ function isNum(x) {
64
+ return typeof x === "number" && !Number.isNaN(x);
167
65
  }
168
- async function cssToStyle(css) {
169
- const sheet = new CSSStyleSheet();
170
- return await sheet.replace(css);
66
+ function isObj(x) {
67
+ return typeof x === "object" && x !== null && Object.getPrototypeOf(x) === Object.prototype;
171
68
  }
172
-
173
- // src/case.ts
174
- function pas2keb(str) {
175
- if (!isStr(str)) {
176
- throw typeErr("str", "a string", str);
177
- }
178
- if (/[^a-zA-Z0-9_]/.test(str)) {
179
- throw new SyntaxError(errMsg("str", "alphanumeric characters and underscores", str));
69
+ function isInstance(x, classConstructor) {
70
+ if (typeof classConstructor !== "function") {
71
+ throw typeErr("classConstructor", "a constructor function", classConstructor);
180
72
  }
181
- return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
73
+ return x instanceof classConstructor;
182
74
  }
183
- function keb2pas(str) {
184
- if (!isStr(str)) {
185
- throw typeErr("str", "a string", str);
186
- }
187
- return str.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("") || // Handle strings that were not kebab-case to begin with (e.g. 'single', 'camelCase')
188
- (str.length > 0 ? str.charAt(0).toUpperCase() + str.slice(1) : "");
75
+ function hasProp(x, propName) {
76
+ return x !== null && typeof x === "object" && propName in x;
189
77
  }
190
78
  function keb2cam(str) {
191
79
  if (!isStr(str)) {
@@ -193,30 +81,68 @@ function keb2cam(str) {
193
81
  }
194
82
  return str.replace(/^-+|-+$/g, "").replace(/-+([a-z])/g, (g, c) => c.toUpperCase());
195
83
  }
84
+ function toStr(x) {
85
+ switch (typeof x) {
86
+ case "string":
87
+ return x;
88
+ case "object":
89
+ if (x === null) {
90
+ return "null";
91
+ }
92
+ try {
93
+ return JSON.stringify(x);
94
+ } catch {
95
+ return String(x);
96
+ }
97
+ case "function":
98
+ return x.toString();
99
+ default:
100
+ return String(x);
101
+ }
102
+ }
196
103
 
197
104
  // src/wrappers/JJET.ts
198
- var _ref, _boundHandlers, _JJET_instances, getBoundHandler_fn;
105
+ function customEvent(type, detail, options) {
106
+ if (!isStr(type)) {
107
+ throw typeErr("eventName", "a string", type, 'Pass an event name like "todo-toggle".');
108
+ }
109
+ return new CustomEvent(type, {
110
+ bubbles: true,
111
+ composed: true,
112
+ ...options,
113
+ detail
114
+ });
115
+ }
116
+ var _ref;
199
117
  var _JJET = class _JJET {
200
118
  /**
201
119
  * Creates a JJET instance.
202
120
  *
203
121
  * @param ref - The EventTarget to wrap.
204
122
  * @throws {TypeError} If `ref` is not an EventTarget.
123
+ * @see {@link JJET.from} for the factory form.
205
124
  */
206
125
  constructor(ref) {
207
- __privateAdd(this, _JJET_instances);
208
126
  __privateAdd(this, _ref);
209
- __privateAdd(this, _boundHandlers, /* @__PURE__ */ new WeakMap());
210
- if (!isA(ref, EventTarget)) {
211
- throw new TypeError(`JJET expects an EventTarget instance. Got ${ref} (${typeof ref}). `);
127
+ if (!isInstance(ref, EventTarget)) {
128
+ throw typeErr("ref", "an EventTarget instance", ref);
212
129
  }
213
130
  __privateSet(this, _ref, ref);
214
131
  }
132
+ /**
133
+ * Creates a JJET instance from an EventTarget reference.
134
+ *
135
+ * @param ref - The EventTarget instance.
136
+ * @returns A new JJET instance.
137
+ * @see {@link constructor} for input validation behavior.
138
+ */
215
139
  static from(ref) {
216
140
  return new _JJET(ref);
217
141
  }
218
142
  /**
219
143
  * Gets the underlying DOM object.
144
+ *
145
+ * @see {@link run} for fluent callbacks that can also access this same wrapped target.
220
146
  */
221
147
  get ref() {
222
148
  return __privateGet(this, _ref);
@@ -224,16 +150,25 @@ var _JJET = class _JJET {
224
150
  /**
225
151
  * Adds an event listener.
226
152
  *
227
- * @remarks
228
- * The handler is automatically bound to this JJET instance, so `this` inside the handler
229
- * refers to the JJET instance, not the DOM element. To access the DOM element, use `this.ref`.
153
+ * @example
154
+ * ```ts
155
+ * const onResize = () => {
156
+ * console.log('resized')
157
+ * }
158
+ *
159
+ * JJET.from(window).on('resize', onResize)
160
+ * ```
230
161
  *
231
162
  * @example
232
163
  * ```ts
233
- * JJET.from(window).on('resize', function() {
234
- * console.log(this) // JJET instance
235
- * console.log(this.ref) // window object
236
- * })
164
+ * const target = {
165
+ * count: 0,
166
+ * increment() {
167
+ * this.count++
168
+ * },
169
+ * }
170
+ *
171
+ * JJET.from(window).on('click', target.increment.bind(target))
237
172
  * ```
238
173
  * @param eventName - The name of the event.
239
174
  * @param handler - The event handler.
@@ -242,8 +177,7 @@ var _JJET = class _JJET {
242
177
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener | EventTarget.addEventListener}
243
178
  */
244
179
  on(eventName, handler, options) {
245
- const boundHandler = __privateMethod(this, _JJET_instances, getBoundHandler_fn).call(this, handler);
246
- this.ref.addEventListener(eventName, boundHandler, options);
180
+ this.ref.addEventListener(eventName, handler, options);
247
181
  return this;
248
182
  }
249
183
  /**
@@ -254,10 +188,15 @@ var _JJET = class _JJET {
254
188
  *
255
189
  * @example
256
190
  * ```ts
257
- * const handler = function() { console.log(this) }
258
- * JJET.from(window).on('resize', handler)
259
- * JJET.from(window).off('resize', handler)
191
+ * const onResize = () => {
192
+ * console.log('resized')
193
+ * }
194
+ *
195
+ * const win = JJET.from(window)
196
+ * win.on('resize', onResize)
197
+ * win.off('resize', onResize)
260
198
  * ```
199
+ *
261
200
  * @param eventName - The name of the event.
262
201
  * @param handler - The event handler.
263
202
  * @param options - Optional event listener options or boolean.
@@ -265,8 +204,7 @@ var _JJET = class _JJET {
265
204
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener | EventTarget.removeEventListener}
266
205
  */
267
206
  off(eventName, handler, options) {
268
- const boundHandler = __privateMethod(this, _JJET_instances, getBoundHandler_fn).call(this, handler);
269
- this.ref.removeEventListener(eventName, boundHandler, options);
207
+ this.ref.removeEventListener(eventName, handler, options);
270
208
  return this;
271
209
  }
272
210
  /**
@@ -281,54 +219,70 @@ var _JJET = class _JJET {
281
219
  return this;
282
220
  }
283
221
  /**
284
- * Runs a function in the context of this JJET instance.
222
+ * Creates and dispatches a `CustomEvent` on the wrapped target.
223
+ *
224
+ * @remarks
225
+ * This is a convenience wrapper around {@link trigger} for the common case of
226
+ * dispatching a payload-bearing custom event.
285
227
  *
228
+ * The created event defaults to `bubbles: true` and `composed: true`.
229
+ * Pass `options` to override those defaults.
230
+ *
231
+ * @param eventName - The event type name.
232
+ * @param detail - Optional payload exposed as `event.detail`.
233
+ * @param options - Additional `CustomEvent` options excluding `detail`.
234
+ * @returns This instance for chaining.
235
+ * @throws {TypeError} If `eventName` is not a string.
286
236
  * @example
287
237
  * ```ts
288
- * node.run(function() {
289
- * console.log(this.ref)
290
- * })
238
+ * JJET.from(window).triggerCustomEvent('panel-ready', { id: '123' })
291
239
  * ```
292
- * @remarks
293
- * If you want to access the current JJ* instance using `this` keyword, you SHOULD use a `function` not an arrow function.
294
- * If the function throws, `run()` doesn't swallow the exception.
295
- * So if you're expecting an error, make sure to wrap it in a `try..catch` block and handle the exception.
296
- * If the function returns a promise, you can `await` on the response.
297
240
  *
298
- * @param fn - The function to run. `this` inside the function will refer to this JJET instance.
299
- * @param args - Arguments to pass to the function.
300
- * @returns The return value of the function.
241
+ * @example
242
+ * ```ts
243
+ * JJET.from(this).triggerCustomEvent('todo-toggle', {
244
+ * id: '123',
245
+ * done: true,
246
+ * })
247
+ * ```
248
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent | CustomEvent()}
301
249
  */
302
- run(fn, ...args) {
303
- return fn.call(this, ...args);
250
+ triggerCustomEvent(eventName, detail, options) {
251
+ return this.trigger(customEvent(eventName, detail, options));
304
252
  }
305
- };
306
- _ref = new WeakMap();
307
- _boundHandlers = new WeakMap();
308
- _JJET_instances = new WeakSet();
309
- /**
310
- * Gets or creates a bound version of the handler.
311
- *
312
- * @remarks
313
- * Bound handlers are cached in a WeakMap to ensure `off()` can properly remove listeners.
314
- * When the original handler is garbage collected, the bound version is automatically removed.
315
- *
316
- * @param handler - The event handler to bind.
317
- * @returns The bound handler, or null if the input is null.
318
- */
319
- getBoundHandler_fn = function(handler) {
320
- if (handler === null) return null;
321
- let bound = __privateGet(this, _boundHandlers).get(handler);
322
- if (!bound) {
323
- if (typeof handler === "function") {
324
- bound = handler.bind(this);
325
- } else {
326
- bound = { handleEvent: handler.handleEvent.bind(this) };
253
+ /**
254
+ * Runs a function in the context of this JJET instance.
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * node
259
+ * .run(function (context) {
260
+ * console.log(this.ref)
261
+ * console.log(context.ref)
262
+ * })
263
+ * .trigger(new Event('ready'))
264
+ * ```
265
+ * @remarks
266
+ * Use this to make synchronous adjustments while staying in a fluent chain.
267
+ * The callback return value is ignored.
268
+ * If you want to access the current JJ* instance using `this`, use a `function` rather than an arrow function.
269
+ *
270
+ * @param fn - The synchronous function to run. `this` inside the function will refer to this JJET instance, and the wrapped instance is also passed as the first argument.
271
+ * @returns This instance for chaining.
272
+ * @see {@link ref} for direct access to the wrapped native target.
273
+ * @see {@link on} for event listener chaining.
274
+ * @see {@link trigger} for dispatching events in-chain.
275
+ */
276
+ run(fn) {
277
+ try {
278
+ fn.call(this, this);
279
+ } catch (cause) {
280
+ throw new Error(`Failed to run the function`, { cause });
327
281
  }
328
- __privateGet(this, _boundHandlers).set(handler, bound);
282
+ return this;
329
283
  }
330
- return bound;
331
284
  };
285
+ _ref = new WeakMap();
332
286
  var JJET = _JJET;
333
287
 
334
288
  // src/wrappers/JJN-raw.ts
@@ -336,6 +290,12 @@ var JJN = class _JJN extends JJET {
336
290
  /**
337
291
  * Creates a JJN instance from a Node reference.
338
292
  *
293
+ * @remarks
294
+ * For better type safety, use the specific wrapper type if you know the Node type:
295
+ * {@link JJHE} for HTMLElement, {@link JJSE} for SVGElement, {@link JJT} for Text, etc.
296
+ *
297
+ * Alternatively, use {@link JJN.wrap} to automatically determine and create the appropriate wrapper.
298
+ *
339
299
  * @example
340
300
  * ```ts
341
301
  * const node = JJN.from(document.createTextNode('hello'))
@@ -343,6 +303,7 @@ var JJN = class _JJN extends JJET {
343
303
  *
344
304
  * @param node - The Node instance.
345
305
  * @returns A new JJN instance.
306
+ * @see {@link JJN.wrap} for automatic wrapper selection
346
307
  */
347
308
  static from(node) {
348
309
  return new _JJN(node);
@@ -351,75 +312,76 @@ var JJN = class _JJN extends JJET {
351
312
  * Checks if a value can be passed to the `wrap()` or `unwrap()` function.
352
313
  *
353
314
  * @remarks
354
- * This is useful for filtering the array that is passed to `append()`, `prepend()` or `setChildren()`
315
+ * This is useful for filtering the array that is passed to `append()`, `prepend()` or `setChildren()`.
316
+ * See {@link Wrappable} type for the full definition.
355
317
  *
356
318
  * @param x an unknown value
357
319
  * @returns true if `x` is a string, Node (or its descendent), JJN (or its descendent)
320
+ * @see {@link JJN.wrap} for converting Wrappable into wrappers.
321
+ * @see {@link JJN.unwrap} for converting Wrappable into native nodes.
358
322
  */
359
323
  static isWrappable(x) {
360
- return isStr(x) || isA(x, Node) || isA(x, _JJN);
324
+ return isStr(x) || isInstance(x, Node) || isInstance(x, _JJN);
361
325
  }
362
326
  /**
363
327
  * Wraps a native DOM node or string into the most specific JJ wrapper available.
364
328
  *
365
329
  * @remarks
366
- * This function acts as a factory, inspecting the input type and returning the appropriate
367
- * subclass of `JJN` (e.g., `JJHE` for `HTMLElement`, `JJT` for `Text`).
368
- * JJN.ts overrides this method to a richer version that handles all subclasses of JJN.
330
+ * This factory function acts as an intelligent wrapper, inspecting the input type and returning the appropriate
331
+ * subclass of `JJN` (e.g., `JJHE` for `HTMLElement`, `JJT` for `Text`, `JJSE` for `SVGElement`, etc.).
332
+ * Strings are automatically converted to Text nodes wrapped in `JJT`.
333
+ *
334
+ * If the input is already a JJ wrapper, it is returned unchanged (no double-wrapping).
335
+ * See the full implementation in src/wrappers/JJN.ts which extends this with support for internal wrapper types.
369
336
  *
370
337
  * @example
371
338
  * ```ts
372
- * const bodyWrapper = JJN.wrap(document.body) // Returns JJHE
339
+ * const bodyWrapper = JJN.wrap(document.body) // Returns JJHE<HTMLBodyElement>
373
340
  * const textWrapper = JJN.wrap('Hello') // Returns JJT wrapping a new Text node
341
+ * const existing = JJN.wrap(alreadyWrapped) // Returns alreadyWrapped unchanged
374
342
  * ```
375
343
  *
376
344
  * @param raw - The object to wrap. If it's already Wrapped, it'll be returned without any change. We don't double-wrap or clone it.
377
345
  * @returns The most granular Wrapped subclass instance. If the input is already wrapped, it'll be returned as is without cloning.
378
- * @throws {TypeError} If the input is not a Node, string, or JJ wrapper.
346
+ * @see {@link JJN.from} for explicit base wrapper construction.
347
+ * @see {@link JJN.unwrap} for the reverse conversion.
379
348
  */
380
349
  static wrap(raw) {
381
- if (isObj(raw)) {
382
- if (isA(raw, _JJN)) {
383
- return raw;
384
- }
385
- if (isA(raw, Node)) {
386
- return new _JJN(raw);
387
- }
350
+ if (isInstance(raw, _JJN)) {
351
+ return raw;
388
352
  }
389
- throw typeErr("raw", "a Node", raw);
353
+ if (isInstance(raw, Node)) {
354
+ return new _JJN(raw);
355
+ }
356
+ return _JJN.from(document.createTextNode(toStr(raw)));
390
357
  }
391
358
  /**
392
359
  * Extracts the underlying native DOM node from a wrapper.
393
360
  *
394
361
  * @remarks
395
362
  * If the input is already a native Node, it is returned as is.
396
- * If the input is a string, a new Text node is created and returned.
363
+ * If the input is a JJ wrapper, its underlying node is returned.
364
+ * Otherwise, the input is coerced into a Text node.
365
+ * Plain objects are stringified with JSON when possible, and fall back to `String(...)`.
397
366
  *
398
367
  * @example
399
368
  * ```ts
400
369
  * const rawElement = JJN.unwrap(myJJHE) // Returns HTMLElement
401
370
  * ```
402
371
  *
403
- * @param obj - The object to unwrap.
372
+ * @param obj - The value to unwrap.
404
373
  * @returns The underlying DOM node.
405
- * @throws {TypeError} If the input cannot be unwrapped.
374
+ * @see {@link JJN.wrap} for the reverse conversion.
375
+ * @see {@link JJN.isWrappable} for pre-validation checks.
406
376
  */
407
377
  static unwrap(obj) {
408
- if (isStr(obj)) {
409
- return document.createTextNode(obj);
410
- }
411
- if (!isObj(obj)) {
412
- throw new TypeError(`JJN.unwrap() expects a string, DOM Node, or JJ wrapper. Got ${obj} (${typeof obj}). `);
413
- }
414
- if (isA(obj, Node)) {
378
+ if (isInstance(obj, Node)) {
415
379
  return obj;
416
380
  }
417
- if (isA(obj, _JJN)) {
381
+ if (isInstance(obj, _JJN)) {
418
382
  return obj.ref;
419
383
  }
420
- throw new TypeError(
421
- `Could not unwrap ${obj} (${typeof obj}). Expected a string, Node, or JJ wrapper. Make sure you're passing a valid DOM element or JJ wrapper.`
422
- );
384
+ return document.createTextNode(toStr(obj));
423
385
  }
424
386
  /**
425
387
  * Wraps an iterable object (e.g. an array of wrapped or DOM elements).
@@ -431,6 +393,8 @@ var JJN = class _JJN extends JJET {
431
393
  *
432
394
  * @param iterable - The iterable to wrap.
433
395
  * @returns An array of wrapped instances.
396
+ * @see {@link JJN.wrap} for single-item wrapping.
397
+ * @see {@link JJN.unwrapAll} for the reverse iterable conversion.
434
398
  */
435
399
  static wrapAll(iterable) {
436
400
  return Array.from(iterable, _JJN.wrap);
@@ -445,6 +409,8 @@ var JJN = class _JJN extends JJET {
445
409
  *
446
410
  * @param iterable - The iterable to unwrap.
447
411
  * @returns An array of native DOM nodes.
412
+ * @see {@link JJN.unwrap} for single-item unwrapping.
413
+ * @see {@link JJN.wrapAll} for iterable wrapping.
448
414
  */
449
415
  static unwrapAll(iterable) {
450
416
  return Array.from(iterable, _JJN.unwrap);
@@ -454,52 +420,31 @@ var JJN = class _JJN extends JJET {
454
420
  *
455
421
  * @param ref - The Node to wrap.
456
422
  * @throws {TypeError} If `ref` is not a Node.
423
+ * @see {@link JJN.from} for the factory form.
424
+ * @see {@link JJN.wrap} for automatic subtype wrapping.
457
425
  */
458
426
  constructor(ref) {
459
- if (!isA(ref, Node)) {
460
- throw new TypeError(
461
- `JJN expects a Node instance. Got ${ref} (${typeof ref}). Use JJN.from(node) with a DOM Node, or check that you're passing a valid DOM element.`
462
- );
427
+ if (!isInstance(ref, Node)) {
428
+ throw typeErr("ref", "a DOM Node instance", ref);
463
429
  }
464
430
  super(ref);
465
431
  }
466
- /**
467
- * Gets the parent node wrapped in the most specific JJ wrapper available.
468
- *
469
- * @remarks
470
- * Returns `null` when this node is detached and therefore has no parent.
471
- *
472
- * @example
473
- * ```ts
474
- * const text = JJT.fromStr('hello')
475
- * JJHE.create('div').addChild(text)
476
- * const parent = text.parent // JJHE
477
- * ```
478
- *
479
- * @returns The wrapped parent node, or `null` if this node has no parent.
480
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode | Node.parentNode}
481
- */
482
- get parent() {
432
+ getParent(required = false) {
483
433
  const { parentNode } = this.ref;
484
- return parentNode ? _JJN.wrap(parentNode) : null;
434
+ if (parentNode) {
435
+ return _JJN.wrap(parentNode);
436
+ }
437
+ if (required) {
438
+ throw new ReferenceError("Node has no parent. Did you forget to attach it to the document?");
439
+ }
440
+ return null;
485
441
  }
486
- /**
487
- * Gets the child nodes wrapped in the most specific JJ wrappers available.
488
- *
489
- * @remarks
490
- * Returns an empty array when this node has no children.
491
- *
492
- * @example
493
- * ```ts
494
- * const el = JJHE.create('div').addChild('hello', JJHE.create('span'))
495
- * const children = el.children // [JJT, JJHE]
496
- * ```
497
- *
498
- * @returns The wrapped child nodes.
499
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes | Node.childNodes}
500
- */
501
- get children() {
502
- return _JJN.wrapAll(this.ref.childNodes);
442
+ getChildren(required = false) {
443
+ const children = _JJN.wrapAll(this.ref.childNodes);
444
+ if (required && children.length === 0) {
445
+ throw new ReferenceError("Node has no children. Did you forget to initialize or append them?");
446
+ }
447
+ return children;
503
448
  }
504
449
  /**
505
450
  * Clones the Node.
@@ -519,8 +464,9 @@ var JJN = class _JJN extends JJET {
519
464
  *
520
465
  * @example
521
466
  * ```ts
467
+ * const doc = JJD.from(document)
522
468
  * const el = JJHE.create('div')
523
- * doc.body.addChild(el)
469
+ * doc.find('body', true).addChild(el)
524
470
  * el.rm()
525
471
  * ```
526
472
  *
@@ -544,9 +490,12 @@ var JJN = class _JJN extends JJET {
544
490
  * ```ts
545
491
  * el.addText('Hello ')
546
492
  * el.addText('World')
493
+ * // Behaves like document.createTextNode('Hello World') and appends it to el
494
+ * // Falsy values are converted to their string representation, except for empty string which is added as is.
495
+ * el.addText('Hello', '', 'world', null, undefined, '!!!') // Adds 6 text nodes with content 'Hello', '', 'world', 'null', 'undefined', and '!!!' respectively.
547
496
  * ```
548
497
  *
549
- * @param text - The text to add. If null or undefined, nothing is added.
498
+ * @param textArr - The text to add. The actual text that's added follows the rules in document.createTextNode() which is basically what you get from String()
550
499
  * @returns This instance for chaining.
551
500
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode | document.createTextNode}
552
501
  */
@@ -559,29 +508,17 @@ var JJN = class _JJN extends JJET {
559
508
  };
560
509
 
561
510
  // src/wrappers/JJNx.ts
511
+ function notNullish(x) {
512
+ return x != null;
513
+ }
562
514
  var JJNx = class extends JJN {
563
- /**
564
- * Finds the first element matching a selector within this Element.
565
- *
566
- * @example
567
- * ```ts
568
- * const span = el.find('span') // Returns null if not found
569
- * const span = el.find('span', true) // Throws if not found
570
- * ```
571
- *
572
- * @param selector - The CSS selector.
573
- * @param required - Whether to throw an error if not found. Defaults to false.
574
- * @returns The wrapped element, or null if not found and required is false.
575
- * @throws {TypeError} If selector is not a string or element not found and required is true.
576
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector | Element.querySelector}
577
- */
578
515
  find(selector, required = false) {
579
516
  const queryResult = this.ref.querySelector(selector);
580
517
  if (queryResult) {
581
518
  return JJN.wrap(queryResult);
582
519
  }
583
520
  if (required) {
584
- throw new TypeError(`No element matched query "${selector}"`);
521
+ throw new ReferenceError(`No element matched query "${selector}"`);
585
522
  }
586
523
  return null;
587
524
  }
@@ -606,58 +543,128 @@ var JJNx = class extends JJN {
606
543
  *
607
544
  * @example
608
545
  * ```ts
609
- * el.addChild(h('span', null, 'hello'))
546
+ * el.addChild(JJHE.tree('span', null, 'hello'))
610
547
  * ```
611
548
  *
612
549
  * @remarks
613
- * To make template codes easier, this function ignores any child that is not possible to `wrap()` (e.g. undefined, null, false).
550
+ * To make template codes easier, this function ignores nullish children (`null` and `undefined`).
551
+ * Other non-node values are coerced into Text nodes.
614
552
  *
615
553
  * @param children - The children to append.
616
554
  * @returns This instance for chaining.
555
+ * @see {@link addChildren} for the array-based form.
556
+ * @see {@link addChildMap} for mapping arrays into appended children.
617
557
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/append | Element.append}
618
558
  */
619
559
  addChild(...children) {
620
- const nodes = JJN.unwrapAll(children.filter(JJN.isWrappable));
560
+ const nodes = JJN.unwrapAll(children.filter(notNullish));
621
561
  this.ref.append(...nodes);
622
562
  return this;
623
563
  }
564
+ /**
565
+ * Appends an array of children to this Element.
566
+ *
567
+ * @remarks
568
+ * This is the array-based companion to {@link addChild}.
569
+ * To make template codes easier, this function ignores nullish children (`null` and `undefined`).
570
+ * Other non-node values are coerced into Text nodes.
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * el.addChildren([JJHE.tree('span', null, 'hello'), ' world'])
575
+ * ```
576
+ *
577
+ * @param children - The children to append.
578
+ * @returns This instance for chaining.
579
+ * @throws {TypeError} If `children` is not an array.
580
+ * @see {@link addChild} for the variadic form.
581
+ * @see {@link addChildMap} for mapping arrays into appended children.
582
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/append | Element.append}
583
+ */
584
+ addChildren(children) {
585
+ if (!Array.isArray(children)) {
586
+ throw typeErr("children", "an array of Wrappable", children);
587
+ }
588
+ return this.addChild(...children);
589
+ }
624
590
  /**
625
591
  * Prepends children to this Element.
626
592
  *
627
593
  * @example
628
594
  * ```ts
629
- * el.preChild(h('span', null, 'first'))
595
+ * el.preChild(JJHE.tree('span', null, 'first'))
630
596
  * ```
631
597
  *
632
598
  * @remarks
633
- * To make template codes easier, this function ignores any child that is not possible to `wrap()` (e.g. undefined, null, false).
599
+ * To make template codes easier, this function ignores nullish children (`null` and `undefined`).
600
+ * Other non-node values are coerced into Text nodes.
634
601
  *
635
602
  * @param children - The children to prepend.
636
603
  * @returns This instance for chaining.
604
+ * @see {@link preChildren} for the array-based form.
605
+ * @see {@link preChildMap} for mapping arrays into prepended children.
637
606
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/prepend | Element.prepend}
638
607
  */
639
608
  preChild(...children) {
640
- const nodes = JJN.unwrapAll(children.filter(JJN.isWrappable));
609
+ const nodes = JJN.unwrapAll(children.filter(notNullish));
641
610
  this.ref.prepend(...nodes);
642
611
  return this;
643
612
  }
613
+ /**
614
+ * Prepends an array of children to this Element.
615
+ *
616
+ * @remarks
617
+ * This is the array-based companion to {@link preChild}.
618
+ * To make template codes easier, this function ignores nullish children (`null` and `undefined`).
619
+ * Other non-node values are coerced into Text nodes.
620
+ *
621
+ * @example
622
+ * ```ts
623
+ * el.preChildren([JJHE.tree('span', null, 'first'), ' child'])
624
+ * ```
625
+ *
626
+ * @param children - The children to prepend.
627
+ * @returns This instance for chaining.
628
+ * @throws {TypeError} If `children` is not an array.
629
+ * @see {@link preChild} for the variadic form.
630
+ * @see {@link preChildMap} for mapping arrays into prepended children.
631
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/prepend | Element.prepend}
632
+ */
633
+ preChildren(children) {
634
+ if (!Array.isArray(children)) {
635
+ throw typeErr("children", "an array of Wrappable", children);
636
+ }
637
+ return this.preChild(...children);
638
+ }
644
639
  /**
645
640
  * Maps an array to children and appends them.
646
641
  *
647
642
  * @example
648
643
  * ```ts
649
- * node.addChildMap(['a', 'b'], item => h('li', null, item))
644
+ * node.addChildMap(['a', 'b'], item => JJHE.tree('li', null, item))
650
645
  * ```
651
646
  *
652
647
  * @remarks
653
- * To make template codes easier, this function ignores any child that is not possible to `wrap()` (e.g. undefined, null, false).
648
+ * To make template codes easier, this function ignores nullish children (`null` and `undefined`).
649
+ * Other non-node values are coerced into Text nodes.
654
650
  *
655
651
  * @param array - The source array.
656
652
  * @param mapFn - The mapping function returning a Wrappable.
657
653
  * @returns This instance for chaining.
654
+ * @throws {TypeError} If `array` is not an array.
655
+ * @throws {Error} If mapping the array or appending the children fails.
656
+ * @see {@link addChild} for directly appending variadic children.
657
+ * @see {@link addChildren} for appending a pre-built array of children.
658
658
  */
659
659
  addChildMap(array, mapFn) {
660
- return this.addChild(...array.map(mapFn));
660
+ if (!Array.isArray(array)) {
661
+ throw typeErr("array", "an array", array);
662
+ }
663
+ try {
664
+ return this.addChildren(array.map(mapFn));
665
+ } catch (cause) {
666
+ throw new Error("Failed to map array to children", { cause });
667
+ }
661
668
  }
662
669
  /**
663
670
  * Maps an array to children and prepends them.
@@ -668,35 +675,116 @@ var JJNx = class extends JJN {
668
675
  * ```
669
676
  *
670
677
  * @remarks
671
- * To make template codes easier, this function ignores any child that is not possible to `wrap()` (e.g. undefined, null, false).
678
+ * To make template codes easier, this function ignores nullish children (`null` and `undefined`).
679
+ * Other non-node values are coerced into Text nodes.
672
680
  *
673
681
  * @param array - The source array.
674
682
  * @param mapFn - The mapping function.
675
683
  * @returns This instance for chaining.
684
+ * @throws {TypeError} If `array` is not an array.
685
+ * @throws {Error} If mapping the array or prepending the children fails.
686
+ * @see {@link preChild} for directly prepending variadic children.
687
+ * @see {@link preChildren} for prepending a pre-built array of children.
676
688
  */
677
689
  preChildMap(array, mapFn) {
678
- return this.preChild(...array.map(mapFn));
690
+ if (!Array.isArray(array)) {
691
+ throw typeErr("array", "an array", array);
692
+ }
693
+ try {
694
+ return this.preChildren(array.map(mapFn));
695
+ } catch (cause) {
696
+ throw new Error("Failed to map array to children", { cause });
697
+ }
679
698
  }
680
699
  /**
681
- * Replaces the existing children of an Element with a specified new set of children.
700
+ * Replaces the existing children of an Element with new children.
682
701
  *
683
702
  * @remarks
684
703
  * If no children are provided, it empties the Element.
685
- * To make template codes easier, this function ignores any child that is not possible to `wrap()` (e.g. undefined, null, false).
704
+ * To make template codes easier, this function ignores nullish children (`null` and `undefined`).
705
+ * Other non-node values are coerced into Text nodes.
686
706
  *
687
707
  * @example
688
708
  * ```ts
689
- * el.setChildren(h('p', null, 'New Content'))
709
+ * el.setChild(JJHE.tree('p', null, 'New Content'))
690
710
  * ```
691
711
  * @param children - The children to replace with.
692
712
  * @returns This instance for chaining.
713
+ * @see {@link setChildren} for the array-based form.
714
+ * @see {@link setChildMap} for mapping arrays into replacement children.
715
+ * @see {@link empty} for clearing all children.
693
716
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren | Element.replaceChildren}
694
717
  */
695
- setChildren(...children) {
696
- const nodes = JJN.unwrapAll(children.filter(JJN.isWrappable));
697
- this.ref.replaceChildren(...nodes);
718
+ setChild(...children) {
719
+ if (children.length === 0) {
720
+ this.ref.replaceChildren();
721
+ } else {
722
+ const nodes = JJN.unwrapAll(children.filter(notNullish));
723
+ this.ref.replaceChildren(...nodes);
724
+ }
698
725
  return this;
699
726
  }
727
+ /**
728
+ * Replaces the existing children of an Element with an array of children.
729
+ *
730
+ * @remarks
731
+ * This is the array-based companion to {@link setChild}.
732
+ * Passing an empty array empties the Element.
733
+ * To make template codes easier, this function ignores nullish children (`null` and `undefined`).
734
+ * Other non-node values are coerced into Text nodes.
735
+ *
736
+ * @example
737
+ * ```ts
738
+ * el.setChildren([JJHE.tree('p', null, 'New Content')])
739
+ * ```
740
+ *
741
+ * @param children - The children to replace with.
742
+ * @returns This instance for chaining.
743
+ * @throws {TypeError} If `children` is not an array of Wrappable.
744
+ * @see {@link setChild} for the variadic form.
745
+ * @see {@link setChildMap} for mapping arrays into replacement children.
746
+ * @see {@link empty} for clearing all children.
747
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren | Element.replaceChildren}
748
+ */
749
+ setChildren(children) {
750
+ if (!Array.isArray(children)) {
751
+ throw typeErr("children", "an array of Wrappable", children);
752
+ }
753
+ return this.setChild(...children);
754
+ }
755
+ /**
756
+ * Maps an array to children and replaces the existing children with the result.
757
+ *
758
+ * @remarks
759
+ * This is the mapping companion to {@link setChildren}.
760
+ * To make template codes easier, this function ignores mapped nullish children (`null` and `undefined`).
761
+ * Other non-node values are coerced into Text nodes.
762
+ * Errors thrown by the mapping function or child replacement are wrapped with a higher-level error that preserves the original cause.
763
+ *
764
+ * @example
765
+ * ```ts
766
+ * node.setChildMap(['a', 'b'], item => JJHE.tree('li', null, item))
767
+ * ```
768
+ *
769
+ * @param array - The source array.
770
+ * @param mapFn - The mapping function returning a Wrappable.
771
+ * @returns This instance for chaining.
772
+ * @throws {TypeError} If `array` is not an array of Wrappable.
773
+ * @throws {Error} If mapping the array or replacing the children fails.
774
+ * @see {@link setChildren} for replacing children from a pre-built array.
775
+ * @see {@link setChild} for the variadic replacement form.
776
+ * @see {@link empty} for clearing all children without replacements.
777
+ */
778
+ setChildMap(array, mapFn) {
779
+ if (!Array.isArray(array)) {
780
+ throw typeErr("array", "an array of Wrappable", array);
781
+ }
782
+ try {
783
+ return this.setChildren(array.map(mapFn));
784
+ } catch (cause) {
785
+ throw new Error("Failed to map array to children", { cause });
786
+ }
787
+ }
700
788
  /**
701
789
  * Removes all children from this Element.
702
790
  *
@@ -706,11 +794,79 @@ var JJNx = class extends JJN {
706
794
  * ```
707
795
  *
708
796
  * @returns This instance for chaining.
797
+ * @see {@link setChild} for replacing children with a variadic list.
798
+ * @see {@link setChildren} for replacing children with an array.
799
+ * @see {@link setChildMap} for replacing children from mapped input.
709
800
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren | Element.setChildren}
710
801
  */
711
802
  empty() {
712
- this.setChildren();
713
- return this;
803
+ return this.setChild();
804
+ }
805
+ /**
806
+ * Clones and appends template-like input to this node.
807
+ *
808
+ * @remarks
809
+ * Supports HTML strings, native template sources, native Nodes, and any JJ wrapper via {@link JJN}.
810
+ * Wrapper inputs are unwrapped and cloned before append.
811
+ *
812
+ * @param template - The template source to clone and append.
813
+ * @returns This instance for chaining.
814
+ * @throws {TypeError} If the template type is unsupported or a Promise was passed.
815
+ * @see {@link setTemplate} for replacing existing children with a cloned template.
816
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/createRange | Document.createRange}
817
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLTemplateElement | HTMLTemplateElement}
818
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment | DocumentFragment}
819
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement | HTMLElement}
820
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode | Node.cloneNode}
821
+ */
822
+ addTemplate(template) {
823
+ if (isStr(template)) {
824
+ return this.addChild(document.createRange().createContextualFragment(template));
825
+ }
826
+ if (isInstance(template, DocumentFragment) || isInstance(template, HTMLElement)) {
827
+ return this.addChild(
828
+ isInstance(template, HTMLTemplateElement) ? template.content.cloneNode(true) : template.cloneNode(true)
829
+ );
830
+ }
831
+ if (isInstance(template, Node)) {
832
+ return this.addChild(template.cloneNode(true));
833
+ }
834
+ if (isInstance(template, JJN)) {
835
+ return this.addTemplate(template.ref);
836
+ }
837
+ if (isInstance(template, Promise)) {
838
+ throw typeErr(
839
+ "template",
840
+ "not a Promise",
841
+ template,
842
+ "Templates must be provided synchronously. Did you forget to await?"
843
+ );
844
+ }
845
+ throw typeErr(
846
+ "template",
847
+ "a string, Node, DocumentFragment, HTMLElement, JJDF, or JJHE",
848
+ template,
849
+ "Pass an HTML string, a DOM template/fragment/element, or a JJ wrapper."
850
+ );
851
+ }
852
+ /**
853
+ * Clones and sets template-like input as the only children of this node.
854
+ *
855
+ * @remarks
856
+ * Supports HTML strings, native template sources, native Nodes, and any JJ wrapper via {@link JJN}.
857
+ * Wrapper inputs are unwrapped and cloned before set.
858
+ *
859
+ * @example
860
+ * ```ts
861
+ * el.setTemplate('<p>New Content</p>')
862
+ * ```
863
+ * @param template - The template source to clone and set.
864
+ * @returns This instance for chaining.
865
+ * @see {@link addTemplate}
866
+ * @see {@link empty} for clearing children without adding a replacement template.
867
+ */
868
+ setTemplate(template) {
869
+ return this.empty().addTemplate(template);
714
870
  }
715
871
  };
716
872
 
@@ -719,12 +875,15 @@ var JJDF = class _JJDF extends JJNx {
719
875
  /**
720
876
  * Creates a JJDF instance from a DocumentFragment reference.
721
877
  *
878
+ * @remarks
879
+ * Use {@link JJDF.create} to create a new empty DocumentFragment.
880
+ * For other Node types, use {@link JJN.from} or the specific wrapper type.
881
+ *
722
882
  * @example
723
883
  * ```ts
724
884
  * const frag = JJDF.from(myFrag)
725
885
  * ```
726
886
  *
727
- *
728
887
  * @param ref - The DocumentFragment instance.
729
888
  * @returns A new JJDF instance.
730
889
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment | DocumentFragment}
@@ -735,6 +894,9 @@ var JJDF = class _JJDF extends JJNx {
735
894
  /**
736
895
  * Creates a new empty JJDF instance (wraps a new DocumentFragment).
737
896
  *
897
+ * @remarks
898
+ * To wrap an existing DocumentFragment, use {@link JJDF.from}.
899
+ *
738
900
  * @example
739
901
  * ```ts
740
902
  * const frag = JJDF.create()
@@ -751,10 +913,12 @@ var JJDF = class _JJDF extends JJNx {
751
913
  *
752
914
  * @param ref - The DocumentFragment instance to wrap.
753
915
  * @throws {TypeError} If `ref` is not a DocumentFragment.
916
+ * @see {@link JJDF.from} to wrap an existing fragment.
917
+ * @see {@link JJDF.create} to create a new empty fragment.
754
918
  */
755
919
  constructor(ref) {
756
- if (!isA(ref, DocumentFragment)) {
757
- throw typeErr("ref", "a DocumentFragment", ref);
920
+ if (!isInstance(ref, DocumentFragment)) {
921
+ throw typeErr("ref", "a DocumentFragment", ref, "Use JJDF.from(documentFragment) to create an instance.");
758
922
  }
759
923
  super(ref);
760
924
  }
@@ -765,6 +929,10 @@ var JJSR = class _JJSR extends JJDF {
765
929
  /**
766
930
  * Creates a JJSR instance from a ShadowRoot reference.
767
931
  *
932
+ * @remarks
933
+ * Typically created via `element.attachShadow({ mode: 'open' })` and passed here to be wrapped.
934
+ * Inherits from {@link JJDF} (DocumentFragment), so you can use all fragment methods like `find()`, `findAll()`, etc.
935
+ *
768
936
  * @example
769
937
  * ```ts
770
938
  * const shadow = JJSR.from(element.shadowRoot)
@@ -772,6 +940,8 @@ var JJSR = class _JJSR extends JJDF {
772
940
  *
773
941
  * @param shadowRoot - The ShadowRoot instance.
774
942
  * @returns A new JJSR instance.
943
+ * @see {@link JJHE.setShadow} for attaching and initializing a shadow root from a host element.
944
+ * @see {@link JJSR.constructor} for validation behavior.
775
945
  */
776
946
  static from(shadowRoot) {
777
947
  return new _JJSR(shadowRoot);
@@ -781,15 +951,38 @@ var JJSR = class _JJSR extends JJDF {
781
951
  *
782
952
  * @param shadowRoot - The ShadowRoot to wrap.
783
953
  * @throws {TypeError} If `shadowRoot` is not a ShadowRoot.
954
+ * @see {@link JJSR.from} to wrap an existing ShadowRoot
784
955
  */
785
956
  constructor(shadowRoot) {
786
- if (!isA(shadowRoot, ShadowRoot)) {
787
- throw new TypeError(
788
- `JJSR expects a ShadowRoot instance. Got ${shadowRoot} (${typeof shadowRoot}). Access a shadow root using element.shadowRoot after calling element.attachShadow().`
957
+ if (!isInstance(shadowRoot, ShadowRoot)) {
958
+ throw typeErr(
959
+ "shadowRoot",
960
+ "a ShadowRoot instance",
961
+ shadowRoot,
962
+ 'Use JJHE.from(element).setShadow() or element.attachShadow({ mode: "open" }).'
789
963
  );
790
964
  }
791
965
  super(shadowRoot);
792
966
  }
967
+ /**
968
+ * Initializes the ShadowRoot with template content and optional styles.
969
+ *
970
+ * @example
971
+ * ```ts
972
+ * shadow.init('<p>Hello</p>', 'p { color: red; }')
973
+ * ```
974
+ *
975
+ * @param template - The template content to clone into the shadow root.
976
+ * @param styles - Optional styles to add to the shadow root.
977
+ * @returns This instance for chaining.
978
+ * @throws {Error} If the template or styles cannot be applied.
979
+ * @see {@link JJDF.addTemplate} for supported template inputs.
980
+ * @see {@link addStyle} for stylesheet handling.
981
+ */
982
+ init(template, ...styles) {
983
+ this.addTemplate(template).addStyle(...styles);
984
+ return this;
985
+ }
793
986
  /**
794
987
  * Gets the inner HTML of the ShadowRoot.
795
988
  *
@@ -824,19 +1017,46 @@ var JJSR = class _JJSR extends JJDF {
824
1017
  /**
825
1018
  * Adds constructed stylesheets to the ShadowRoot.
826
1019
  *
1020
+ * @remarks
1021
+ * Although this function accepts CSS strings, it converts them to CSSStyleSheet synchronously which has a performance penalty.
1022
+ * For better performance, create CSSStyleSheet instances in advance.
1023
+ *
827
1024
  * @example
828
1025
  * ```ts
829
1026
  * const sheet = new CSSStyleSheet()
830
1027
  * sheet.replaceSync('p { color: red; }')
831
- * shadow.addStyleSheets(sheet)
1028
+ * shadow.addStyle(sheet)
832
1029
  * ```
833
1030
  *
834
- * @param styleSheets - The stylesheets to add.
1031
+ * @param styles - The stylesheets to add.
835
1032
  * @returns This instance for chaining.
836
1033
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/adoptedStyleSheets | ShadowRoot.adoptedStyleSheets}
837
1034
  */
838
- addStyleSheets(...styleSheets) {
839
- this.ref.adoptedStyleSheets.push(...styleSheets);
1035
+ addStyle(...styles) {
1036
+ if (styles.length) {
1037
+ const cssStyleSheets = [];
1038
+ try {
1039
+ for (const sheet of styles) {
1040
+ if (isInstance(sheet, CSSStyleSheet)) {
1041
+ cssStyleSheets.push(sheet);
1042
+ } else if (isStr(sheet)) {
1043
+ const cssSheet = new CSSStyleSheet();
1044
+ cssSheet.replaceSync(sheet);
1045
+ cssStyleSheets.push(cssSheet);
1046
+ } else {
1047
+ throw typeErr(
1048
+ "styleSheets",
1049
+ "CSSStyleSheet instances or CSS strings",
1050
+ sheet,
1051
+ "Pass a CSS string or a stylesheet created with `new CSSStyleSheet()`."
1052
+ );
1053
+ }
1054
+ }
1055
+ } catch (cause) {
1056
+ throw new Error(`Failed to create CSSStyleSheet from provided styles.`, { cause });
1057
+ }
1058
+ this.ref.adoptedStyleSheets.push(...cssStyleSheets);
1059
+ }
840
1060
  return this;
841
1061
  }
842
1062
  };
@@ -846,6 +1066,10 @@ var JJE = class _JJE extends JJNx {
846
1066
  /**
847
1067
  * Creates a JJE instance from an Element reference.
848
1068
  *
1069
+ * @remarks
1070
+ * Use this factory method to wrap an existing Element. For creating new Elements,
1071
+ * use the specific wrapper type: {@link JJHE.create}, {@link JJSE.create}, or {@link JJME.create}.
1072
+ *
849
1073
  * @example
850
1074
  * ```ts
851
1075
  * const el = JJE.from(document.querySelector('.my-class'))
@@ -853,6 +1077,9 @@ var JJE = class _JJE extends JJNx {
853
1077
  *
854
1078
  * @param ref - The Element instance.
855
1079
  * @returns A new JJE instance.
1080
+ * @see {@link JJHE.create} for creating HTMLElements
1081
+ * @see {@link JJSE.create} for creating SVGElements
1082
+ * @see {@link JJME.create} for creating MathMLElements
856
1083
  */
857
1084
  static from(ref) {
858
1085
  return new _JJE(ref);
@@ -862,11 +1089,17 @@ var JJE = class _JJE extends JJNx {
862
1089
  *
863
1090
  * @param ref - The Element to wrap.
864
1091
  * @throws {TypeError} If `ref` is not an Element.
1092
+ * @see {@link JJHE} for wrapping HTMLElements
1093
+ * @see {@link JJSE} for wrapping SVGElements
1094
+ * @see {@link JJME} for wrapping MathMLElements
865
1095
  */
866
1096
  constructor(ref) {
867
- if (!isA(ref, Element)) {
868
- throw new TypeError(
869
- `JJE expects an Element instance. Got ${ref} (${typeof ref}). Use JJE.from(element) with a DOM Element, or use the specific wrapper (JJHE for HTMLElement, JJSE for SVGElement).`
1097
+ if (!isInstance(ref, Element)) {
1098
+ throw typeErr(
1099
+ "ref",
1100
+ "an Element instance",
1101
+ ref,
1102
+ "Use JJHE.from(), JJSE.from(), or JJME.from() with the appropriate Element type."
870
1103
  );
871
1104
  }
872
1105
  super(ref);
@@ -899,16 +1132,61 @@ var JJE = class _JJE extends JJNx {
899
1132
  }
900
1133
  return this.ref.hasAttribute(name);
901
1134
  }
902
- setAttr(nameOrObj, value) {
903
- if (typeof nameOrObj === "string") {
904
- this.ref.setAttribute(nameOrObj, value);
905
- } else if (isObj(nameOrObj)) {
906
- for (const [k, v] of Object.entries(nameOrObj)) {
907
- this.ref.setAttribute(k, v);
908
- }
909
- } else {
910
- throw typeErr("nameOrObj", "a string or object", nameOrObj);
911
- }
1135
+ /**
1136
+ * Sets a single attribute on the Element.
1137
+ *
1138
+ * @example
1139
+ * ```ts
1140
+ * el.setAttr('id', 'my-id')
1141
+ * el.setAttr('x', 50) // Numbers are automatically converted
1142
+ * ```
1143
+ *
1144
+ * @throws {TypeError} If arguments are invalid types.
1145
+ * @see {@link setAttrs} for setting multiple attributes at once.
1146
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute | Element.setAttribute}
1147
+ */
1148
+ setAttr(name, value) {
1149
+ if (!isStr(name)) {
1150
+ throw typeErr("name", "a string", name);
1151
+ }
1152
+ this.ref.setAttribute(name, value);
1153
+ return this;
1154
+ }
1155
+ /**
1156
+ * Sets multiple attributes from an object, or no-ops for nullish input.
1157
+ *
1158
+ * @remarks
1159
+ * This helper is useful for optional attribute bags in builder APIs.
1160
+ * - `null` or `undefined`: does nothing and returns `this`
1161
+ * - plain object: sets each attribute on the element
1162
+ * - anything else: throws `TypeError`
1163
+ *
1164
+ * @example
1165
+ * ```ts
1166
+ * el.setAttrs({ id: 'app', role: 'main' })
1167
+ * el.setAttrs(null) // no-op
1168
+ * ```
1169
+ *
1170
+ * @param attributes - Attributes object or nullish to skip.
1171
+ * @returns This instance for chaining.
1172
+ * @throws {TypeError} If `attributes` is not nullish and not a plain object.
1173
+ * @see {@link setAttr} for setting a single attribute.
1174
+ */
1175
+ setAttrs(attributes) {
1176
+ if (attributes == null) {
1177
+ return this;
1178
+ }
1179
+ if (!isObj(attributes)) {
1180
+ throw typeErr(
1181
+ "attributes",
1182
+ "a plain object",
1183
+ attributes,
1184
+ 'Pass null/undefined or an object like { id: "app" }.'
1185
+ );
1186
+ }
1187
+ for (const [name, value] of Object.entries(attributes)) {
1188
+ this.setAttr(name, value);
1189
+ }
912
1190
  return this;
913
1191
  }
914
1192
  /**
@@ -934,6 +1212,73 @@ var JJE = class _JJE extends JJNx {
934
1212
  }
935
1213
  return this;
936
1214
  }
1215
+ /**
1216
+ * Conditionally sets or removes a boolean-style (presence-based) attribute, or
1217
+ * auto-toggles it when called without a `force` value.
1218
+ *
1219
+ * @remarks
1220
+ * HTML boolean attributes (e.g. `disabled`, `hidden`, `checked`, `readonly`, `required`)
1221
+ * are active whenever they are **present**, regardless of their value.
1222
+ *
1223
+ * This method has two modes:
1224
+ *
1225
+ * **Explicit mode** — pass any `force` value other than `undefined`:
1226
+ * - Truthy `force` → sets the attribute to `""` (presence = active).
1227
+ * - Falsy `force` (e.g. `false`, `null`, `0`, `""`) → removes the attribute. For `undefined`, see auto mode.
1228
+ *
1229
+ * **Auto mode** — omit `force` entirely (i.e. call with one argument) or pass `undefined` explicitly:
1230
+ * - Delegates to the native `Element.toggleAttribute()`: removes the attribute if
1231
+ * present, adds it (as `""`) if absent. Useful for click handlers and event
1232
+ * callbacks where you just want to flip the current state.
1233
+ *
1234
+ * > **Warning:** Passing `undefined` explicitly — `swAttr('disabled', undefined)` —
1235
+ * > is treated as **auto mode**, not as an explicit remove. If you need to
1236
+ * > unconditionally remove the attribute, use {@link rmAttr} instead.
1237
+ *
1238
+ * ARIA attributes are not HTML boolean attributes. Even boolean-like ARIA states
1239
+ * such as `aria-hidden`, `aria-disabled`, and `aria-readonly` require explicit
1240
+ * string values like `"true"` or `"false"`, so they should be managed with
1241
+ * {@link setAriaAttr} / {@link rmAriaAttr}, not with `swAttr()`.
1242
+ *
1243
+ * For other attributes that carry a meaningful string value (e.g.
1244
+ * `dir="rtl"`, `hidden="until-found"`), use {@link setAttr} and {@link rmAttr} directly.
1245
+ *
1246
+ * @example
1247
+ * ```ts
1248
+ * // Explicit mode — driven by a runtime condition (most common)
1249
+ * btn.swAttr('disabled', !isReady)
1250
+ * panel.swAttr('hidden', !isExpanded)
1251
+ * input.swAttr('readonly', isReadonly)
1252
+ *
1253
+ * // Not for ARIA — these need explicit string values
1254
+ * dialog.setAriaAttr('hidden', 'true')
1255
+ * button.setAriaAttr('disabled', 'false')
1256
+ *
1257
+ * // Auto mode — flip current state (no condition required)
1258
+ * btn.on('click', () => btn.swAttr('disabled'))
1259
+ *
1260
+ * // ⚠️ Watch out: passing undefined is NOT an explicit remove
1261
+ * swAttr('disabled', undefined) // → auto mode, NOT rmAttr!
1262
+ * ```
1263
+ *
1264
+ * @param name - The attribute name.
1265
+ * @param force - If omitted or `undefined`: auto-toggle. Truthy: add. Falsy (not `undefined`): remove.
1266
+ * @returns This instance for chaining.
1267
+ * @throws {TypeError} If `name` is not a string.
1268
+ * @see {@link setAttr} for setting a non-ARIA attribute with a specific string value.
1269
+ * @see {@link setAriaAttr} for ARIA attributes that require explicit values.
1270
+ * @see {@link rmAriaAttr} for unconditionally removing an ARIA attribute.
1271
+ * @see {@link rmAttr} for unconditionally removing an attribute.
1272
+ * @see {@link swClass} for the class-list equivalent.
1273
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/toggleAttribute | Element.toggleAttribute}
1274
+ */
1275
+ swAttr(name, force) {
1276
+ if (!isStr(name)) {
1277
+ throw typeErr("name", "a string", name);
1278
+ }
1279
+ this.ref.toggleAttribute(name, force);
1280
+ return this;
1281
+ }
937
1282
  /**
938
1283
  * Gets the value of an ARIA attribute.
939
1284
  *
@@ -942,7 +1287,7 @@ var JJE = class _JJE extends JJNx {
942
1287
  *
943
1288
  * @example
944
1289
  * ```ts
945
- * el.getAria('label') // gets 'aria-label'
1290
+ * el.getAriaAttr('label') // gets 'aria-label'
946
1291
  * ```
947
1292
  *
948
1293
  * @param name - The ARIA attribute suffix (e.g., 'label' for 'aria-label').
@@ -950,7 +1295,7 @@ var JJE = class _JJE extends JJNx {
950
1295
  * @throws {TypeError} If `name` is not a string.
951
1296
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes | ARIA Attributes}
952
1297
  */
953
- getAria(name) {
1298
+ getAriaAttr(name) {
954
1299
  if (!isStr(name)) {
955
1300
  throw typeErr("name", "a string", name);
956
1301
  }
@@ -962,22 +1307,78 @@ var JJE = class _JJE extends JJNx {
962
1307
  * @param name - The ARIA attribute suffix.
963
1308
  * @returns `true` if the attribute exists.
964
1309
  * @throws {TypeError} If `name` is not a string.
1310
+ * @see {@link getAriaAttr} for reading ARIA values.
1311
+ * @see {@link setAriaAttr} for setting ARIA values.
1312
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes | ARIA Attributes}
965
1313
  */
966
- hasAria(name) {
1314
+ hasAriaAttr(name) {
967
1315
  if (!isStr(name)) {
968
1316
  throw typeErr("name", "a string", name);
969
1317
  }
970
1318
  return this.ref.hasAttribute(`aria-${name}`);
971
1319
  }
972
- setAria(nameOrObj, value) {
973
- if (isStr(nameOrObj)) {
974
- this.ref.setAttribute(`aria-${nameOrObj}`, value);
975
- } else if (isObj(nameOrObj)) {
976
- for (const [k, v] of Object.entries(nameOrObj)) {
977
- this.ref.setAttribute(`aria-${k}`, v);
1320
+ /**
1321
+ * Sets a single ARIA attribute on the Element.
1322
+ *
1323
+ * @example
1324
+ * ```ts
1325
+ * el.setAriaAttr('hidden', 'true')
1326
+ * el.setAriaAttr('level', 2)
1327
+ * ```
1328
+ *
1329
+ * @param name - The ARIA attribute suffix.
1330
+ * @param value - The value to assign.
1331
+ * @returns This instance for chaining.
1332
+ * @throws {TypeError} If `name` is not a string.
1333
+ * @see {@link setAriaAttrs} for setting multiple ARIA attributes at once.
1334
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes | ARIA Attributes}
1335
+ */
1336
+ setAriaAttr(name, value) {
1337
+ if (!isStr(name)) {
1338
+ throw typeErr("name", "a string", name);
1339
+ }
1340
+ this.ref.setAttribute(`aria-${name}`, value);
1341
+ return this;
1342
+ }
1343
+ /**
1344
+ * Sets multiple ARIA attributes from an object, or no-ops for nullish input.
1345
+ *
1346
+ * @remarks
1347
+ * This helper is useful for optional ARIA attribute bags in builder APIs.
1348
+ * - `null` or `undefined`: does nothing and returns `this`
1349
+ * - plain object: sets each ARIA attribute on the element
1350
+ * - anything else: throws `TypeError`
1351
+ *
1352
+ * @example
1353
+ * ```ts
1354
+ * el.setAriaAttrs({ label: 'Close', hidden: 'false' })
1355
+ * el.setAriaAttrs(null) // no-op
1356
+ * ```
1357
+ *
1358
+ * @param attributes - ARIA attributes object or nullish to skip.
1359
+ * @returns This instance for chaining.
1360
+ * @throws {TypeError} If `attributes` is not nullish and not a plain object.
1361
+ * @see {@link setAriaAttr} for setting a single ARIA attribute.
1362
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes | ARIA Attributes}
1363
+ */
1364
+ setAriaAttrs(attributes) {
1365
+ if (attributes == null) {
1366
+ return this;
1367
+ }
1368
+ if (!isObj(attributes)) {
1369
+ throw typeErr(
1370
+ "attributes",
1371
+ "a plain object",
1372
+ attributes,
1373
+ 'Pass null/undefined or an object like { hidden: "true" }.'
1374
+ );
1375
+ }
1376
+ try {
1377
+ for (const [name, value] of Object.entries(attributes)) {
1378
+ this.setAriaAttr(name, value);
978
1379
  }
979
- } else {
980
- throw typeErr("nameOrObj", "a string or object", nameOrObj);
1380
+ } catch (cause) {
1381
+ throw new Error(`Failed to set some ARIA attributes from object: ${JSON.stringify(attributes)}.`, { cause });
981
1382
  }
982
1383
  return this;
983
1384
  }
@@ -986,20 +1387,27 @@ var JJE = class _JJE extends JJNx {
986
1387
  *
987
1388
  * @example
988
1389
  * ```ts
989
- * el.rmAria('hidden') // Remove single
990
- * el.rmAria('label', 'hidden') // Remove multiple
1390
+ * el.rmAriaAttr('hidden') // Remove single
1391
+ * el.rmAriaAttr('label', 'hidden') // Remove multiple
991
1392
  * ```
992
1393
  *
993
1394
  * @param names - The ARIA attribute suffix(es) to remove.
994
1395
  * @returns This instance for chaining.
995
1396
  * @throws {TypeError} If any name is not a string.
1397
+ * @see {@link setAriaAttr} for setting a single ARIA attribute.
1398
+ * @see {@link setAriaAttrs} for setting multiple ARIA attributes.
1399
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute | Element.removeAttribute}
996
1400
  */
997
- rmAria(...names) {
998
- for (const name of names) {
999
- if (!isStr(name)) {
1000
- throw typeErr("name", "a string", name);
1401
+ rmAriaAttr(...names) {
1402
+ try {
1403
+ for (const name of names) {
1404
+ if (!isStr(name)) {
1405
+ throw typeErr("name", "a string", name);
1406
+ }
1407
+ this.ref.removeAttribute(`aria-${name}`);
1001
1408
  }
1002
- this.ref.removeAttribute(`aria-${name}`);
1409
+ } catch (cause) {
1410
+ throw new Error(`Failed to remove some ARIA attributes: ${JSON.stringify(names)}.`, { cause });
1003
1411
  }
1004
1412
  return this;
1005
1413
  }
@@ -1012,16 +1420,76 @@ var JJE = class _JJE extends JJNx {
1012
1420
  getClass() {
1013
1421
  return this.getAttr("class");
1014
1422
  }
1015
- setClass(classNameOrMap) {
1016
- if (typeof classNameOrMap === "string") {
1017
- return this.setAttr("class", classNameOrMap);
1423
+ /**
1424
+ * Sets the class attribute.
1425
+ *
1426
+ * @remarks
1427
+ * To remove all classes, pass an empty string: `setClass('')`
1428
+ *
1429
+ * @example
1430
+ * ```ts
1431
+ * el.setClass('btn btn-primary')
1432
+ * el.setClass('')
1433
+ * ```
1434
+ *
1435
+ * @param className - The full class attribute value.
1436
+ * @returns This instance for chaining.
1437
+ * @throws {TypeError} If `className` is not a string.
1438
+ * @see {@link setClasses} for conditional class maps.
1439
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/className | Element.className}
1440
+ */
1441
+ setClass(className) {
1442
+ if (!isStr(className)) {
1443
+ throw typeErr("className", "a string", className);
1018
1444
  }
1019
- for (const [className, condition] of Object.entries(classNameOrMap)) {
1020
- if (condition) {
1021
- this.ref.classList.add(className);
1022
- } else {
1023
- this.ref.classList.remove(className);
1445
+ return this.setAttr("class", className);
1446
+ }
1447
+ /**
1448
+ * Conditionally adds or removes classes from an object map, or no-ops for nullish input.
1449
+ *
1450
+ * @remarks
1451
+ * - `null` or `undefined`: does nothing and returns `this`
1452
+ * - plain object: truthy values add a class, falsy values remove it
1453
+ * - anything else: throws `TypeError`
1454
+ *
1455
+ * @example
1456
+ * ```ts
1457
+ * el.setClasses({
1458
+ * active: true,
1459
+ * disabled: false,
1460
+ * highlight: isHighlighted,
1461
+ * })
1462
+ * el.setClasses(null) // no-op
1463
+ * ```
1464
+ *
1465
+ * @param classMap - Conditional class map or nullish to skip.
1466
+ * @returns This instance for chaining.
1467
+ * @throws {TypeError} If `classMap` is not nullish and not a plain object.
1468
+ * @see {@link setClass} for replacing the full class attribute.
1469
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/classList | Element.classList}
1470
+ */
1471
+ setClasses(classMap) {
1472
+ if (classMap == null) {
1473
+ return this;
1474
+ }
1475
+ if (!isObj(classMap)) {
1476
+ throw typeErr(
1477
+ "classMap",
1478
+ "a plain object",
1479
+ classMap,
1480
+ "Pass null/undefined or an object like { active: true }."
1481
+ );
1482
+ }
1483
+ try {
1484
+ for (const [className, condition] of Object.entries(classMap)) {
1485
+ if (condition) {
1486
+ this.addClass(className);
1487
+ } else {
1488
+ this.rmClass(className);
1489
+ }
1024
1490
  }
1491
+ } catch (cause) {
1492
+ throw new Error(`Failed to set some classes from object: ${JSON.stringify(classMap)}.`, { cause });
1025
1493
  }
1026
1494
  return this;
1027
1495
  }
@@ -1048,6 +1516,28 @@ var JJE = class _JJE extends JJNx {
1048
1516
  this.ref.classList.add(...classNames);
1049
1517
  return this;
1050
1518
  }
1519
+ /**
1520
+ * Adds multiple classes from an array of class names.
1521
+ *
1522
+ * @example
1523
+ * ```ts
1524
+ * el.addClasses(['btn', 'btn-primary'])
1525
+ * ```
1526
+ *
1527
+ * @param classNames - The classes to add.
1528
+ * @returns This instance for chaining.
1529
+ * @throws {TypeError} If `classNames` is not an array.
1530
+ * @throws {TypeError} If any class name in `classNames` is not a string.
1531
+ * @see {@link addClass} for adding one or more classes via rest arguments.
1532
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/classList | Element.classList}
1533
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/add | DOMTokenList.add}
1534
+ */
1535
+ addClasses(classNames) {
1536
+ if (!Array.isArray(classNames)) {
1537
+ throw typeErr("classNames", "an array of strings", classNames);
1538
+ }
1539
+ return this.addClass(...classNames);
1540
+ }
1051
1541
  /**
1052
1542
  * Removes one or more classes from the Element.
1053
1543
  *
@@ -1072,6 +1562,28 @@ var JJE = class _JJE extends JJNx {
1072
1562
  this.ref.classList.remove(...classNames);
1073
1563
  return this;
1074
1564
  }
1565
+ /**
1566
+ * Removes multiple classes from an array of class names.
1567
+ *
1568
+ * @example
1569
+ * ```ts
1570
+ * el.rmClasses(['btn', 'btn-primary'])
1571
+ * ```
1572
+ *
1573
+ * @param classNames - The classes to remove.
1574
+ * @returns This instance for chaining.
1575
+ * @throws {TypeError} If `classNames` is not an array.
1576
+ * @throws {TypeError} If any class name in `classNames` is not a string.
1577
+ * @see {@link rmClass} for removing one or more classes via rest arguments.
1578
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/classList | Element.classList}
1579
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/remove | DOMTokenList.remove}
1580
+ */
1581
+ rmClasses(classNames) {
1582
+ if (!Array.isArray(classNames)) {
1583
+ throw typeErr("classNames", "an array of strings", classNames);
1584
+ }
1585
+ return this.rmClass(...classNames);
1586
+ }
1075
1587
  /**
1076
1588
  * Checks if the Element has a specific class.
1077
1589
  *
@@ -1088,39 +1600,56 @@ var JJE = class _JJE extends JJNx {
1088
1600
  return this.ref.classList.contains(className);
1089
1601
  }
1090
1602
  /**
1091
- * Toggles a class on the Element.
1603
+ * Conditionally adds or removes a single class, or auto-toggles it when called
1604
+ * without a `force` value.
1605
+ *
1606
+ * @remarks
1607
+ * This method has two modes, mirroring the behavior of {@link swAttr}:
1608
+ *
1609
+ * **Explicit mode** — pass any `force` value other than `undefined`:
1610
+ * - Truthy `force` → adds the class.
1611
+ * - Falsy `force` (e.g. `false`, `null`, `0`, `""`) → removes the class.
1612
+ *
1613
+ * **Auto mode** — omit `force` entirely (i.e. call with one argument):
1614
+ * - Delegates to the native `DOMTokenList.toggle()`: removes the class if present,
1615
+ * adds it if absent. Useful for click handlers and event callbacks where you just
1616
+ * want to flip the current state.
1617
+ *
1618
+ * > **Warning:** Passing `undefined` explicitly — `swClass('foo', undefined)` —
1619
+ * > is treated as **auto mode**, not as an explicit remove. If you need to
1620
+ * > unconditionally remove a class, use {@link rmClass} instead.
1092
1621
  *
1093
- * @param className - The class to toggle.
1622
+ * To toggle multiple classes from a condition map in a single call, use {@link setClasses}.
1623
+ *
1624
+ * @example
1625
+ * ```ts
1626
+ * // Explicit mode — driven by a runtime condition (most common)
1627
+ * el.swClass('is-expanded', isExpanded)
1628
+ * el.swClass('is-loading', isPending)
1629
+ *
1630
+ * // Auto mode — flip current state (no condition required)
1631
+ * btn.on('click', () => btn.swClass('is-active'))
1632
+ *
1633
+ * // ⚠️ Watch out: passing undefined is NOT an explicit remove
1634
+ * swClass('foo', undefined) // → auto mode, NOT rmClass!
1635
+ * ```
1636
+ *
1637
+ * @param className - The class to add or remove.
1638
+ * @param force - If omitted or `undefined`: auto-toggle. Truthy: add. Falsy (not `undefined`): remove.
1094
1639
  * @returns This instance for chaining.
1095
1640
  * @throws {TypeError} If `className` is not a string.
1641
+ * @see {@link setClasses} for toggling multiple classes at once via a condition map.
1642
+ * @see {@link addClass} for unconditionally adding a class.
1643
+ * @see {@link rmClass} for unconditionally removing a class.
1644
+ * @see {@link swAttr} for the attribute equivalent.
1096
1645
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/classList | Element.classList}
1097
1646
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle | DOMTokenList.toggle}
1098
1647
  */
1099
- toggleClass(className) {
1648
+ swClass(className, force) {
1100
1649
  if (!isStr(className)) {
1101
1650
  throw typeErr("className", "a string", className);
1102
1651
  }
1103
- this.ref.classList.toggle(className);
1104
- return this;
1105
- }
1106
- /**
1107
- * Replaces a class with another one
1108
- *
1109
- * @remarks
1110
- * If the `oldClassName` doesn't exist, the `newClassName` isn't added
1111
- *
1112
- * @param oldClassName - The class name to remove
1113
- * @param newClassName - The class name to add
1114
- * @throws {TypeError} If either className is not a string.
1115
- */
1116
- replaceClass(oldClassName, newClassName) {
1117
- if (!isStr(oldClassName)) {
1118
- throw typeErr("oldClassName", "a string", oldClassName);
1119
- }
1120
- if (!isStr(newClassName)) {
1121
- throw typeErr("newClassName", "a string", newClassName);
1122
- }
1123
- this.ref.classList.replace(oldClassName, newClassName);
1652
+ this.ref.classList.toggle(className, force);
1124
1653
  return this;
1125
1654
  }
1126
1655
  /**
@@ -1148,7 +1677,7 @@ var JJE = class _JJE extends JJNx {
1148
1677
  throw typeErr("selector", "a string", selector);
1149
1678
  }
1150
1679
  const match = this.ref.closest(selector);
1151
- return match ? JJN.wrap(match) : null;
1680
+ return match ? _JJE.wrap(match) : null;
1152
1681
  }
1153
1682
  /**
1154
1683
 
@@ -1165,6 +1694,8 @@ var JJE = class _JJE extends JJNx {
1165
1694
  * Shows the Element by removing the `hidden` and `aria-hidden` attributes.
1166
1695
  *
1167
1696
  * @returns This instance for chaining.
1697
+ * @see {@link hide} for the inverse operation.
1698
+ * @see {@link rmAttr} for generic attribute removal.
1168
1699
  */
1169
1700
  show() {
1170
1701
  return this.rmAttr("hidden", "aria-hidden");
@@ -1183,6 +1714,8 @@ var JJE = class _JJE extends JJNx {
1183
1714
  * Enables the Element by removing the `disabled` and `aria-disabled` attributes.
1184
1715
  *
1185
1716
  * @returns This instance for chaining.
1717
+ * @see {@link disable} for the inverse operation.
1718
+ * @see {@link rmAttr} for generic attribute removal.
1186
1719
  */
1187
1720
  enable() {
1188
1721
  return this.rmAttr("disabled", "aria-disabled");
@@ -1221,74 +1754,220 @@ var JJE = class _JJE extends JJNx {
1221
1754
  return this;
1222
1755
  }
1223
1756
  /**
1224
- * Attaches a Shadow DOM to the Element and optionally sets its content and styles.
1757
+ * Attaches a Shadow DOM to the Element if it's not already attached.
1225
1758
  *
1226
1759
  * @remarks
1227
- * We prevent FOUC by assigning the template and CSS in one go.
1228
- * **Note:** You can't attach a shadow root to every type of element. There are some that can't have a
1760
+ * You can't attach a shadow root to every type of element. There are some that can't have a
1229
1761
  * shadow DOM for security reasons (for example `<a>`).
1230
1762
  *
1231
1763
  * @param mode - The encapsulation mode ('open' or 'closed'). Defaults to 'open'.
1232
- * @param config - Optional configuration object containing `template` (HTML string) and `styles` (array of CSSStyleSheet).
1233
1764
  * @returns This instance for chaining.
1765
+ * @throws {DOMException} If the element cannot have a shadow root. See {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow | Element.attachShadow} for more details.
1766
+ * @throws {Error} If the shadow DOM is already attached but with another mode.
1234
1767
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow | Element.attachShadow}
1235
1768
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/adoptedStyleSheets | ShadowRoot.adoptedStyleSheets}
1236
1769
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets | Document.adoptedStyleSheets}
1237
1770
  */
1238
- initShadow(mode = "open", config) {
1239
- const shadowRoot = this.ref.shadowRoot ?? this.ref.attachShadow({ mode });
1240
- if (isObj(config)) {
1241
- const { template, styles } = config;
1242
- if (template) {
1243
- if (isStr(template)) {
1244
- shadowRoot.innerHTML = template;
1245
- } else {
1246
- shadowRoot.appendChild(template);
1247
- }
1248
- }
1249
- if (isArr(styles) && styles.length) {
1250
- shadowRoot.adoptedStyleSheets.push(...styles);
1251
- }
1771
+ setShadow(mode = "open") {
1772
+ const { shadowRoot } = this.ref;
1773
+ if (!shadowRoot) {
1774
+ this.ref.attachShadow({ mode });
1775
+ } else if (shadowRoot.mode !== mode) {
1776
+ throw new Error(
1777
+ `Element already has a shadow root with mode "${shadowRoot.mode}". Cannot attach another with mode "${mode}".`
1778
+ );
1252
1779
  }
1253
1780
  return this;
1254
1781
  }
1255
1782
  /**
1256
- * Gets a wrapper around the Element's Shadow Root, if it exists.
1783
+ * Initializes an already-attached Shadow DOM with template content and optional styles.
1257
1784
  *
1258
- * @returns A JJSR instance wrapping the shadow root, or null if no shadow root exists.
1259
- */
1260
- get shadow() {
1261
- return this.ref.shadowRoot ? new JJSR(this.ref.shadowRoot) : null;
1785
+ * @remarks
1786
+ * This method expects the shadow root to already exist.
1787
+ * You would typically call `setShadow()` in the custom component constructor and then
1788
+ * perform initialization in `connectedCallback()`.
1789
+ *
1790
+ * @example
1791
+ * ```ts
1792
+ * const host = JJHE.from(this).setShadow('open')
1793
+ * host.initShadow(await templatePromise, await stylePromise)
1794
+ * ```
1795
+ *
1796
+ * @param template - The template content to clone into the shadow root.
1797
+ * @param styles - Optional styles to add to the shadow root.
1798
+ * @returns This instance for chaining.
1799
+ * @throws {ReferenceError} If the element does not have a shadow root yet.
1800
+ * @throws {Error} If it fails to initialize the shadow DOM with the provided template or styles.
1801
+ * @see {@link setShadow} for attaching the shadow root first.
1802
+ * @see {@link JJSR.init} for the underlying shadow-root initialization helper.
1803
+ */
1804
+ initShadow(template, ...styles) {
1805
+ try {
1806
+ this.getShadow(true).init(template, ...styles);
1807
+ } catch (cause) {
1808
+ throw new Error(`Failed to initialize shadow DOM`, { cause });
1809
+ }
1810
+ return this;
1811
+ }
1812
+ getShadow(required = false) {
1813
+ const shadowRoot = this.ref.shadowRoot;
1814
+ if (shadowRoot) {
1815
+ return new JJSR(shadowRoot);
1816
+ }
1817
+ if (required) {
1818
+ throw new ReferenceError("No shadow root found on this element. Did you forget to call setShadow() first?");
1819
+ }
1820
+ return null;
1262
1821
  }
1263
1822
  };
1264
1823
 
1265
1824
  // src/wrappers/JJEx.ts
1266
1825
  var JJEx = class extends JJE {
1267
1826
  /**
1268
- * Gets a data attribute from the HTMLElement.
1827
+ * Gets the value of a single inline style property.
1828
+ *
1829
+ * @example
1830
+ * ```ts
1831
+ * const display = el.getStyle('display')
1832
+ * ```
1833
+ *
1834
+ * @param name - The CSS property name (kebab-case recommended).
1835
+ * @returns The property value, or an empty string when it is not set.
1836
+ * @throws {TypeError} If `name` is not a string.
1837
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style | HTMLElement.style}
1838
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/getPropertyValue | CSSStyleDeclaration.getPropertyValue}
1839
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/style | SVG style attribute}
1840
+ */
1841
+ getStyle(name) {
1842
+ if (!isStr(name)) {
1843
+ throw typeErr("name", "a string", name);
1844
+ }
1845
+ return this.ref.style.getPropertyValue(name);
1846
+ }
1847
+ /**
1848
+ * Sets a single inline style property.
1849
+ *
1850
+ * @example
1851
+ * ```ts
1852
+ * el.setStyle('display', 'grid')
1853
+ * el.setStyle('opacity', 0.8)
1854
+ * ```
1855
+ *
1856
+ * @param name - The CSS property name (kebab-case recommended).
1857
+ * @param value - The CSS property value.
1858
+ * @returns This instance for chaining.
1859
+ * @throws {TypeError} If `name` is not a string.
1860
+ * @see {@link setStyles} for setting/removing specific style properties.
1861
+ * @see {@link rmStyle} for removing style properties.
1862
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style | HTMLElement.style}
1863
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/setProperty | CSSStyleDeclaration.setProperty}
1864
+ */
1865
+ setStyle(name, value) {
1866
+ if (!isStr(name)) {
1867
+ throw typeErr("name", "a string", name);
1868
+ }
1869
+ this.ref.style.setProperty(name, value);
1870
+ return this;
1871
+ }
1872
+ /**
1873
+ * Removes one or more inline style properties.
1269
1874
  *
1270
1875
  * @example
1271
1876
  * ```ts
1272
- * const value = el.getData('my-key')
1877
+ * el.rmStyle('display')
1878
+ * el.rmStyle('display', 'gap')
1879
+ * ```
1880
+ *
1881
+ * @param names - The CSS property names to remove.
1882
+ * @returns This instance for chaining.
1883
+ * @throws {TypeError} If any property name is not a string.
1884
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration/removeProperty | CSSStyleDeclaration.removeProperty}
1885
+ */
1886
+ rmStyle(...names) {
1887
+ for (const name of names) {
1888
+ if (!isStr(name)) {
1889
+ throw typeErr("name", "a string", name);
1890
+ }
1891
+ this.ref.style.removeProperty(name);
1892
+ }
1893
+ return this;
1894
+ }
1895
+ /**
1896
+ * Sets or removes multiple inline style properties from an object map, or no-ops for nullish input.
1897
+ *
1898
+ * @remarks
1899
+ * - `null` or `undefined`: does nothing and returns `this`
1900
+ * - plain object: non-nullish values set properties, `null`/`undefined`/`false` remove properties
1901
+ * - anything else: throws `TypeError`
1902
+ *
1903
+ * @example
1904
+ * ```ts
1905
+ * el.setStyles({
1906
+ * display: 'grid',
1907
+ * gap: '1rem',
1908
+ * opacity: 0,
1909
+ * color: null,
1910
+ * })
1911
+ * el.setStyles(null) // no-op
1912
+ * ```
1913
+ *
1914
+ * @param styleMap - Style property map or nullish to skip.
1915
+ * @returns This instance for chaining.
1916
+ * @throws {TypeError} If `styleMap` is not nullish and not a plain object.
1917
+ * @see {@link setStyle} for setting a single style property.
1918
+ */
1919
+ setStyles(styleMap) {
1920
+ if (styleMap == null) {
1921
+ return this;
1922
+ }
1923
+ if (!isObj(styleMap)) {
1924
+ throw typeErr(
1925
+ "styleMap",
1926
+ "a plain object",
1927
+ styleMap,
1928
+ 'Pass null/undefined or an object like { display: "grid" }.'
1929
+ );
1930
+ }
1931
+ try {
1932
+ for (const [name, value] of Object.entries(styleMap)) {
1933
+ if (value == null || value === false) {
1934
+ this.rmStyle(name);
1935
+ } else {
1936
+ this.setStyle(name, value);
1937
+ }
1938
+ }
1939
+ } catch (cause) {
1940
+ throw new Error(`Failed to set some styles from object: ${JSON.stringify(styleMap)}.`, { cause });
1941
+ }
1942
+ return this;
1943
+ }
1944
+ /**
1945
+ * Gets a data attribute from the element.
1946
+ *
1947
+ * @example
1948
+ * ```ts
1949
+ * const value = el.getDataAttr('myKey')
1273
1950
  * ```
1274
1951
  *
1275
1952
  * @param name - The data attribute name (in camelCase).
1276
1953
  * @returns The value of the attribute, or undefined if not set.
1277
1954
  * @throws {TypeError} If `name` is not a string.
1278
1955
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset | HTMLElement.dataset}
1956
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement/dataset | SVGElement.dataset}
1957
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MathMLElement/dataset | MathMLElement.dataset}
1279
1958
  */
1280
- getData(name) {
1959
+ getDataAttr(name) {
1281
1960
  if (!isStr(name)) {
1282
1961
  throw typeErr("name", "a string", name);
1283
1962
  }
1284
1963
  return this.ref.dataset[name];
1285
1964
  }
1286
1965
  /**
1287
- * Checks if a data attribute exists on the HTMLElement.
1966
+ * Checks if a data attribute exists on the element.
1288
1967
  *
1289
1968
  * @example
1290
1969
  * ```ts
1291
- * if (el.hasData('my-key')) {
1970
+ * if (el.hasDataAttr('myKey')) {
1292
1971
  * // ...
1293
1972
  * }
1294
1973
  * ```
@@ -1297,40 +1976,101 @@ var JJEx = class extends JJE {
1297
1976
  * @returns True if the attribute exists, false otherwise.
1298
1977
  * @throws {TypeError} If `name` is not a string.
1299
1978
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset | HTMLElement.dataset}
1979
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement/dataset | SVGElement.dataset}
1980
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MathMLElement/dataset | MathMLElement.dataset}
1300
1981
  */
1301
- hasData(name) {
1982
+ hasDataAttr(name) {
1302
1983
  if (!isStr(name)) {
1303
1984
  throw typeErr("name", "a string", name);
1304
1985
  }
1305
1986
  return hasProp(this.ref.dataset, name);
1306
1987
  }
1307
- setData(nameOrObj, value) {
1308
- if (typeof nameOrObj === "string") {
1309
- this.ref.dataset[nameOrObj] = value;
1310
- } else if (isObj(nameOrObj)) {
1311
- for (const [k, v] of Object.entries(nameOrObj)) {
1312
- this.ref.dataset[k] = v;
1988
+ /**
1989
+ * Sets a single data attribute on the element.
1990
+ *
1991
+ * @example
1992
+ * ```ts
1993
+ * el.setDataAttr('myKey', 'myValue')
1994
+ * el.setDataAttr('count', 42 as unknown as string)
1995
+ * ```
1996
+ *
1997
+ * @param name - The data attribute name (in camelCase).
1998
+ * @param value - The value to assign.
1999
+ * @returns This instance for chaining.
2000
+ * @throws {TypeError} If `name` is not a string.
2001
+ * @see {@link setDataAttrs} for setting multiple data attributes at once.
2002
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset | HTMLElement.dataset}
2003
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement/dataset | SVGElement.dataset}
2004
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MathMLElement/dataset | MathMLElement.dataset}
2005
+ */
2006
+ setDataAttr(name, value) {
2007
+ if (!isStr(name)) {
2008
+ throw typeErr("name", "a string", name);
2009
+ }
2010
+ this.ref.dataset[name] = value;
2011
+ return this;
2012
+ }
2013
+ /**
2014
+ * Sets multiple data attributes from an object, or no-ops for nullish input.
2015
+ *
2016
+ * @remarks
2017
+ * This helper is useful for optional dataset bags in builder APIs.
2018
+ * - `null` or `undefined`: does nothing and returns `this`
2019
+ * - plain object: sets each data attribute on the element
2020
+ * - anything else: throws `TypeError`
2021
+ *
2022
+ * @example
2023
+ * ```ts
2024
+ * el.setDataAttrs({ myKey: 'myValue', otherKey: 'otherValue' })
2025
+ * el.setDataAttrs(null) // no-op
2026
+ * ```
2027
+ *
2028
+ * @param attributes - Data attributes object or nullish to skip.
2029
+ * @returns This instance for chaining.
2030
+ * @throws {TypeError} If `attributes` is not nullish and not a plain object.
2031
+ * @see {@link setDataAttr} for setting a single data attribute.
2032
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset | HTMLElement.dataset}
2033
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement/dataset | SVGElement.dataset}
2034
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MathMLElement/dataset | MathMLElement.dataset}
2035
+ */
2036
+ setDataAttrs(attributes) {
2037
+ if (attributes == null) {
2038
+ return this;
2039
+ }
2040
+ if (!isObj(attributes)) {
2041
+ throw typeErr(
2042
+ "attributes",
2043
+ "a plain object",
2044
+ attributes,
2045
+ 'Pass null/undefined or an object like { userId: "42" }.'
2046
+ );
2047
+ }
2048
+ try {
2049
+ for (const [name, value] of Object.entries(attributes)) {
2050
+ this.setDataAttr(name, value);
1313
2051
  }
1314
- } else {
1315
- throw typeErr("nameOrObj", "a string or object", nameOrObj);
2052
+ } catch (cause) {
2053
+ throw new Error(`Failed to set some data attributes from object: ${JSON.stringify(attributes)}.`, { cause });
1316
2054
  }
1317
2055
  return this;
1318
2056
  }
1319
2057
  /**
1320
- * Removes one or more data attributes from the HTMLElement.
2058
+ * Removes one or more data attributes from the element.
1321
2059
  *
1322
2060
  * @example
1323
2061
  * ```ts
1324
- * el.rmData('myKey') // Remove single
1325
- * el.rmData('myKey', 'otherKey') // Remove multiple
2062
+ * el.rmDataAttr('myKey') // Remove single
2063
+ * el.rmDataAttr('myKey', 'otherKey') // Remove multiple
1326
2064
  * ```
1327
2065
  *
1328
2066
  * @param names - The data attribute name(s) (in camelCase).
1329
2067
  * @returns This instance for chaining.
1330
2068
  * @throws {TypeError} If any name is not a string.
1331
2069
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset | HTMLElement.dataset}
2070
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement/dataset | SVGElement.dataset}
2071
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MathMLElement/dataset | MathMLElement.dataset}
1332
2072
  */
1333
- rmData(...names) {
2073
+ rmDataAttr(...names) {
1334
2074
  for (const name of names) {
1335
2075
  if (!isStr(name)) {
1336
2076
  throw typeErr("name", "a string", name);
@@ -1339,13 +2079,42 @@ var JJEx = class extends JJE {
1339
2079
  }
1340
2080
  return this;
1341
2081
  }
2082
+ /**
2083
+ * Removes multiple data attributes from an array of names.
2084
+ *
2085
+ * @example
2086
+ * ```ts
2087
+ * el.rmDataAttrs(['myKey', 'otherKey'])
2088
+ * ```
2089
+ *
2090
+ * @param names - The data attribute names to remove (in camelCase).
2091
+ * @returns This instance for chaining.
2092
+ * @throws {TypeError} If `names` is not an array.
2093
+ * @throws {TypeError} If any name in `names` is not a string.
2094
+ * @see {@link rmDataAttr} for removing one or more data attributes via rest arguments.
2095
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset | HTMLElement.dataset}
2096
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement/dataset | SVGElement.dataset}
2097
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MathMLElement/dataset | MathMLElement.dataset}
2098
+ */
2099
+ rmDataAttrs(names) {
2100
+ if (!Array.isArray(names)) {
2101
+ throw typeErr("names", "an array", names);
2102
+ }
2103
+ return this.rmDataAttr(...names);
2104
+ }
1342
2105
  };
1343
2106
 
1344
2107
  // src/wrappers/JJHE.ts
2108
+ var COMMON_SVG_TAGS = ["svg", "rect", "circle", "line", "path", "text"];
2109
+ var COMMON_MATHML_TAGS = ["math", "mi", "mn", "mo", "mtext"];
1345
2110
  var JJHE = class _JJHE extends JJEx {
1346
2111
  /**
1347
2112
  * Creates a JJHE instance from an HTMLElement reference.
1348
2113
  *
2114
+ * @remarks
2115
+ * Use {@link JJHE.create} to create new HTMLElements, or use this method to wrap existing ones.
2116
+ * For other element types, use {@link JJSE.from} for SVGElements or {@link JJME.from} for MathMLElements.
2117
+ *
1349
2118
  * @example
1350
2119
  * ```ts
1351
2120
  * const el = JJHE.from(document.getElementById('my-id')) // from an existing HTMLElement
@@ -1360,19 +2129,77 @@ var JJHE = class _JJHE extends JJEx {
1360
2129
  }
1361
2130
  static create(tagName, options) {
1362
2131
  if (!isStr(tagName)) {
1363
- throw typeErr("tagName", "a string like 'div' or 'button'", tagName);
2132
+ throw typeErr("tagName", "a string like 'div' or 'button'", tagName, "Pass a valid HTML tag name.");
2133
+ }
2134
+ if (COMMON_SVG_TAGS.includes(tagName)) {
2135
+ throw errMsg(
2136
+ `tagName`,
2137
+ `a HTML tag name (not an SVG tag name)`,
2138
+ tagName,
2139
+ `Use JJSE.create("${tagName}") for SVG elements.`
2140
+ );
2141
+ }
2142
+ if (COMMON_MATHML_TAGS.includes(tagName)) {
2143
+ throw errMsg(
2144
+ `tagName`,
2145
+ `a HTML tag name (not a MathML tag name)`,
2146
+ tagName,
2147
+ `Use JJME.create("${tagName}") for MathML elements.`
2148
+ );
1364
2149
  }
1365
2150
  return new _JJHE(document.createElement(tagName, options));
1366
2151
  }
2152
+ /**
2153
+ * Builds an HTML element tree with optional attributes and children.
2154
+ *
2155
+ * @remarks
2156
+ * A concise declarative way to build HTML DOM snippets. Chain further JJ methods on the return value.
2157
+ * Pass `null` or omit `attributes` when no attributes are needed. Pass children as additional arguments.
2158
+ * Unlike `create()`, the return type is always `JJHE` (not the specific subtype), which is fine for
2159
+ * snippet construction where precise inference is not needed.
2160
+ *
2161
+ * If you prefer a shorter alias compatible with hyperscript conventions, you can use:
2162
+ * ```ts
2163
+ * const h = JJHE.tree
2164
+ * ```
2165
+ *
2166
+ * @example
2167
+ * ```ts
2168
+ * // Simple element with text
2169
+ * JJHE.tree('p', { class: 'intro' }, 'Hello World')
2170
+ *
2171
+ * // Nested structure
2172
+ * JJHE.tree('nav', { class: 'main-nav' },
2173
+ * JJHE.tree('a', { href: '/' }, 'Home'),
2174
+ * JJHE.tree('a', { href: '/about' }, 'About'),
2175
+ * )
2176
+ *
2177
+ * // No attributes
2178
+ * JJHE.tree('section', null, JJHE.tree('h1', null, 'Title'), JJHE.tree('p', null, 'Body'))
2179
+ * ```
2180
+ *
2181
+ * @param tagName - The HTML tag name.
2182
+ * @param attributes - Attributes to set. Pass `null` or `undefined` to skip.
2183
+ * @param children - Children to append (strings, nodes, or JJ wrappers).
2184
+ * @returns A new JJHE instance.
2185
+ * @throws {TypeError} If `attributes` is not a plain object.
2186
+ * @see {@link JJHE.create} for a type-narrowed single-element factory.
2187
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement | document.createElement}
2188
+ */
2189
+ static tree(tagName, attributes, ...children) {
2190
+ return _JJHE.create(tagName).setAttrs(attributes).addChild(...children);
2191
+ }
1367
2192
  /**
1368
2193
  * Creates an instance of JJHE.
1369
2194
  *
1370
2195
  * @param ref - The HTMLElement to wrap.
1371
2196
  * @throws {TypeError} If `ref` is not an HTMLElement.
2197
+ * @see {@link JJHE.from} to wrap existing HTMLElements
2198
+ * @see {@link JJHE.create} to create new HTMLElements
1372
2199
  */
1373
2200
  constructor(ref) {
1374
- if (!isA(ref, HTMLElement)) {
1375
- throw typeErr("ref", "an HTMLElement", ref);
2201
+ if (!isInstance(ref, HTMLElement)) {
2202
+ throw typeErr("ref", "an HTMLElement", ref, "Use JJHE.from() or JJHE.create().");
1376
2203
  }
1377
2204
  super(ref);
1378
2205
  }
@@ -1465,6 +2292,10 @@ var JJT = class _JJT extends JJN {
1465
2292
  /**
1466
2293
  * Creates a JJT instance from a Text node.
1467
2294
  *
2295
+ * @remarks
2296
+ * Use {@link JJT.create} to create a Text node from a string.
2297
+ * For other Node types, use {@link JJN.from} or the specific wrapper type.
2298
+ *
1468
2299
  * @example
1469
2300
  * ```ts
1470
2301
  * const textNode = document.createTextNode('foo')
@@ -1474,12 +2305,21 @@ var JJT = class _JJT extends JJN {
1474
2305
  * @param text - The Text node.
1475
2306
  * @returns A new JJT instance.
1476
2307
  * @throws {TypeError} If `text` is not a Text node.
2308
+ * @see {@link JJT.create} for creating from string input.
1477
2309
  */
1478
2310
  static from(text) {
1479
2311
  return new _JJT(text);
1480
2312
  }
1481
- static fromStr(text) {
1482
- return new _JJT(document.createTextNode(text));
2313
+ /**
2314
+ * Creates a JJT instance from a string.
2315
+ *
2316
+ * @param text - The string to convert into a Text node.
2317
+ * @returns A new JJT instance wrapping a Text node.
2318
+ * @see {@link JJT.from} for wrapping an existing Text node.
2319
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode | document.createTextNode}
2320
+ */
2321
+ static create(text) {
2322
+ return new _JJT(document.createTextNode(toStr(text)));
1483
2323
  }
1484
2324
  /**
1485
2325
  * Creates an instance of JJT.
@@ -1491,11 +2331,16 @@ var JJT = class _JJT extends JJN {
1491
2331
  *
1492
2332
  * @param ref - The Text node or a string to create a Text node from.
1493
2333
  * @throws {TypeError} If `ref` is not a Text node or string.
2334
+ * @see {@link JJT.from} for wrapping an existing Text node.
2335
+ * @see {@link JJT.create} for creating from a plain string.
1494
2336
  */
1495
2337
  constructor(ref) {
1496
- if (!isA(ref, Text)) {
1497
- throw new TypeError(
1498
- `JJT expects a Text node. Got ${ref} (${typeof ref}). Create a Text node with JJT.fromStr() or document.createTextNode('text').`
2338
+ if (!isInstance(ref, Text)) {
2339
+ throw typeErr(
2340
+ "ref",
2341
+ "a Text node",
2342
+ ref,
2343
+ "Create a Text node with JJT.create() or document.createTextNode('text')."
1499
2344
  );
1500
2345
  }
1501
2346
  super(ref);
@@ -1569,6 +2414,10 @@ var JJD = class _JJD extends JJNx {
1569
2414
  /**
1570
2415
  * Creates a JJD instance from a Document reference.
1571
2416
  *
2417
+ * @remarks
2418
+ * Typically, you'll use this to wrap the global `document` object.
2419
+ * Use {@link JJHE.from} to wrap individual elements, and {@link JJHE} for the head/body elements.
2420
+ *
1572
2421
  * @example
1573
2422
  * ```ts
1574
2423
  * const doc = JJD.from(document)
@@ -1586,39 +2435,156 @@ var JJD = class _JJD extends JJNx {
1586
2435
  *
1587
2436
  * @param ref - The Document instance to wrap.
1588
2437
  * @throws {TypeError} If `ref` is not a Document.
2438
+ * @see {@link JJD.from} to wrap a Document
1589
2439
  */
1590
2440
  constructor(ref) {
1591
- if (!isA(ref, Document)) {
1592
- throw new TypeError(`JJD expects a Document instance. Got ${ref} (${typeof ref}). `);
2441
+ if (!isInstance(ref, Document)) {
2442
+ throw typeErr("ref", "a Document instance", ref, "Use JJD.from(document) to create an instance.");
1593
2443
  }
1594
2444
  super(ref);
1595
2445
  }
2446
+ };
2447
+
2448
+ // src/wrappers/JJME.ts
2449
+ var JJME = class _JJME extends JJEx {
1596
2450
  /**
1597
- * Gets the `<head>` element of the document wrapped in a `JJHE` instance.
2451
+ * Creates a JJME instance from a MathMLElement reference.
1598
2452
  *
1599
- * @returns The wrapped head element.
1600
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/head | Document.head}
2453
+ * @remarks
2454
+ * Use {@link JJME.create} to create new MathMLElements, or use this method to wrap existing ones.
2455
+ * For HTMLElements, use {@link JJHE.from}, or {@link JJSE.from} for SVGElements.
2456
+ *
2457
+ * @example
2458
+ * ```ts
2459
+ * const mrow = JJME.from(myMrow)
2460
+ * ```
2461
+ *
2462
+ * @param ref - The MathMLElement.
2463
+ * @returns A new JJME instance.
2464
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MathMLElement | MathMLElement}
1601
2465
  */
1602
- get head() {
1603
- return JJHE.from(this.ref.head);
2466
+ static from(ref) {
2467
+ return new _JJME(ref);
2468
+ }
2469
+ static create(tagName, options) {
2470
+ if (!isStr(tagName)) {
2471
+ throw typeErr(
2472
+ "tagName",
2473
+ 'a string like "math" or "mfrac"',
2474
+ tagName,
2475
+ 'Pass a valid MathML tag name like "math", "mrow", or "mfrac".'
2476
+ );
2477
+ }
2478
+ const element = document.createElementNS(MATHML_NS, tagName, options);
2479
+ return new _JJME(element);
1604
2480
  }
1605
2481
  /**
1606
- * Gets the `<body>` element of the document wrapped in a `JJHE` instance.
2482
+ * Builds a MathML element tree with optional attributes and children.
2483
+ *
2484
+ * @remarks
2485
+ * A concise declarative way to build MathML DOM snippets. All elements are created in the MathML namespace.
2486
+ * Chain further JJ methods on the return value. Pass `null` or omit `attributes` when no attributes are needed.
2487
+ *
2488
+ * If you prefer a shorter alias compatible with hyperscript conventions, you can use:
2489
+ * ```ts
2490
+ * const h = JJME.tree
2491
+ * ```
2492
+ *
2493
+ * @example
2494
+ * ```ts
2495
+ * // Fraction: x/y
2496
+ * JJME.tree('math', null,
2497
+ * JJME.tree('mfrac', null,
2498
+ * JJME.tree('mi', null, 'x'),
2499
+ * JJME.tree('mi', null, 'y'),
2500
+ * ),
2501
+ * )
2502
+ *
2503
+ * // Subscript: a_n
2504
+ * JJME.tree('math', null,
2505
+ * JJME.tree('msub', null,
2506
+ * JJME.tree('mi', null, 'a'),
2507
+ * JJME.tree('mi', null, 'n'),
2508
+ * ),
2509
+ * )
2510
+ * ```
1607
2511
  *
1608
- * @returns The wrapped body element.
1609
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/body | Document.body}
2512
+ * @param tagName - The MathML tag name.
2513
+ * @param attributes - Attributes to set. Pass `null` or `undefined` to skip.
2514
+ * @param children - Children to append (strings, nodes, or JJ wrappers).
2515
+ * @returns A new JJME instance.
2516
+ * @throws {TypeError} If `attributes` is not a plain object.
2517
+ * @see {@link JJME.create} for a type-narrowed single-element factory.
2518
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS | document.createElementNS}
2519
+ */
2520
+ static tree(tagName, attributes, ...children) {
2521
+ return _JJME.create(tagName).setAttrs(attributes).addChild(...children);
2522
+ }
2523
+ /**
2524
+ * Creates an instance of JJME.
2525
+ *
2526
+ * @param ref - The MathMLElement to wrap.
2527
+ * @throws {TypeError} If `ref` is not a MathMLElement.
2528
+ * @see {@link JJME.from} to wrap existing MathMLElements
2529
+ * @see {@link JJME.create} to create new MathMLElements
1610
2530
  */
1611
- get body() {
1612
- return JJHE.from(this.ref.body);
2531
+ constructor(ref) {
2532
+ if (!isInstance(ref, Element) || ref.namespaceURI !== MATHML_NS) {
2533
+ throw typeErr("ref", `a MathML element (${MATHML_NS})`, ref, "Use JJME.from() or JJME.create().");
2534
+ }
2535
+ super(ref);
2536
+ }
2537
+ /**
2538
+ * Gets the text content of the MathMLElement.
2539
+ *
2540
+ * @remarks
2541
+ * This method operates on `textContent`. The method name is kept short for convenience.
2542
+ *
2543
+ * @example
2544
+ * ```ts
2545
+ * const text = math.getText()
2546
+ * ```
2547
+ *
2548
+ * @returns The text content.
2549
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent | Node.textContent}
2550
+ */
2551
+ getText() {
2552
+ return this.ref.textContent ?? "";
2553
+ }
2554
+ /**
2555
+ * Sets the text content of the MathMLElement.
2556
+ *
2557
+ * @remarks
2558
+ * This method operates on `textContent`. The method name is kept short for convenience.
2559
+ * Pass an empty string, `null`, or `undefined` to clear the content.
2560
+ * Numbers and booleans are automatically converted to strings.
2561
+ *
2562
+ * @example
2563
+ * ```ts
2564
+ * mi.setText('x')
2565
+ * mi.setText(null) // Clear content
2566
+ * mi.setText(42) // Numbers are converted
2567
+ * ```
2568
+ *
2569
+ * @param text - The text to set, or null/undefined to clear.
2570
+ * @returns This instance for chaining.
2571
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent | Node.textContent}
2572
+ */
2573
+ setText(text) {
2574
+ this.ref.textContent = text;
2575
+ return this;
1613
2576
  }
1614
2577
  };
1615
2578
 
1616
2579
  // src/wrappers/JJSE.ts
1617
- var SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg";
1618
2580
  var JJSE = class _JJSE extends JJEx {
1619
2581
  /**
1620
2582
  * Creates a JJSE instance from an SVGElement reference.
1621
2583
  *
2584
+ * @remarks
2585
+ * Use {@link JJSE.create} to create new SVGElements, or use this method to wrap existing ones.
2586
+ * For HTMLElements, use {@link JJHE.from}, or {@link JJME.from} for MathMLElements.
2587
+ *
1622
2588
  * @example
1623
2589
  * ```ts
1624
2590
  * const svg = JJSE.from(myCircle)
@@ -1631,39 +2597,68 @@ var JJSE = class _JJSE extends JJEx {
1631
2597
  static from(ref) {
1632
2598
  return new _JJSE(ref);
1633
2599
  }
2600
+ static create(tagName, options) {
2601
+ if (!isStr(tagName)) {
2602
+ throw typeErr(
2603
+ "tagName",
2604
+ 'a string like "circle" or "path"',
2605
+ tagName,
2606
+ 'Pass a valid SVG tag name like "svg", "circle", or "path".'
2607
+ );
2608
+ }
2609
+ const element = document.createElementNS(SVG_NS, tagName, options);
2610
+ return new _JJSE(element);
2611
+ }
1634
2612
  /**
1635
- * Creates a JJSE instance from a tag name (in the SVG namespace).
2613
+ * Builds an SVG element tree with optional attributes and children.
1636
2614
  *
1637
2615
  * @remarks
1638
- * Automatically uses the correct SVG namespace URI: `http://www.w3.org/2000/svg`.
2616
+ * A concise declarative way to build SVG DOM snippets. All elements are created in the SVG namespace.
2617
+ * Chain further JJ methods on the return value. Pass `null` or omit `attributes` when no attributes are needed.
2618
+ *
2619
+ * If you prefer a shorter alias compatible with hyperscript conventions, you can use:
2620
+ * ```ts
2621
+ * const h = JJSE.tree
2622
+ * ```
1639
2623
  *
1640
2624
  * @example
1641
2625
  * ```ts
1642
- * const circle = JJSE.create('circle')
2626
+ * // Simple SVG icon
2627
+ * JJSE.tree('svg', { viewBox: '0 0 24 24', width: '24', height: '24' },
2628
+ * JJSE.tree('circle', { cx: '12', cy: '12', r: '10', fill: 'currentColor' }),
2629
+ * )
2630
+ *
2631
+ * // No attributes
2632
+ * JJSE.tree('g', null, JJSE.tree('rect', { x: '0', y: '0', width: '10', height: '10' }))
1643
2633
  * ```
1644
2634
  *
1645
- * @param tagName - The tag name.
1646
- * @param options - Element creation options.
2635
+ * @param tagName - The SVG tag name.
2636
+ * @param attributes - Attributes to set. Pass `null` or `undefined` to skip.
2637
+ * @param children - Children to append (strings, nodes, or JJ wrappers).
1647
2638
  * @returns A new JJSE instance.
1648
- * @throws {TypeError} If `tagName` is not a string.
2639
+ * @throws {TypeError} If `attributes` is not a plain object.
2640
+ * @see {@link JJSE.create} for a type-narrowed single-element factory.
1649
2641
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS | document.createElementNS}
1650
2642
  */
1651
- static create(tagName, options) {
1652
- if (!isStr(tagName)) {
1653
- throw typeErr("tagName", 'a string like "circle" or "path"', tagName);
1654
- }
1655
- const element = document.createElementNS(SVG_NAMESPACE_URI, tagName, options);
1656
- return new _JJSE(element);
2643
+ static tree(tagName, attributes, ...children) {
2644
+ return _JJSE.create(tagName).setAttrs(attributes).addChild(...children);
1657
2645
  }
1658
2646
  /**
1659
2647
  * Creates an instance of JJSE.
1660
2648
  *
1661
2649
  * @param ref - The SVGElement to wrap.
1662
2650
  * @throws {TypeError} If `ref` is not an SVGElement.
2651
+ * @see {@link JJSE.from} to wrap an existing SVG element.
2652
+ * @see {@link JJSE.create} to create a new SVG element.
1663
2653
  */
1664
2654
  constructor(ref) {
1665
- if (!isA(ref, SVGElement)) {
1666
- throw typeErr("ref", "an SVGElement", ref);
2655
+ if (!isInstance(ref, SVGElement)) {
2656
+ throw typeErr(
2657
+ "ref",
2658
+ "an SVGElement",
2659
+ ref,
2660
+ 'Wrap an existing SVG element with JJSE.from(el) or create one with JJSE.create("svg").'
2661
+ );
1667
2662
  }
1668
2663
  super(ref);
1669
2664
  }
@@ -1754,7 +2749,7 @@ var JJSE = class _JJSE extends JJEx {
1754
2749
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox | viewBox}
1755
2750
  */
1756
2751
  setViewBox(p1, p2, p3, p4) {
1757
- if (typeof p1 === "number" && p2 !== void 0 && p3 !== void 0 && p4 !== void 0) {
2752
+ if (isNum(p1) && isNum(p2) && isNum(p3) && isNum(p4)) {
1758
2753
  return this.setAttr("viewBox", `${p1} ${p2} ${p3} ${p4}`);
1759
2754
  }
1760
2755
  const value = p1;
@@ -1804,96 +2799,42 @@ var JJSE = class _JJSE extends JJEx {
1804
2799
 
1805
2800
  // src/wrappers/JJN.ts
1806
2801
  JJN.wrap = function wrap(raw) {
1807
- if (isStr(raw)) {
1808
- return JJT.fromStr(raw);
1809
- }
1810
- if (!isObj(raw)) {
1811
- throw typeErr("raw", "an object", raw);
1812
- }
1813
- if (isA(raw, JJN)) {
1814
- return raw;
1815
- }
1816
- if (isA(raw, HTMLElement)) {
1817
- return JJHE.from(raw);
1818
- }
1819
- if (isA(raw, SVGElement)) {
1820
- return JJSE.from(raw);
1821
- }
1822
- if (isA(raw, Element)) {
1823
- return JJE.from(raw);
1824
- }
1825
- if (isA(raw, ShadowRoot)) {
1826
- return JJSR.from(raw);
1827
- }
1828
- if (isA(raw, DocumentFragment)) {
1829
- return JJDF.from(raw);
1830
- }
1831
- if (isA(raw, Document)) {
1832
- return JJD.from(raw);
1833
- }
1834
- if (isA(raw, Text)) {
1835
- return JJT.from(raw);
1836
- }
1837
- if (isA(raw, Node)) {
1838
- return JJN.from(raw);
1839
- }
1840
- throw typeErr("raw", "a Node", raw);
1841
- };
1842
-
1843
- // src/helpers.ts
1844
- function h(tagName, attributes, ...children) {
1845
- const ret = JJHE.create(tagName).addChild(...children);
1846
- if (attributes) {
1847
- ret.setAttr(attributes);
1848
- }
1849
- return ret;
1850
- }
1851
- function linkAs(href) {
1852
- switch (fileExt(href)) {
1853
- case "html":
1854
- case "htm":
1855
- case "md":
1856
- return "fetch";
1857
- case "css":
1858
- return "style";
1859
- case "js":
1860
- case "mjs":
1861
- case "cjs":
1862
- return "script";
1863
- default:
1864
- throw new Error(`No 'as' attribute was specified and we failed to guess it from the URL: ${href}`);
1865
- }
1866
- }
1867
- function createLinkPre(href, rel, as) {
1868
- if (!isStr(href)) {
1869
- if (!isA(href, URL)) {
1870
- throw typeErr("href", "a string or URL", href);
2802
+ if (raw && typeof raw === "object") {
2803
+ if (isInstance(raw, JJN)) {
2804
+ return raw;
1871
2805
  }
1872
- href = href.toString();
1873
- }
1874
- if (!["prefetch", "preload"].includes(rel)) {
1875
- throw new RangeError(errMsg("rel", `'prefetch' or 'preload'`, rel));
1876
- }
1877
- if (!as) {
1878
- as = linkAs(href);
1879
- if (!as) {
1880
- throw new Error(`Could not guess 'as' attribute from URL: ${href}`);
2806
+ if (isInstance(raw, HTMLElement)) {
2807
+ return JJHE.from(raw);
2808
+ }
2809
+ if (isInstance(raw, SVGElement)) {
2810
+ return JJSE.from(raw);
2811
+ }
2812
+ if (isInstance(raw, MathMLElement)) {
2813
+ return JJME.from(raw);
2814
+ }
2815
+ if (isInstance(raw, Element)) {
2816
+ return JJE.from(raw);
2817
+ }
2818
+ if (isInstance(raw, ShadowRoot)) {
2819
+ return JJSR.from(raw);
2820
+ }
2821
+ if (isInstance(raw, DocumentFragment)) {
2822
+ return JJDF.from(raw);
2823
+ }
2824
+ if (isInstance(raw, Document)) {
2825
+ return JJD.from(raw);
2826
+ }
2827
+ if (isInstance(raw, Text)) {
2828
+ return JJT.from(raw);
2829
+ }
2830
+ if (isInstance(raw, Node)) {
2831
+ return JJN.from(raw);
1881
2832
  }
1882
2833
  }
1883
- if (!["fetch", "style", "script"].includes(as)) {
1884
- throw new RangeError(errMsg("as", `'fetch', 'style', or 'script'`, as));
1885
- }
1886
- return JJHE.create("link").setAttr({
1887
- href,
1888
- rel,
1889
- as
1890
- });
1891
- }
1892
- function addLinkPre(...args) {
1893
- const link = createLinkPre(...args);
1894
- document.head.append(link.ref);
1895
- return link;
1896
- }
2834
+ return JJT.create(String(raw));
2835
+ };
2836
+
2837
+ // src/fetchers.ts
1897
2838
  async function fetchText(url, mime = "text/*") {
1898
2839
  if (!isStr(mime)) {
1899
2840
  throw typeErr("mime", "a string", mime);
@@ -1904,168 +2845,77 @@ async function fetchText(url, mime = "text/*") {
1904
2845
  }
1905
2846
  return response.text();
1906
2847
  }
1907
- async function fetchHtml(url) {
1908
- return await fetchText(url, "text/html");
1909
- }
1910
- async function fetchCss(url) {
1911
- return await fetchText(url, "text/css");
2848
+ async function fetchTemplate(url) {
2849
+ try {
2850
+ const htmlStr = await fetchText(url, "text/html");
2851
+ return JJDF.from(document.createRange().createContextualFragment(htmlStr));
2852
+ } catch (err) {
2853
+ throw new Error(`Failed to fetch or process HTML from ${url}`, { cause: err });
2854
+ }
1912
2855
  }
1913
2856
  async function fetchStyle(url) {
1914
- return await cssToStyle(await fetchCss(url));
2857
+ try {
2858
+ const sheet = new CSSStyleSheet();
2859
+ return await sheet.replace(await fetchText(url, "text/css"));
2860
+ } catch (err) {
2861
+ throw new Error(`Failed to fetch or convert CSS string to CSSStyleSheet from ${url}`, { cause: err });
2862
+ }
1915
2863
  }
1916
2864
 
1917
2865
  // src/components.ts
1918
2866
  function attr2prop(instance, name, oldValue, newValue) {
1919
- if (!isA(instance, HTMLElement)) {
1920
- throw typeErr("instance", "an HTMLElement", instance);
2867
+ if (!isInstance(instance, HTMLElement)) {
2868
+ throw typeErr(
2869
+ "instance",
2870
+ "an HTMLElement",
2871
+ instance,
2872
+ "Call attr2prop(this, ...) from attributeChangedCallback on a custom element instance."
2873
+ );
1921
2874
  }
1922
2875
  if (oldValue !== newValue) {
1923
- const propName = keb2cam(name);
1924
- if (hasProp(instance, propName)) {
1925
- instance[propName] = newValue;
1926
- return true;
2876
+ try {
2877
+ const propName = keb2cam(name);
2878
+ if (hasProp(instance, propName)) {
2879
+ instance[propName] = newValue;
2880
+ return true;
2881
+ }
2882
+ } catch (err) {
2883
+ throw new Error(
2884
+ `Failed to set property using attribute change event ${name}. Old value: ${oldValue}, New value: ${newValue}.`,
2885
+ { cause: err }
2886
+ );
1927
2887
  }
1928
2888
  }
1929
2889
  return false;
1930
2890
  }
1931
- async function registerComponent(name, constructor, options) {
2891
+ async function defineComponent(name, constructor, options) {
1932
2892
  if (!isStr(name)) {
1933
- throw typeErr("name", "a string", name);
1934
- }
1935
- if (!isFn(constructor)) {
1936
- throw typeErr("constructor", "a function", constructor);
1937
- }
1938
- if (!customElements.get(name)) {
1939
- customElements.define(name, constructor, options);
1940
- await customElements.whenDefined(name);
1941
- }
1942
- }
1943
-
1944
- // src/ShadowMaster.ts
1945
- async function templatePromise(templateConfig) {
1946
- if (templateConfig === void 0) {
1947
- return void 0;
1948
- }
1949
- if (isFn(templateConfig)) {
1950
- templateConfig = await templateConfig();
1951
- }
1952
- templateConfig = await templateConfig;
1953
- if (isStr(templateConfig)) {
1954
- return templateConfig;
2893
+ throw typeErr("name", "a string", name, 'Pass a valid name like "my-component".');
1955
2894
  }
1956
- if (isA(templateConfig, JJDF)) {
1957
- return templateConfig.ref.cloneNode(true);
2895
+ if (!name.includes("-")) {
2896
+ throw new SyntaxError(
2897
+ errMsg("name", "a custom-element name containing a hyphen", name, 'Use kebab-case like "my-component".')
2898
+ );
1958
2899
  }
1959
- if (isA(templateConfig, DocumentFragment)) {
1960
- return templateConfig.cloneNode(true);
2900
+ if (typeof constructor !== "function") {
2901
+ throw typeErr(
2902
+ "constructor",
2903
+ "a function",
2904
+ constructor,
2905
+ 'Pass the custom element class itself, e.g. defineComponent("my-component", MyComponent).'
2906
+ );
1961
2907
  }
1962
- if (isA(templateConfig, JJHE)) {
1963
- if (templateConfig.ref instanceof HTMLTemplateElement) {
1964
- return templateConfig.ref.content.cloneNode(true);
2908
+ const definedConstructor = customElements.get(name);
2909
+ if (definedConstructor) {
2910
+ if (definedConstructor !== constructor) {
2911
+ throw new ReferenceError(`A different constructor is already defined for the custom element "${name}".`);
1965
2912
  }
1966
- return templateConfig.ref.outerHTML;
1967
- }
1968
- if (isA(templateConfig, HTMLElement)) {
1969
- return templateConfig instanceof HTMLTemplateElement ? templateConfig.content.cloneNode(true) : templateConfig.outerHTML;
1970
- }
1971
- throw typeErr("template", "a string, JJHE, JJDF, HTMLElement, or DocumentFragment", templateConfig);
1972
- }
1973
- async function stylePromise(styleConfig) {
1974
- if (isFn(styleConfig)) {
1975
- styleConfig = await styleConfig();
1976
- }
1977
- styleConfig = await styleConfig;
1978
- if (isA(styleConfig, CSSStyleSheet)) {
1979
- return styleConfig;
1980
- }
1981
- if (isStr(styleConfig)) {
1982
- return await cssToStyle(styleConfig);
1983
- }
1984
- throw typeErr("style", "a CSS string or CSSStyleSheet", styleConfig);
1985
- }
1986
- function stylePromises(styleConfigs) {
1987
- if (!isArr(styleConfigs)) {
1988
- return [];
2913
+ } else {
2914
+ customElements.define(name, constructor, options);
2915
+ await customElements.whenDefined(name);
1989
2916
  }
1990
- return styleConfigs.map(stylePromise);
1991
- }
1992
- async function resolveConfig(templateConfig, styleConfigs) {
1993
- const [template, ...styles] = await Promise.all([templatePromise(templateConfig), ...stylePromises(styleConfigs)]);
1994
- return { template, styles };
2917
+ return Boolean(definedConstructor);
1995
2918
  }
1996
- var _templateConfig, _stylesConfig, _normalizedConfig;
1997
- var _ShadowMaster = class _ShadowMaster {
1998
- constructor() {
1999
- __privateAdd(this, _templateConfig);
2000
- __privateAdd(this, _stylesConfig, []);
2001
- __privateAdd(this, _normalizedConfig);
2002
- }
2003
- /**
2004
- * Creates a new instance of ShadowMaster.
2005
- *
2006
- * @returns A new ShadowMaster instance.
2007
- */
2008
- static create() {
2009
- return new _ShadowMaster();
2010
- }
2011
- /**
2012
- * Sets the template configuration.
2013
- *
2014
- * @param templateConfig - The template configuration.
2015
- * @returns The instance for chaining.
2016
- *
2017
- * @example
2018
- * ```ts
2019
- * // Accepts string, promise, or fetchHtml result
2020
- * sm.setTemplate(fetchHtml('./template.html'))
2021
- * ```
2022
- */
2023
- setTemplate(templateConfig) {
2024
- __privateSet(this, _templateConfig, templateConfig);
2025
- return this;
2026
- }
2027
- /**
2028
- * Adds one or more style configurations.
2029
- *
2030
- * @param stylesConfig - Variable number of style configurations.
2031
- * @returns The instance for chaining.
2032
- *
2033
- * @example
2034
- * ```ts
2035
- * sm.addStyles(
2036
- * 'p { color: red; }',
2037
- * fetchCss('./styles.css'),
2038
- * () => fetchCss('../lazy-loaded-styles.css'),
2039
- * )
2040
- * ```
2041
- */
2042
- addStyles(...stylesConfig) {
2043
- __privateGet(this, _stylesConfig).push(...stylesConfig);
2044
- return this;
2045
- }
2046
- /**
2047
- * Resolves the configuration to something that can be fed to `JJHE.initShadow()` function
2048
- *
2049
- * The result is cached, so subsequent calls return the same promise.
2050
- * Note: Any changes made to the ShadowMaster instance (via setTemplate/addStyles)
2051
- * after the first call to getResolved() will be ignored.
2052
- *
2053
- * @returns A promise resolving to the ShadowConfig.
2054
- */
2055
- async getResolved() {
2056
- if (!__privateGet(this, _normalizedConfig)) {
2057
- __privateSet(this, _normalizedConfig, resolveConfig(__privateGet(this, _templateConfig), __privateGet(this, _stylesConfig)));
2058
- }
2059
- return await __privateGet(this, _normalizedConfig);
2060
- }
2061
- };
2062
- _templateConfig = new WeakMap();
2063
- _stylesConfig = new WeakMap();
2064
- _normalizedConfig = new WeakMap();
2065
- var ShadowMaster = _ShadowMaster;
2066
-
2067
- // src/index.ts
2068
- var doc = JJD.from(document);
2069
2919
  // Annotate the CommonJS export names for ESM import in node:
2070
2920
  0 && (module.exports = {
2071
2921
  JJD,
@@ -2073,27 +2923,15 @@ var doc = JJD.from(document);
2073
2923
  JJE,
2074
2924
  JJET,
2075
2925
  JJHE,
2926
+ JJME,
2076
2927
  JJN,
2077
2928
  JJSE,
2078
2929
  JJSR,
2079
2930
  JJT,
2080
- ShadowMaster,
2081
- addLinkPre,
2082
2931
  attr2prop,
2083
- createLinkPre,
2084
- cssToStyle,
2085
- doc,
2086
- fetchCss,
2087
- fetchHtml,
2932
+ customEvent,
2933
+ defineComponent,
2088
2934
  fetchStyle,
2089
- fetchText,
2090
- fileExt,
2091
- h,
2092
- keb2cam,
2093
- keb2pas,
2094
- nextAnimationFrame,
2095
- pas2keb,
2096
- registerComponent,
2097
- sleep
2935
+ fetchTemplate
2098
2936
  });
2099
2937
  //# sourceMappingURL=bundle.cjs.map