thunderous 2.3.13 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,1152 @@
1
- export { customElement } from './custom-element';
2
- export { createRegistry } from './registry';
3
- export { onServerDefine, insertTemplates, clientOnlyCallback } from './server-side';
4
- export { createEffect, createSignal, derived } from './signals';
5
- export { html, css } from './render';
1
+ // src/constants.ts
2
+ var DEFAULT_RENDER_OPTIONS = {
3
+ formAssociated: false,
4
+ observedAttributes: [],
5
+ attributesAsProperties: [],
6
+ attachShadow: true,
7
+ shadowRootOptions: {
8
+ mode: "closed",
9
+ delegatesFocus: false,
10
+ clonable: false,
11
+ serializable: false,
12
+ slotAssignment: "named"
13
+ }
14
+ };
15
+
16
+ // src/signals.ts
17
+ var ident = null;
18
+ var effects = /* @__PURE__ */ new WeakMap();
19
+ var createSignal = (initVal, options) => {
20
+ const subscribers = /* @__PURE__ */ new Set();
21
+ let value = initVal;
22
+ const getter = (getterOptions) => {
23
+ if (ident !== null) {
24
+ subscribers.add(ident);
25
+ }
26
+ if (options?.debugMode === true || getterOptions?.debugMode === true) {
27
+ let label = "anonymous signal";
28
+ if (options?.label !== void 0) {
29
+ label = `(${options.label})`;
30
+ if (getterOptions?.label !== void 0) {
31
+ label += ` ${getterOptions.label}`;
32
+ }
33
+ } else if (getterOptions?.label !== void 0) {
34
+ label = getterOptions.label;
35
+ }
36
+ console.log("Signal retrieved:", {
37
+ value,
38
+ subscribers: Array.from(subscribers).map((sym) => effects.get(sym)),
39
+ label
40
+ });
41
+ }
42
+ return value;
43
+ };
44
+ getter.getter = true;
45
+ let stackLength = 0;
46
+ const setter = (newValue, setterOptions) => {
47
+ stackLength++;
48
+ queueMicrotask(() => stackLength--);
49
+ if (stackLength > 1e3) {
50
+ console.error(new Error("Signal setter stack overflow detected. Possible infinite loop. Bailing out."));
51
+ stackLength = 0;
52
+ return;
53
+ }
54
+ const isObject = typeof newValue === "object" && newValue !== null;
55
+ if (!isObject && value === newValue) return;
56
+ if (isObject && typeof value === "object" && value !== null) {
57
+ const isPlainObject = (obj) => typeof obj === "object" && obj !== null && Object.getPrototypeOf(obj) === Object.prototype;
58
+ if (isPlainObject(value) && isPlainObject(newValue)) {
59
+ if (JSON.stringify(value) === JSON.stringify(newValue)) return;
60
+ }
61
+ }
62
+ const oldValue = value;
63
+ value = newValue;
64
+ for (const sym of subscribers) {
65
+ const effectRef = effects.get(sym);
66
+ if (effectRef !== void 0) {
67
+ try {
68
+ effectRef.fn({
69
+ lastValue: effectRef.value,
70
+ destroy: () => {
71
+ effects.delete(sym);
72
+ queueMicrotask(() => subscribers.delete(sym));
73
+ }
74
+ });
75
+ } catch (error) {
76
+ console.error("Error in subscriber:", { error, oldValue, newValue, fn: effectRef.fn });
77
+ }
78
+ } else {
79
+ queueMicrotask(() => subscribers.delete(sym));
80
+ }
81
+ }
82
+ if (options?.debugMode === true || setterOptions?.debugMode === true) {
83
+ let label = "anonymous signal";
84
+ if (options?.label !== void 0) {
85
+ label = `(${options.label})`;
86
+ if (setterOptions?.label !== void 0) {
87
+ label += ` ${setterOptions.label}`;
88
+ }
89
+ } else if (setterOptions?.label !== void 0) {
90
+ label = setterOptions.label;
91
+ }
92
+ console.log("Signal set:", {
93
+ oldValue,
94
+ newValue,
95
+ subscribers: Array.from(subscribers).map((sym) => effects.get(sym)),
96
+ label
97
+ });
98
+ }
99
+ };
100
+ return [getter, setter];
101
+ };
102
+ var derived = (fn, options) => {
103
+ const [getter, setter] = createSignal(void 0, options);
104
+ createEffect(() => {
105
+ try {
106
+ setter(fn());
107
+ } catch (error) {
108
+ console.error("Error in derived signal:", { error, fn });
109
+ }
110
+ });
111
+ return getter;
112
+ };
113
+ var createEffect = (fn, value) => {
114
+ const privateIdent = ident = {};
115
+ effects.set(ident, { fn, value });
116
+ try {
117
+ fn({
118
+ lastValue: value,
119
+ destroy: () => {
120
+ effects.delete(privateIdent);
121
+ }
122
+ });
123
+ } catch (error) {
124
+ console.error("Error in effect:", { error, fn });
125
+ }
126
+ ident = null;
127
+ };
128
+
129
+ // src/utilities.ts
130
+ var NOOP = () => void 0;
131
+ var queryComment = (node, comment) => {
132
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_COMMENT, {
133
+ acceptNode: (n) => n.nodeValue === comment ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
134
+ });
135
+ return walker.nextNode();
136
+ };
137
+ var queryChildren = (children, selector) => {
138
+ for (const child of children) {
139
+ if (child instanceof Element && child.matches(selector)) {
140
+ return child;
141
+ }
142
+ }
143
+ return null;
144
+ };
145
+
146
+ // src/server-side.ts
147
+ var isServer = typeof window === "undefined";
148
+ var serverDefineFns = /* @__PURE__ */ new Set();
149
+ var onServerDefine = (fn) => {
150
+ serverDefineFns.add(fn);
151
+ };
152
+ var serverDefine = ({
153
+ tagName,
154
+ serverRender,
155
+ options,
156
+ elementResult,
157
+ scopedRegistry,
158
+ parentRegistry
159
+ }) => {
160
+ if (parentRegistry !== void 0) {
161
+ if (parentRegistry.getTagName(elementResult) !== tagName.toUpperCase()) {
162
+ parentRegistry.define(tagName, elementResult);
163
+ }
164
+ parentRegistry.__serverRenderOpts.set(tagName, { serverRender, ...options });
165
+ if (parentRegistry.scoped) return;
166
+ }
167
+ for (const fn of serverDefineFns) {
168
+ let result = serverRender(getServerRenderArgs(tagName));
169
+ result = wrapTemplate({
170
+ tagName,
171
+ serverRender,
172
+ options
173
+ });
174
+ if (scopedRegistry !== void 0) {
175
+ for (const [scopedTagName, scopedRenderOptions] of scopedRegistry.__serverRenderOpts) {
176
+ const { serverRender: serverRender2, ...scopedOptions } = scopedRenderOptions;
177
+ let template = serverRender2(getServerRenderArgs(scopedTagName, scopedRegistry));
178
+ template = wrapTemplate({
179
+ tagName: scopedTagName,
180
+ serverRender: serverRender2,
181
+ options: scopedOptions
182
+ });
183
+ result = insertTemplates(scopedTagName, template, result);
184
+ }
185
+ }
186
+ fn(tagName, result);
187
+ }
188
+ };
189
+ var serverCss = /* @__PURE__ */ new Map();
190
+ var getServerRenderArgs = (tagName, registry) => ({
191
+ get elementRef() {
192
+ return new Proxy({}, {
193
+ get: () => {
194
+ const error = new Error("The `elementRef` property is not available on the server.");
195
+ console.error(error);
196
+ throw error;
197
+ }
198
+ });
199
+ },
200
+ get root() {
201
+ return new Proxy({}, {
202
+ get: () => {
203
+ const error = new Error("The `root` property is not available on the server.");
204
+ console.error(error);
205
+ throw error;
206
+ }
207
+ });
208
+ },
209
+ get internals() {
210
+ return new Proxy({}, {
211
+ get: () => {
212
+ const error = new Error("The `internals` property is not available on the server.");
213
+ console.error(error);
214
+ throw error;
215
+ }
216
+ });
217
+ },
218
+ attributeChangedCallback: NOOP,
219
+ connectedCallback: NOOP,
220
+ disconnectedCallback: NOOP,
221
+ adoptedCallback: NOOP,
222
+ formDisabledCallback: NOOP,
223
+ formResetCallback: NOOP,
224
+ formStateRestoreCallback: NOOP,
225
+ formAssociatedCallback: NOOP,
226
+ clientOnlyCallback: NOOP,
227
+ customCallback: () => "",
228
+ getter: (fn) => {
229
+ const _fn = () => fn();
230
+ _fn.getter = true;
231
+ return _fn;
232
+ },
233
+ attrSignals: new Proxy({}, { get: (_, attr) => createSignal(`{{attr:${String(attr)}}}`) }),
234
+ propSignals: new Proxy({}, { get: () => createSignal(null) }),
235
+ refs: {},
236
+ // @ts-expect-error // this will be a string for SSR, but this is true for internal cases only.
237
+ // The end user will see the public type, which is either a CSSStyleSheet or HTMLStyleElement.
238
+ adoptStyleSheet: (cssStr) => {
239
+ const _serverCss = registry !== void 0 ? registry.__serverCss : serverCss;
240
+ const cssArr = _serverCss.get(tagName) ?? [];
241
+ cssArr.push(cssStr);
242
+ _serverCss.set(tagName, cssArr);
243
+ }
244
+ });
245
+ var wrapTemplate = ({ tagName, serverRender, options }) => {
246
+ const { registry } = options.shadowRootOptions;
247
+ const scopedRegistry = registry !== void 0 && "scoped" in registry ? registry : void 0;
248
+ const initialRenderString = serverRender(getServerRenderArgs(tagName, scopedRegistry));
249
+ const _serverCss = scopedRegistry?.__serverCss ?? serverCss;
250
+ const cssRenderString = (_serverCss.get(tagName) ?? []).map((cssStr) => `<style>${cssStr}</style>`).join("");
251
+ const finalScopedRenderString = options.attachShadow ? (
252
+ /* html */
253
+ `
254
+ <template
255
+ shadowrootmode="${options.shadowRootOptions.mode}"
256
+ shadowrootdelegatesfocus="${options.shadowRootOptions.delegatesFocus}"
257
+ shadowrootclonable="${options.shadowRootOptions.clonable}"
258
+ shadowrootserializable="${options.shadowRootOptions.serializable}"
259
+ >
260
+ ${cssRenderString + initialRenderString}
261
+ </template>
262
+ `
263
+ ) : cssRenderString + initialRenderString;
264
+ return finalScopedRenderString;
265
+ };
266
+ var insertTemplates = (tagName, template, inputString) => {
267
+ return inputString.replace(new RegExp(`(<s*${tagName}([^>]*)>)`, "gm"), ($1, _, $3) => {
268
+ const attrs = $3.split(/(?<=")\s+/).filter((attr) => attr.trim() !== "").map((attr) => {
269
+ const [_key, _value] = attr.split("=");
270
+ const key = _key.trim();
271
+ const value = _value?.replace(/"/g, "") ?? "";
272
+ return [key, value];
273
+ });
274
+ let scopedResult = template;
275
+ for (const [key, value] of attrs) {
276
+ scopedResult = scopedResult.replace(new RegExp(`{{attr:${key}}}`, "gm"), value);
277
+ }
278
+ return $1 + scopedResult;
279
+ });
280
+ };
281
+ var clientOnlyCallback = (fn) => {
282
+ if (!isServer) return fn();
283
+ };
284
+
285
+ // src/render.ts
286
+ var CALLBACK_BINDING_REGEX = /(\{\{callback:.+\}\})/;
287
+ var LEGACY_CALLBACK_BINDING_REGEX = /(this.getRootNode\(\).host.__customCallbackFns.get\('.+'\)\(event\))/;
288
+ var SIGNAL_BINDING_REGEX = /(\{\{signal:.+\}\})/;
289
+ var FRAGMENT_ATTRIBUTE = "___thunderous-fragment";
290
+ var renderState = {
291
+ currentShadowRoot: null,
292
+ signalMap: /* @__PURE__ */ new Map(),
293
+ callbackMap: /* @__PURE__ */ new Map(),
294
+ fragmentMap: /* @__PURE__ */ new Map(),
295
+ childrenMap: /* @__PURE__ */ new Map(),
296
+ propertyMap: /* @__PURE__ */ new Map(),
297
+ registry: typeof customElements !== "undefined" ? customElements : {}
298
+ };
299
+ var logPropertyWarning = (propName, element) => {
300
+ console.warn(
301
+ `Property "${propName}" does not exist on element:`,
302
+ element,
303
+ "\n\nThunderous will attempt to set the property anyway, but this may result in unexpected behavior. Please make sure the property exists on the element prior to setting it."
304
+ );
305
+ };
306
+ var asNodeList = (value, parent) => {
307
+ if (typeof value === "string") return [new Text(value)];
308
+ if (value instanceof DocumentFragment) return [...value.children];
309
+ if (Array.isArray(value)) {
310
+ const nodeList = [];
311
+ let count = 0;
312
+ const keys = /* @__PURE__ */ new Set();
313
+ for (const item of value) {
314
+ const cachedItem = item instanceof DocumentFragment ? renderState.childrenMap.get(item) : void 0;
315
+ const children = cachedItem ?? asNodeList(item, parent);
316
+ if (cachedItem === void 0 && item instanceof DocumentFragment) {
317
+ renderState.childrenMap.set(item, children);
318
+ }
319
+ if (children.length > 1) {
320
+ console.error(
321
+ "When rendering arrays, fragments must contain only one top-level element at a time. Error occured in:",
322
+ parent
323
+ );
324
+ }
325
+ const child = children[0];
326
+ if (child === null || !(child instanceof Element)) continue;
327
+ let key = child.getAttribute("key");
328
+ if (key === null) {
329
+ console.warn(
330
+ "When rendering arrays, a `key` attribute should be provided on each child element. An index was automatically applied, but this could result in unexpected behavior:",
331
+ child
332
+ );
333
+ key = String(count);
334
+ child.setAttribute("key", key);
335
+ }
336
+ if (keys.has(key)) {
337
+ console.warn(
338
+ `When rendering arrays, each child should have a unique \`key\` attribute. Duplicate key "${key}" found on:`,
339
+ child
340
+ );
341
+ }
342
+ keys.add(key);
343
+ count++;
344
+ nodeList.push(...children);
345
+ }
346
+ return nodeList;
347
+ }
348
+ return [new Text()];
349
+ };
350
+ var processValue = (value) => {
351
+ if (!isServer && value instanceof DocumentFragment) {
352
+ const uniqueKey = crypto.randomUUID();
353
+ renderState.fragmentMap.set(uniqueKey, value);
354
+ return `<div ${FRAGMENT_ATTRIBUTE}="${uniqueKey}"></div>`;
355
+ }
356
+ if (typeof value === "function" && "getter" in value && value.getter === true) {
357
+ const getter = value;
358
+ const uniqueKey = crypto.randomUUID();
359
+ renderState.signalMap.set(uniqueKey, getter);
360
+ let result = getter();
361
+ if (Array.isArray(result)) {
362
+ result = result.map((item) => processValue(item)).join("");
363
+ }
364
+ return isServer ? String(result) : `{{signal:${uniqueKey}}}`;
365
+ }
366
+ if (typeof value === "function") {
367
+ const uniqueKey = crypto.randomUUID();
368
+ renderState.callbackMap.set(uniqueKey, value);
369
+ return isServer ? String(value()) : `{{callback:${uniqueKey}}}`;
370
+ }
371
+ return String(value);
372
+ };
373
+ var evaluateBindings = (element, fragment) => {
374
+ for (const child of [...element.childNodes]) {
375
+ if (child instanceof Text && SIGNAL_BINDING_REGEX.test(child.data)) {
376
+ const textList = child.data.split(SIGNAL_BINDING_REGEX);
377
+ const nextSibling = child.nextSibling;
378
+ const prevSibling = child.previousSibling;
379
+ textList.forEach((text, i) => {
380
+ const uniqueKey = SIGNAL_BINDING_REGEX.test(text) ? text.replace(/\{\{signal:(.+)\}\}/, "$1") : void 0;
381
+ const signal = uniqueKey !== void 0 ? renderState.signalMap.get(uniqueKey) : void 0;
382
+ const newValue = signal !== void 0 ? signal() : text;
383
+ const initialChildren = asNodeList(newValue, element);
384
+ if (i === 0) {
385
+ child.replaceWith(...initialChildren);
386
+ } else {
387
+ const endAnchor2 = queryComment(element, `${uniqueKey}:end`) ?? nextSibling;
388
+ if (endAnchor2 !== null) {
389
+ endAnchor2.before(...initialChildren);
390
+ } else {
391
+ element.append(...initialChildren);
392
+ }
393
+ }
394
+ if (uniqueKey === void 0) return;
395
+ const startAnchor = document.createComment(`${uniqueKey}:start`);
396
+ if (prevSibling !== null) {
397
+ prevSibling.after(startAnchor);
398
+ } else {
399
+ element.prepend(startAnchor);
400
+ }
401
+ const endAnchor = document.createComment(`${uniqueKey}:end`);
402
+ if (nextSibling !== null) {
403
+ nextSibling.before(endAnchor);
404
+ } else {
405
+ element.append(endAnchor);
406
+ }
407
+ const bindText = (node, signal2) => {
408
+ createEffect(({ destroy }) => {
409
+ const result = signal2();
410
+ if (Array.isArray(result)) {
411
+ destroy();
412
+ bindArray(signal2);
413
+ return;
414
+ }
415
+ if (result instanceof DocumentFragment) {
416
+ destroy();
417
+ bindFragment(signal2);
418
+ return;
419
+ }
420
+ node.data = result === null ? "" : String(result);
421
+ });
422
+ };
423
+ const bindArray = (signal2) => {
424
+ createEffect(
425
+ ({ lastValue: oldChildren, destroy }) => {
426
+ const result = signal2();
427
+ const newChildren = asNodeList(result, element);
428
+ const firstChild = newChildren[0];
429
+ if (!Array.isArray(result) && newChildren.length === 1 && firstChild instanceof DocumentFragment) {
430
+ destroy();
431
+ bindFragment(signal2);
432
+ return;
433
+ }
434
+ if (newChildren.length === 1 && firstChild instanceof Text) {
435
+ destroy();
436
+ bindText(firstChild, signal2);
437
+ return;
438
+ }
439
+ while (startAnchor.nextSibling !== endAnchor) {
440
+ startAnchor.nextSibling?.remove();
441
+ }
442
+ startAnchor.after(...newChildren);
443
+ if (oldChildren === null) return newChildren;
444
+ for (const persistedChild of oldChildren) {
445
+ if (persistedChild instanceof Element) {
446
+ const key = persistedChild.getAttribute("key");
447
+ if (key === null) continue;
448
+ const newChild = queryChildren(newChildren, `[key="${key}"]`);
449
+ if (newChild === null) {
450
+ persistedChild.remove();
451
+ continue;
452
+ }
453
+ for (const attr of [...persistedChild.attributes]) {
454
+ if (!newChild.hasAttribute(attr.name)) persistedChild.removeAttribute(attr.name);
455
+ }
456
+ for (const newAttr of [...newChild.attributes]) {
457
+ const oldAttrValue = persistedChild.getAttribute(newAttr.name);
458
+ if (oldAttrValue?.startsWith("this.__customCallbackFns")) continue;
459
+ persistedChild.setAttribute(newAttr.name, newAttr.value);
460
+ }
461
+ newChild.replaceWith(persistedChild);
462
+ }
463
+ }
464
+ return newChildren;
465
+ },
466
+ null
467
+ );
468
+ };
469
+ const bindFragment = (signal2) => {
470
+ const initialFragment = signal2();
471
+ renderState.childrenMap.set(initialFragment, [...initialFragment.childNodes]);
472
+ createEffect(({ destroy }) => {
473
+ const result = signal2();
474
+ const cachedChildren = renderState.childrenMap.get(initialFragment);
475
+ const children = cachedChildren ?? asNodeList(result, element);
476
+ if (Array.isArray(result)) {
477
+ destroy();
478
+ bindArray(signal2);
479
+ return;
480
+ }
481
+ if (result instanceof Text) {
482
+ const children2 = asNodeList(result, element);
483
+ const text2 = children2[0];
484
+ destroy();
485
+ bindText(text2, signal2);
486
+ return;
487
+ }
488
+ while (startAnchor.nextSibling !== endAnchor) {
489
+ startAnchor.nextSibling?.remove();
490
+ }
491
+ startAnchor.after(...children);
492
+ });
493
+ };
494
+ if (signal !== void 0) {
495
+ if (Array.isArray(newValue)) {
496
+ bindArray(signal);
497
+ } else if (initialChildren instanceof DocumentFragment) {
498
+ bindFragment(signal);
499
+ } else {
500
+ const initialChild = initialChildren[0];
501
+ bindText(initialChild, signal);
502
+ }
503
+ }
504
+ });
505
+ }
506
+ if (child instanceof Element && child.hasAttribute(FRAGMENT_ATTRIBUTE)) {
507
+ const uniqueKey = child.getAttribute(FRAGMENT_ATTRIBUTE);
508
+ const childFragment = renderState.fragmentMap.get(uniqueKey);
509
+ if (childFragment !== void 0) {
510
+ child.replaceWith(childFragment);
511
+ }
512
+ } else if (child instanceof Element) {
513
+ for (const attr of [...child.attributes]) {
514
+ const attrName = attr.name;
515
+ if (SIGNAL_BINDING_REGEX.test(attr.value)) {
516
+ const textList = attr.value.split(SIGNAL_BINDING_REGEX);
517
+ let prevText = attr.value;
518
+ createEffect(() => {
519
+ let newText = "";
520
+ let hasNull = false;
521
+ let signal;
522
+ for (const text of textList) {
523
+ const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
524
+ if (signal === void 0) {
525
+ signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : void 0;
526
+ const value = signal !== void 0 ? signal() : text;
527
+ if (value === null) hasNull = true;
528
+ newText += String(value);
529
+ } else {
530
+ newText += text;
531
+ }
532
+ }
533
+ if (hasNull && newText === "null" || attrName.startsWith("prop-id:")) {
534
+ if (child.hasAttribute(attrName)) child.removeAttribute(attrName);
535
+ } else {
536
+ if (newText !== prevText) child.setAttribute(attrName, newText);
537
+ }
538
+ if (attrName.startsWith("prop-id:")) {
539
+ if (child.hasAttribute(attrName)) child.removeAttribute(attrName);
540
+ const propId = attrName.replace("prop-id:", "");
541
+ const propName = renderState.propertyMap.get(propId);
542
+ if (propName === void 0) {
543
+ console.error(
544
+ `BRANCH:SIGNAL; Property ID "${propId}" does not exist in the property map. This is likely a problem with Thunderous. Report a bug if you see this message. https://github.com/Thunder-Solutions/Thunderous/issues`,
545
+ child
546
+ );
547
+ return;
548
+ }
549
+ const newValue = hasNull && newText === "null" ? null : newText;
550
+ if (!(propName in child)) logPropertyWarning(propName, child);
551
+ child[propName] = signal !== void 0 ? signal() : newValue;
552
+ }
553
+ prevText = newText;
554
+ });
555
+ } else if (LEGACY_CALLBACK_BINDING_REGEX.test(attr.value)) {
556
+ const getRootNode = child.getRootNode.bind(child);
557
+ child.getRootNode = () => {
558
+ const rootNode = getRootNode();
559
+ return rootNode instanceof ShadowRoot ? rootNode : fragment;
560
+ };
561
+ } else if (CALLBACK_BINDING_REGEX.test(attr.value)) {
562
+ const textList = attr.value.split(CALLBACK_BINDING_REGEX);
563
+ createEffect(() => {
564
+ child.__customCallbackFns = child.__customCallbackFns ?? /* @__PURE__ */ new Map();
565
+ let uniqueKey = "";
566
+ for (const text of textList) {
567
+ const _uniqueKey = text.replace(/\{\{callback:(.+)\}\}/, "$1");
568
+ if (_uniqueKey !== text) uniqueKey = _uniqueKey;
569
+ const callback = uniqueKey !== text ? renderState.callbackMap.get(uniqueKey) : void 0;
570
+ if (callback !== void 0) {
571
+ child.__customCallbackFns.set(uniqueKey, callback);
572
+ }
573
+ }
574
+ if (uniqueKey !== "" && !attrName.startsWith("prop-id:")) {
575
+ child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
576
+ } else if (attrName.startsWith("prop-id:")) {
577
+ child.removeAttribute(attrName);
578
+ const propId = attrName.replace("prop-id:", "");
579
+ const propName = renderState.propertyMap.get(propId);
580
+ if (propName === void 0) {
581
+ console.error(
582
+ `BRANCH:CALLBACK; Property ID "${propId}" does not exist in the property map. This is likely a problem with Thunderous. Report a bug if you see this message. https://github.com/Thunder-Solutions/Thunderous/issues`,
583
+ child
584
+ );
585
+ return;
586
+ }
587
+ if (!(propName in child)) logPropertyWarning(propName, child);
588
+ child[propName] = child.__customCallbackFns.get(uniqueKey);
589
+ }
590
+ });
591
+ } else if (attrName.startsWith("prop-id:")) {
592
+ child.removeAttribute(attrName);
593
+ const propId = attrName.replace("prop-id:", "");
594
+ const propName = renderState.propertyMap.get(propId);
595
+ if (propName === void 0) {
596
+ console.error(
597
+ `BRANCH:PROP; Property ID "${propId}" does not exist in the property map. This is likely a problem with Thunderous. Report a bug if you see this message. https://github.com/Thunder-Solutions/Thunderous/issues`,
598
+ child
599
+ );
600
+ return;
601
+ }
602
+ if (!(propName in child)) logPropertyWarning(propName, child);
603
+ child[propName] = attr.value;
604
+ }
605
+ }
606
+ evaluateBindings(child, fragment);
607
+ }
608
+ }
609
+ };
610
+ var html = (strings, ...values) => {
611
+ let innerHTML = strings.reduce((innerHTML2, str, i) => {
612
+ let value = values[i] ?? "";
613
+ if (Array.isArray(value)) {
614
+ value = value.map((item) => processValue(item)).join("");
615
+ } else {
616
+ value = processValue(value);
617
+ }
618
+ innerHTML2 += str + String(value === null ? "" : value);
619
+ return innerHTML2;
620
+ }, "");
621
+ if (isServer) return innerHTML;
622
+ const props = innerHTML.match(/prop:([^=]+)/g);
623
+ if (props !== null) {
624
+ for (const prop of props) {
625
+ const name = prop.split(":")[1].trim();
626
+ const id = crypto.randomUUID();
627
+ const newProp = `prop-id:${id}`;
628
+ renderState.propertyMap.set(id, name);
629
+ innerHTML = innerHTML.replace(`prop:${name}`, newProp);
630
+ }
631
+ }
632
+ const template = document.createElement("template");
633
+ template.innerHTML = innerHTML;
634
+ const fragment = renderState.currentShadowRoot?.importNode?.(template.content, true) ?? document.importNode(template.content, true);
635
+ renderState.registry.upgrade(fragment);
636
+ evaluateBindings(fragment, fragment);
637
+ return fragment;
638
+ };
639
+ var adoptedStylesSupported = typeof window !== "undefined" && window.ShadowRoot?.prototype.hasOwnProperty("adoptedStyleSheets") && window.CSSStyleSheet?.prototype.hasOwnProperty("replace");
640
+ var isCSSStyleSheet = (stylesheet) => {
641
+ return typeof CSSStyleSheet !== "undefined" && stylesheet instanceof CSSStyleSheet;
642
+ };
643
+ var css = (strings, ...values) => {
644
+ let cssText = "";
645
+ const signalMap = /* @__PURE__ */ new Map();
646
+ const signalBindingRegex = /(\{\{signal:.+\}\})/;
647
+ strings.forEach((string, i) => {
648
+ let value = values[i] ?? "";
649
+ if (typeof value === "function") {
650
+ const uniqueKey = crypto.randomUUID();
651
+ signalMap.set(uniqueKey, value);
652
+ value = isServer ? value() : `{{signal:${uniqueKey}}}`;
653
+ }
654
+ if (typeof value === "object" && value !== null) {
655
+ console.error("Objects are not valid in CSS values. Received:", value);
656
+ value = "";
657
+ }
658
+ cssText += string + String(value);
659
+ });
660
+ if (isServer) {
661
+ return cssText;
662
+ }
663
+ const stylesheet = adoptedStylesSupported ? new CSSStyleSheet() : document.createElement("style");
664
+ const textList = cssText.split(signalBindingRegex);
665
+ createEffect(() => {
666
+ const newCSSTextList = [];
667
+ for (const text of textList) {
668
+ const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
669
+ const signal = uniqueKey !== text ? signalMap.get(uniqueKey) : null;
670
+ const newValue = signal !== null ? signal() : text;
671
+ const newText = String(newValue);
672
+ newCSSTextList.push(newText);
673
+ }
674
+ const newCSSText = newCSSTextList.join("");
675
+ if (isCSSStyleSheet(stylesheet)) {
676
+ stylesheet.replaceSync(newCSSText);
677
+ } else {
678
+ stylesheet.textContent = newCSSText;
679
+ }
680
+ });
681
+ return stylesheet;
682
+ };
683
+
684
+ // src/custom-element.ts
685
+ var ANY_BINDING_REGEX = /(\{\{.+:.+\}\})/;
686
+ var customElement = (render, options) => {
687
+ const _options = { ...DEFAULT_RENDER_OPTIONS, ...options };
688
+ const {
689
+ formAssociated,
690
+ observedAttributes: _observedAttributes,
691
+ attributesAsProperties,
692
+ attachShadow,
693
+ shadowRootOptions: _shadowRootOptions
694
+ } = _options;
695
+ const shadowRootOptions = { ...DEFAULT_RENDER_OPTIONS.shadowRootOptions, ..._shadowRootOptions };
696
+ const allOptions = { ..._options, shadowRootOptions };
697
+ if (isServer) {
698
+ const serverRender = render;
699
+ let _tagName2;
700
+ let _registry2;
701
+ const scopedRegistry = (() => {
702
+ if (shadowRootOptions.registry !== void 0 && "scoped" in shadowRootOptions.registry && shadowRootOptions.registry.scoped) {
703
+ return shadowRootOptions.registry;
704
+ }
705
+ return void 0;
706
+ })();
707
+ return {
708
+ define(tagName) {
709
+ _tagName2 = tagName;
710
+ serverDefine({
711
+ tagName,
712
+ serverRender,
713
+ options: allOptions,
714
+ scopedRegistry,
715
+ parentRegistry: _registry2,
716
+ elementResult: this
717
+ });
718
+ return this;
719
+ },
720
+ register(registry) {
721
+ if (_tagName2 !== void 0 && "eject" in registry && registry.scoped) {
722
+ console.error("Must call `register()` before `define()` for scoped registries.");
723
+ return this;
724
+ }
725
+ if ("eject" in registry) {
726
+ _registry2 = registry;
727
+ } else {
728
+ console.error("Registry must be created with `createRegistry()` for SSR.");
729
+ }
730
+ return this;
731
+ },
732
+ eject() {
733
+ const error = new Error("Cannot eject a custom element on the server.");
734
+ console.error(error);
735
+ throw error;
736
+ }
737
+ };
738
+ }
739
+ shadowRootOptions.registry = shadowRootOptions.customElements = shadowRootOptions.registry instanceof CustomElementRegistry ? shadowRootOptions.registry : shadowRootOptions.registry?.eject();
740
+ const observedAttributesSet = new Set(_observedAttributes);
741
+ const attributesAsPropertiesMap = /* @__PURE__ */ new Map();
742
+ for (const [attrName, coerce] of attributesAsProperties) {
743
+ observedAttributesSet.add(attrName);
744
+ attributesAsPropertiesMap.set(attrName, {
745
+ // convert kebab-case attribute names to camelCase property names
746
+ prop: attrName.replace(/(?<=-|_)([a-z])/g, (_, letter) => letter.toUpperCase()).replace(/(-|_)/g, ""),
747
+ coerce,
748
+ value: null
749
+ });
750
+ }
751
+ const observedAttributes = Array.from(observedAttributesSet);
752
+ class CustomElement extends HTMLElement {
753
+ #attributesAsPropertiesMap = new Map(attributesAsPropertiesMap);
754
+ #attrSignals = {};
755
+ #propSignals = {};
756
+ #attributeChangedFns = /* @__PURE__ */ new Set();
757
+ #connectedFns = /* @__PURE__ */ new Set();
758
+ #disconnectedFns = /* @__PURE__ */ new Set();
759
+ #adoptedCallbackFns = /* @__PURE__ */ new Set();
760
+ #formAssociatedCallbackFns = /* @__PURE__ */ new Set();
761
+ #formDisabledCallbackFns = /* @__PURE__ */ new Set();
762
+ #formResetCallbackFns = /* @__PURE__ */ new Set();
763
+ #formStateRestoreCallbackFns = /* @__PURE__ */ new Set();
764
+ #clientOnlyCallbackFns = /* @__PURE__ */ new Set();
765
+ #internals = this.attachInternals();
766
+ #shadowRoot = attachShadow ? this.#internals.shadowRoot ?? this.attachShadow(shadowRootOptions) : null;
767
+ #observer = options?.observedAttributes !== void 0 ? null : new MutationObserver((mutations) => {
768
+ for (const mutation of mutations) {
769
+ const attrName = mutation.attributeName;
770
+ if (mutation.type !== "attributes" || attrName === null) continue;
771
+ if (!(attrName in this.#attrSignals)) this.#attrSignals[attrName] = createSignal(null);
772
+ const [getter, setter] = this.#attrSignals[attrName];
773
+ const oldValue = getter();
774
+ const newValue = this.getAttribute(attrName);
775
+ setter(newValue);
776
+ for (const fn of this.#attributeChangedFns) {
777
+ fn(attrName, oldValue, newValue);
778
+ }
779
+ }
780
+ });
781
+ #getPropSignal = ((prop, { allowUndefined = false } = {}) => {
782
+ if (!(prop in this.#propSignals)) this.#propSignals[prop] = createSignal();
783
+ const [_getter, __setter] = this.#propSignals[prop];
784
+ let stackLength = 0;
785
+ const _setter = (newValue, options2) => {
786
+ stackLength++;
787
+ queueMicrotask(() => stackLength--);
788
+ if (stackLength > 999) {
789
+ console.error(
790
+ new Error(
791
+ `Property signal setter stack overflow detected. Possible infinite loop. Bailing out.
792
+
793
+ Property: ${prop}
794
+
795
+ New value: ${JSON.stringify(newValue, null, 2)}
796
+
797
+ Element: <${this.tagName.toLowerCase()}>
798
+ `
799
+ )
800
+ );
801
+ stackLength = 0;
802
+ return;
803
+ }
804
+ __setter(newValue, options2);
805
+ };
806
+ const descriptor = Object.getOwnPropertyDescriptor(this, prop);
807
+ if (descriptor === void 0) {
808
+ Object.defineProperty(this, prop, {
809
+ get: _getter,
810
+ set: _setter,
811
+ configurable: false,
812
+ enumerable: true
813
+ });
814
+ }
815
+ const setter = (newValue, options2) => {
816
+ this[prop] = newValue;
817
+ _setter(newValue, options2);
818
+ };
819
+ const getter = ((options2) => {
820
+ const value = _getter(options2);
821
+ if (value === void 0 && !allowUndefined) {
822
+ const error = new Error(
823
+ `Error accessing property: "${prop}"
824
+ You must set an initial value before calling a property signal's getter.
825
+ `
826
+ );
827
+ console.error(error);
828
+ throw error;
829
+ }
830
+ return value;
831
+ });
832
+ getter.getter = true;
833
+ const publicSignal = [getter, setter];
834
+ publicSignal.init = (value) => {
835
+ _setter(value);
836
+ return [getter, setter];
837
+ };
838
+ return publicSignal;
839
+ }).bind(this);
840
+ #render = (() => {
841
+ const root = this.#shadowRoot ?? this;
842
+ renderState.currentShadowRoot = this.#shadowRoot;
843
+ renderState.registry = shadowRootOptions.customElements ?? customElements;
844
+ const fragment = render({
845
+ elementRef: this,
846
+ root,
847
+ internals: this.#internals,
848
+ attributeChangedCallback: (fn) => this.#attributeChangedFns.add(fn),
849
+ connectedCallback: (fn) => this.#connectedFns.add(fn),
850
+ disconnectedCallback: (fn) => this.#disconnectedFns.add(fn),
851
+ adoptedCallback: (fn) => this.#adoptedCallbackFns.add(fn),
852
+ formAssociatedCallback: (fn) => this.#formAssociatedCallbackFns.add(fn),
853
+ formDisabledCallback: (fn) => this.#formDisabledCallbackFns.add(fn),
854
+ formResetCallback: (fn) => this.#formResetCallbackFns.add(fn),
855
+ formStateRestoreCallback: (fn) => this.#formStateRestoreCallbackFns.add(fn),
856
+ clientOnlyCallback: (fn) => this.#clientOnlyCallbackFns.add(fn),
857
+ getter: (fn) => {
858
+ const _fn = () => fn();
859
+ _fn.getter = true;
860
+ return _fn;
861
+ },
862
+ customCallback: (fn) => {
863
+ const key = crypto.randomUUID();
864
+ this.__customCallbackFns?.set(key, fn);
865
+ return `this.getRootNode().host.__customCallbackFns.get('${key}')(event)`;
866
+ },
867
+ attrSignals: new Proxy(
868
+ {},
869
+ {
870
+ get: (_, prop) => {
871
+ if (!(prop in this.#attrSignals)) this.#attrSignals[prop] = createSignal(null);
872
+ const [getter] = this.#attrSignals[prop];
873
+ const setter = (newValue) => this.setAttribute(prop, newValue);
874
+ return [getter, setter];
875
+ },
876
+ set: () => {
877
+ console.error("Signals must be assigned via setters.");
878
+ return false;
879
+ }
880
+ }
881
+ ),
882
+ propSignals: new Proxy({}, {
883
+ get: (_, prop) => this.#getPropSignal(prop),
884
+ set: () => {
885
+ console.error("Signals must be assigned via setters.");
886
+ return false;
887
+ }
888
+ }),
889
+ refs: new Proxy(
890
+ {},
891
+ {
892
+ get: (_, prop) => root.querySelector(`[ref=${prop}]`),
893
+ set: () => {
894
+ console.error("Refs are readonly and cannot be assigned.");
895
+ return false;
896
+ }
897
+ }
898
+ ),
899
+ adoptStyleSheet: (stylesheet) => {
900
+ if (!attachShadow) {
901
+ console.warn(
902
+ "Styles are only encapsulated when using shadow DOM. The stylesheet will be applied to the global document instead."
903
+ );
904
+ }
905
+ if (isCSSStyleSheet(stylesheet)) {
906
+ if (this.#shadowRoot === null) {
907
+ for (const rule of stylesheet.cssRules) {
908
+ if (rule instanceof CSSStyleRule && rule.selectorText.includes(":host")) {
909
+ console.error("Styles with :host are not supported when not using shadow DOM.");
910
+ }
911
+ }
912
+ document.adoptedStyleSheets.push(stylesheet);
913
+ return;
914
+ }
915
+ this.#shadowRoot.adoptedStyleSheets.push(stylesheet);
916
+ } else {
917
+ requestAnimationFrame(() => {
918
+ root.appendChild(stylesheet);
919
+ });
920
+ }
921
+ }
922
+ });
923
+ fragment.host = this;
924
+ for (const fn of this.#clientOnlyCallbackFns) {
925
+ fn();
926
+ }
927
+ root.replaceChildren(fragment);
928
+ renderState.currentShadowRoot = null;
929
+ renderState.registry = customElements;
930
+ }).bind(this);
931
+ static get formAssociated() {
932
+ return formAssociated;
933
+ }
934
+ static get observedAttributes() {
935
+ return observedAttributes;
936
+ }
937
+ constructor() {
938
+ try {
939
+ super();
940
+ if (!Object.prototype.hasOwnProperty.call(this, "__customCallbackFns")) {
941
+ this.__customCallbackFns = /* @__PURE__ */ new Map();
942
+ }
943
+ for (const attr of this.attributes) {
944
+ this.#attrSignals[attr.name] = createSignal(attr.value);
945
+ }
946
+ this.#render();
947
+ } catch (error) {
948
+ const _error = new Error(
949
+ "Error instantiating element:\nThis usually occurs if you have errors in the function body of your component. Check prior logs for possible causes.\n",
950
+ { cause: error }
951
+ );
952
+ console.error(_error);
953
+ throw _error;
954
+ }
955
+ }
956
+ connectedCallback() {
957
+ for (const [attrName, attr] of this.#attributesAsPropertiesMap) {
958
+ if (!(attrName in this.#attrSignals)) this.#attrSignals[attrName] = createSignal(null);
959
+ const propName = attr.prop;
960
+ const [getter] = this.#getPropSignal(propName, { allowUndefined: true });
961
+ let busy = false;
962
+ createEffect(() => {
963
+ if (busy) return;
964
+ busy = true;
965
+ const value = getter();
966
+ if (value === void 0) return;
967
+ if (value !== null && value !== false) {
968
+ this.setAttribute(attrName, String(value === true ? "" : value));
969
+ } else {
970
+ this.removeAttribute(attrName);
971
+ }
972
+ busy = false;
973
+ });
974
+ }
975
+ if (this.#observer !== null) {
976
+ this.#observer.observe(this, { attributes: true });
977
+ }
978
+ for (const fn of this.#connectedFns) {
979
+ fn();
980
+ }
981
+ }
982
+ disconnectedCallback() {
983
+ if (this.#observer !== null) {
984
+ this.#observer.disconnect();
985
+ }
986
+ for (const fn of this.#disconnectedFns) {
987
+ fn();
988
+ }
989
+ }
990
+ #attributesBusy = false;
991
+ attributeChangedCallback(name, oldValue, newValue) {
992
+ if (this.#attributesBusy || ANY_BINDING_REGEX.test(newValue ?? "")) return;
993
+ const [, attrSetter] = this.#attrSignals[name] ?? [];
994
+ attrSetter?.(newValue);
995
+ const prop = this.#attributesAsPropertiesMap.get(name);
996
+ if (prop !== void 0) {
997
+ const propName = prop.prop;
998
+ this.#attributesBusy = true;
999
+ const [, propSetter] = this.#getPropSignal(propName);
1000
+ if (prop.coerce === Boolean) {
1001
+ const bool = newValue !== null && newValue !== "false";
1002
+ const propValue = newValue === null ? null : bool;
1003
+ propSetter(propValue);
1004
+ } else {
1005
+ const propValue = newValue === null ? null : prop.coerce(newValue);
1006
+ propSetter(propValue);
1007
+ }
1008
+ this.#attributesBusy = false;
1009
+ }
1010
+ for (const fn of this.#attributeChangedFns) {
1011
+ fn(name, oldValue, newValue);
1012
+ }
1013
+ }
1014
+ adoptedCallback() {
1015
+ for (const fn of this.#adoptedCallbackFns) {
1016
+ fn();
1017
+ }
1018
+ }
1019
+ formAssociatedCallback() {
1020
+ for (const fn of this.#formAssociatedCallbackFns) {
1021
+ fn();
1022
+ }
1023
+ }
1024
+ formDisabledCallback() {
1025
+ for (const fn of this.#formDisabledCallbackFns) {
1026
+ fn();
1027
+ }
1028
+ }
1029
+ formResetCallback() {
1030
+ for (const fn of this.#formResetCallbackFns) {
1031
+ fn();
1032
+ }
1033
+ }
1034
+ formStateRestoreCallback() {
1035
+ for (const fn of this.#formStateRestoreCallbackFns) {
1036
+ fn();
1037
+ }
1038
+ }
1039
+ }
1040
+ let _tagName;
1041
+ let _registry;
1042
+ const elementResult = {
1043
+ define(tagName, options2) {
1044
+ const registry = _registry ?? customElements;
1045
+ const nativeRegistry = "eject" in registry ? registry.eject() : registry;
1046
+ if (nativeRegistry.get(tagName) !== void 0) {
1047
+ console.warn(`Custom element "${tagName}" was already defined. Skipping...`);
1048
+ return this;
1049
+ }
1050
+ registry.define(tagName, CustomElement, options2);
1051
+ _tagName = tagName;
1052
+ return this;
1053
+ },
1054
+ register(registry) {
1055
+ if (_tagName !== void 0 && "eject" in registry && registry.scoped) {
1056
+ console.error("Must call `register()` before `define()` for scoped registries.");
1057
+ return this;
1058
+ }
1059
+ _registry = registry;
1060
+ return this;
1061
+ },
1062
+ eject: () => CustomElement
1063
+ };
1064
+ return elementResult;
1065
+ };
1066
+
1067
+ // src/registry.ts
1068
+ var createRegistry = (args) => {
1069
+ const { scoped = false } = args ?? {};
1070
+ const customElementMap = /* @__PURE__ */ new Map();
1071
+ const elementResultMap = /* @__PURE__ */ new Map();
1072
+ const customElementTags = /* @__PURE__ */ new Set();
1073
+ const nativeRegistry = (() => {
1074
+ if (isServer) return;
1075
+ if (scoped) {
1076
+ try {
1077
+ return new CustomElementRegistry();
1078
+ } catch {
1079
+ console.error(
1080
+ "The scoped custom elements polyfill was not found. Falling back to global registry.\n\nCheck `RegistryResult.scoped` at https://thunderous.dev/docs/registries for more information."
1081
+ );
1082
+ }
1083
+ }
1084
+ return customElements;
1085
+ })();
1086
+ return {
1087
+ __serverCss: /* @__PURE__ */ new Map(),
1088
+ __serverRenderOpts: /* @__PURE__ */ new Map(),
1089
+ define(tagName, ElementResult, options) {
1090
+ const isResult = "eject" in ElementResult;
1091
+ const upperCaseTagName = tagName.toUpperCase();
1092
+ if (customElementTags.has(upperCaseTagName)) {
1093
+ console.warn(`Custom element tag name "${upperCaseTagName}" was already defined. Skipping...`);
1094
+ return this;
1095
+ }
1096
+ if (isResult) {
1097
+ if (elementResultMap.has(ElementResult)) {
1098
+ console.warn(`${upperCaseTagName} was already defined. Skipping...`);
1099
+ return this;
1100
+ }
1101
+ }
1102
+ if (!isServer) {
1103
+ const CustomElement2 = isResult ? ElementResult.eject() : ElementResult;
1104
+ if (customElementMap.has(CustomElement2)) {
1105
+ console.warn(`Custom element class "${CustomElement2.constructor.name}" was already defined. Skipping...`);
1106
+ return this;
1107
+ }
1108
+ customElementMap.set(CustomElement2, upperCaseTagName);
1109
+ }
1110
+ if (isResult) elementResultMap.set(ElementResult, upperCaseTagName);
1111
+ customElementTags.add(upperCaseTagName);
1112
+ if (isServer) {
1113
+ if (isResult) ElementResult.register(this).define(tagName, options);
1114
+ return this;
1115
+ }
1116
+ const CustomElement = isResult ? ElementResult.eject() : ElementResult;
1117
+ nativeRegistry?.define(tagName, CustomElement, options);
1118
+ return this;
1119
+ },
1120
+ getTagName: (ElementResult) => {
1121
+ const isResult = "eject" in ElementResult;
1122
+ if (isServer) {
1123
+ if (isResult) return elementResultMap.get(ElementResult);
1124
+ return;
1125
+ }
1126
+ const CustomElement = isResult ? ElementResult.eject() : ElementResult;
1127
+ return customElementMap.get(CustomElement);
1128
+ },
1129
+ getAllTagNames: () => Array.from(customElementTags),
1130
+ eject: () => {
1131
+ if (nativeRegistry === void 0) {
1132
+ const error = new Error("Cannot eject a registry on the server.");
1133
+ console.error(error);
1134
+ throw error;
1135
+ }
1136
+ return nativeRegistry;
1137
+ },
1138
+ scoped
1139
+ };
1140
+ };
1141
+ export {
1142
+ clientOnlyCallback,
1143
+ createEffect,
1144
+ createRegistry,
1145
+ createSignal,
1146
+ css,
1147
+ customElement,
1148
+ derived,
1149
+ html,
1150
+ insertTemplates,
1151
+ onServerDefine
1152
+ };