thunderous 2.4.1 → 2.4.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/dist/index.cjs +23 -25
- package/dist/index.d.cts +1 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.js +23 -25
- package/package.json +54 -69
- package/dist/__test__/custom-element.test.js +0 -48
- package/dist/__test__/registry.test.js +0 -60
- package/dist/__test__/render.test.js +0 -48
- package/dist/__test__/server-side.test.js +0 -196
- package/dist/__test__/signals.test.js +0 -247
- package/dist/__test__/utilities.test.js +0 -8
- package/dist/constants.js +0 -13
- package/dist/custom-element.js +0 -391
- package/dist/registry.js +0 -95
- package/dist/render.js +0 -350
- package/dist/server-side.js +0 -136
- package/dist/signals.js +0 -115
- package/dist/utilities.js +0 -9
package/dist/render.js
DELETED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
import { isServer } from './server-side';
|
|
2
|
-
import { createEffect } from './signals';
|
|
3
|
-
import { queryComment } from './utilities';
|
|
4
|
-
const CALLBACK_BINDING_REGEX = /(\{\{callback:.+\}\})/;
|
|
5
|
-
const LEGACY_CALLBACK_BINDING_REGEX = /(this.getRootNode\(\).host.__customCallbackFns.get\('.+'\)\(event\))/;
|
|
6
|
-
const SIGNAL_BINDING_REGEX = /(\{\{signal:.+\}\})/;
|
|
7
|
-
const FRAGMENT_ATTRIBUTE = '___thunderous-fragment';
|
|
8
|
-
export const renderState = {
|
|
9
|
-
currentShadowRoot: null,
|
|
10
|
-
signalMap: new Map(),
|
|
11
|
-
callbackMap: new Map(),
|
|
12
|
-
fragmentMap: new Map(),
|
|
13
|
-
propertyMap: new Map(),
|
|
14
|
-
registry: typeof customElements !== 'undefined' ? customElements : {},
|
|
15
|
-
};
|
|
16
|
-
const logPropertyWarning = (propName, element) => {
|
|
17
|
-
console.warn(`Property "${propName}" does not exist on element:`, element, '\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.');
|
|
18
|
-
};
|
|
19
|
-
// For nested loops in templating logic...
|
|
20
|
-
const arrayToDocumentFragment = (array, parent, uniqueKey) => {
|
|
21
|
-
const documentFragment = new DocumentFragment();
|
|
22
|
-
let count = 0;
|
|
23
|
-
const keys = new Set();
|
|
24
|
-
for (const item of array) {
|
|
25
|
-
const node = createNewNode(item, parent, uniqueKey);
|
|
26
|
-
if (node instanceof DocumentFragment) {
|
|
27
|
-
const child = node.firstElementChild;
|
|
28
|
-
if (node.children.length > 1) {
|
|
29
|
-
console.error('When rendering arrays, fragments must contain only one top-level element at a time. Error occured in:', parent);
|
|
30
|
-
}
|
|
31
|
-
if (child === null)
|
|
32
|
-
continue;
|
|
33
|
-
let key = child.getAttribute('key');
|
|
34
|
-
if (key === null) {
|
|
35
|
-
console.warn('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:', child);
|
|
36
|
-
key = String(count);
|
|
37
|
-
child.setAttribute('key', key);
|
|
38
|
-
}
|
|
39
|
-
if (keys.has(key)) {
|
|
40
|
-
console.warn(`When rendering arrays, each child should have a unique \`key\` attribute. Duplicate key "${key}" found on:`, child);
|
|
41
|
-
}
|
|
42
|
-
keys.add(key);
|
|
43
|
-
count++;
|
|
44
|
-
}
|
|
45
|
-
documentFragment.append(node);
|
|
46
|
-
}
|
|
47
|
-
const comment = document.createComment(uniqueKey);
|
|
48
|
-
documentFragment.append(comment);
|
|
49
|
-
return documentFragment;
|
|
50
|
-
};
|
|
51
|
-
const createNewNode = (value, parent, uniqueKey) => {
|
|
52
|
-
if (typeof value === 'string')
|
|
53
|
-
return new Text(value);
|
|
54
|
-
if (Array.isArray(value))
|
|
55
|
-
return arrayToDocumentFragment(value, parent, uniqueKey);
|
|
56
|
-
if (value instanceof DocumentFragment)
|
|
57
|
-
return value;
|
|
58
|
-
return new Text('');
|
|
59
|
-
};
|
|
60
|
-
// Handle each interpolated value and convert it to a string.
|
|
61
|
-
// Binding is done only after the combined HTML string is parsed into a DocumentFragment.
|
|
62
|
-
const processValue = (value) => {
|
|
63
|
-
if (!isServer && value instanceof DocumentFragment) {
|
|
64
|
-
const uniqueKey = crypto.randomUUID();
|
|
65
|
-
renderState.fragmentMap.set(uniqueKey, value);
|
|
66
|
-
return `<div ${FRAGMENT_ATTRIBUTE}="${uniqueKey}"></div>`;
|
|
67
|
-
}
|
|
68
|
-
if (typeof value === 'function' && 'getter' in value && value.getter === true) {
|
|
69
|
-
const getter = value;
|
|
70
|
-
const uniqueKey = crypto.randomUUID();
|
|
71
|
-
renderState.signalMap.set(uniqueKey, getter);
|
|
72
|
-
let result = getter();
|
|
73
|
-
if (Array.isArray(result)) {
|
|
74
|
-
result = result.map((item) => processValue(item)).join('');
|
|
75
|
-
}
|
|
76
|
-
return isServer ? String(result) : `{{signal:${uniqueKey}}}`;
|
|
77
|
-
}
|
|
78
|
-
if (typeof value === 'function') {
|
|
79
|
-
const uniqueKey = crypto.randomUUID();
|
|
80
|
-
renderState.callbackMap.set(uniqueKey, value);
|
|
81
|
-
return isServer ? String(value()) : `{{callback:${uniqueKey}}}`;
|
|
82
|
-
}
|
|
83
|
-
return String(value);
|
|
84
|
-
};
|
|
85
|
-
// Bind signals and callbacks to DOM nodes in a DocumentFragment.
|
|
86
|
-
const evaluateBindings = (element, fragment) => {
|
|
87
|
-
for (const child of element.childNodes) {
|
|
88
|
-
if (child instanceof Text && SIGNAL_BINDING_REGEX.test(child.data)) {
|
|
89
|
-
const textList = child.data.split(SIGNAL_BINDING_REGEX);
|
|
90
|
-
const sibling = child.nextSibling;
|
|
91
|
-
textList.forEach((text, i) => {
|
|
92
|
-
const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, '$1');
|
|
93
|
-
const signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : undefined;
|
|
94
|
-
const newValue = signal !== undefined ? signal() : text;
|
|
95
|
-
const newNode = createNewNode(newValue, element, uniqueKey);
|
|
96
|
-
// there is only one text node, originally, so we have to replace it before inserting additional nodes
|
|
97
|
-
if (i === 0) {
|
|
98
|
-
child.replaceWith(newNode);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
element.insertBefore(newNode, sibling);
|
|
102
|
-
}
|
|
103
|
-
// evaluate signals and subscribe to them
|
|
104
|
-
if (signal !== undefined && newNode instanceof Text) {
|
|
105
|
-
createEffect(() => {
|
|
106
|
-
newNode.data = signal();
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
else if (signal !== undefined && newNode instanceof DocumentFragment) {
|
|
110
|
-
let init = false;
|
|
111
|
-
createEffect(() => {
|
|
112
|
-
const result = signal();
|
|
113
|
-
const nextNode = createNewNode(result, element, uniqueKey);
|
|
114
|
-
if (nextNode instanceof Text) {
|
|
115
|
-
const error = new TypeError('Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text');
|
|
116
|
-
console.error(error);
|
|
117
|
-
throw error;
|
|
118
|
-
}
|
|
119
|
-
// remove elements that are not in the updated array (fragment)
|
|
120
|
-
for (const child of element.children) {
|
|
121
|
-
const key = child.getAttribute('key');
|
|
122
|
-
if (key === null)
|
|
123
|
-
continue;
|
|
124
|
-
const matchingNode = nextNode.querySelector(`[key="${key}"]`);
|
|
125
|
-
if (init && matchingNode === null) {
|
|
126
|
-
child.remove();
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
// persist elements using the same key
|
|
130
|
-
let anchor = queryComment(element, uniqueKey);
|
|
131
|
-
for (const child of nextNode.children) {
|
|
132
|
-
const key = child.getAttribute('key');
|
|
133
|
-
const matchingNode = element.querySelector(`[key="${key}"]`);
|
|
134
|
-
if (matchingNode === null)
|
|
135
|
-
continue;
|
|
136
|
-
matchingNode.__customCallbackFns = child.__customCallbackFns;
|
|
137
|
-
for (const attr of child.attributes) {
|
|
138
|
-
matchingNode.setAttribute(attr.name, attr.value);
|
|
139
|
-
}
|
|
140
|
-
matchingNode.replaceChildren(...child.childNodes);
|
|
141
|
-
anchor = matchingNode.nextSibling;
|
|
142
|
-
child.replaceWith(matchingNode);
|
|
143
|
-
}
|
|
144
|
-
const nextAnchor = queryComment(nextNode, uniqueKey);
|
|
145
|
-
nextAnchor?.remove();
|
|
146
|
-
element.insertBefore(nextNode, anchor);
|
|
147
|
-
if (!init)
|
|
148
|
-
init = true;
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
if (child instanceof Element && child.hasAttribute(FRAGMENT_ATTRIBUTE)) {
|
|
154
|
-
const uniqueKey = child.getAttribute(FRAGMENT_ATTRIBUTE);
|
|
155
|
-
const childFragment = renderState.fragmentMap.get(uniqueKey);
|
|
156
|
-
if (childFragment !== undefined) {
|
|
157
|
-
child.replaceWith(childFragment);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else if (child instanceof Element) {
|
|
161
|
-
for (const attr of [...child.attributes]) {
|
|
162
|
-
const attrName = attr.name;
|
|
163
|
-
if (SIGNAL_BINDING_REGEX.test(attr.value)) {
|
|
164
|
-
const textList = attr.value.split(SIGNAL_BINDING_REGEX);
|
|
165
|
-
let prevText = attr.value;
|
|
166
|
-
createEffect(() => {
|
|
167
|
-
let newText = '';
|
|
168
|
-
let hasNull = false;
|
|
169
|
-
let signal;
|
|
170
|
-
for (const text of textList) {
|
|
171
|
-
const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, '$1');
|
|
172
|
-
if (signal === undefined) {
|
|
173
|
-
signal = uniqueKey !== text ? renderState.signalMap.get(uniqueKey) : undefined;
|
|
174
|
-
const value = signal !== undefined ? signal() : text;
|
|
175
|
-
if (value === null)
|
|
176
|
-
hasNull = true;
|
|
177
|
-
newText += String(value);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
newText += text;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if ((hasNull && newText === 'null') || attrName.startsWith('prop-id:')) {
|
|
184
|
-
if (child.hasAttribute(attrName))
|
|
185
|
-
child.removeAttribute(attrName);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
if (newText !== prevText)
|
|
189
|
-
child.setAttribute(attrName, newText);
|
|
190
|
-
}
|
|
191
|
-
if (attrName.startsWith('prop-id:')) {
|
|
192
|
-
if (child.hasAttribute(attrName))
|
|
193
|
-
child.removeAttribute(attrName);
|
|
194
|
-
const propId = attrName.replace('prop-id:', '');
|
|
195
|
-
const propName = renderState.propertyMap.get(propId);
|
|
196
|
-
if (propName === undefined) {
|
|
197
|
-
console.error(`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`, child);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const newValue = hasNull && newText === 'null' ? null : newText;
|
|
201
|
-
if (!(propName in child))
|
|
202
|
-
logPropertyWarning(propName, child);
|
|
203
|
-
// @ts-expect-error // the above warning should suffice for developers
|
|
204
|
-
child[propName] = signal !== undefined ? signal() : newValue;
|
|
205
|
-
}
|
|
206
|
-
prevText = newText;
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
else if (LEGACY_CALLBACK_BINDING_REGEX.test(attr.value)) {
|
|
210
|
-
const getRootNode = child.getRootNode.bind(child);
|
|
211
|
-
child.getRootNode = () => {
|
|
212
|
-
const rootNode = getRootNode();
|
|
213
|
-
return rootNode instanceof ShadowRoot ? rootNode : fragment;
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
else if (CALLBACK_BINDING_REGEX.test(attr.value)) {
|
|
217
|
-
const textList = attr.value.split(CALLBACK_BINDING_REGEX);
|
|
218
|
-
createEffect(() => {
|
|
219
|
-
child.__customCallbackFns = child.__customCallbackFns ?? new Map();
|
|
220
|
-
let uniqueKey = '';
|
|
221
|
-
for (const text of textList) {
|
|
222
|
-
const _uniqueKey = text.replace(/\{\{callback:(.+)\}\}/, '$1');
|
|
223
|
-
if (_uniqueKey !== text)
|
|
224
|
-
uniqueKey = _uniqueKey;
|
|
225
|
-
const callback = uniqueKey !== text ? renderState.callbackMap.get(uniqueKey) : undefined;
|
|
226
|
-
if (callback !== undefined) {
|
|
227
|
-
child.__customCallbackFns.set(uniqueKey, callback);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (uniqueKey !== '' && !attrName.startsWith('prop-id:')) {
|
|
231
|
-
child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
|
|
232
|
-
}
|
|
233
|
-
else if (attrName.startsWith('prop-id:')) {
|
|
234
|
-
child.removeAttribute(attrName);
|
|
235
|
-
const propId = attrName.replace('prop-id:', '');
|
|
236
|
-
const propName = renderState.propertyMap.get(propId);
|
|
237
|
-
if (propName === undefined) {
|
|
238
|
-
console.error(`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`, child);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
// @ts-expect-error // the above warning should suffice for developers
|
|
242
|
-
child[propName] = child.__customCallbackFns.get(uniqueKey);
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
else if (attrName.startsWith('prop-id:')) {
|
|
247
|
-
child.removeAttribute(attrName);
|
|
248
|
-
const propId = attrName.replace('prop-id:', '');
|
|
249
|
-
const propName = renderState.propertyMap.get(propId);
|
|
250
|
-
if (propName === undefined) {
|
|
251
|
-
console.error(`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`, child);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
// @ts-expect-error // the above warning should suffice for developers
|
|
255
|
-
child[propName] = attr.value;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
evaluateBindings(child, fragment);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
/**
|
|
263
|
-
* A tagged template function for creating DocumentFragment instances.
|
|
264
|
-
*/
|
|
265
|
-
export const html = (strings, ...values) => {
|
|
266
|
-
// Combine the strings and values into a single HTML string
|
|
267
|
-
let innerHTML = strings.reduce((innerHTML, str, i) => {
|
|
268
|
-
let value = values[i] ?? '';
|
|
269
|
-
if (Array.isArray(value)) {
|
|
270
|
-
value = value.map((item) => processValue(item)).join('');
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
value = processValue(value);
|
|
274
|
-
}
|
|
275
|
-
innerHTML += str + String(value === null ? '' : value);
|
|
276
|
-
return innerHTML;
|
|
277
|
-
}, '');
|
|
278
|
-
// @ts-expect-error // return a plain string for server-side rendering
|
|
279
|
-
if (isServer)
|
|
280
|
-
return innerHTML;
|
|
281
|
-
// Track properties in the render state while the HTML is still a string,
|
|
282
|
-
// since everything converts to lowercase after parsing as a DOM fragment.
|
|
283
|
-
const props = innerHTML.match(/prop:([^=]+)/g);
|
|
284
|
-
if (props !== null) {
|
|
285
|
-
for (const prop of props) {
|
|
286
|
-
const name = prop.split(':')[1].trim();
|
|
287
|
-
const id = crypto.randomUUID();
|
|
288
|
-
const newProp = `prop-id:${id}`;
|
|
289
|
-
renderState.propertyMap.set(id, name);
|
|
290
|
-
innerHTML = innerHTML.replace(`prop:${name}`, newProp);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
// Parse the HTML string into a DocumentFragment
|
|
294
|
-
const template = document.createElement('template');
|
|
295
|
-
template.innerHTML = innerHTML;
|
|
296
|
-
const fragment = renderState.currentShadowRoot?.importNode?.(template.content, true) ?? document.importNode(template.content, true);
|
|
297
|
-
// Ensure the DocumentFragment is upgraded before binding to properties
|
|
298
|
-
renderState.registry.upgrade(fragment);
|
|
299
|
-
// Bind signals and callbacks to the DocumentFragment
|
|
300
|
-
evaluateBindings(fragment, fragment);
|
|
301
|
-
return fragment;
|
|
302
|
-
};
|
|
303
|
-
const adoptedStylesSupported = typeof window !== 'undefined' &&
|
|
304
|
-
window.ShadowRoot?.prototype.hasOwnProperty('adoptedStyleSheets') &&
|
|
305
|
-
window.CSSStyleSheet?.prototype.hasOwnProperty('replace');
|
|
306
|
-
export const isCSSStyleSheet = (stylesheet) => {
|
|
307
|
-
return typeof CSSStyleSheet !== 'undefined' && stylesheet instanceof CSSStyleSheet;
|
|
308
|
-
};
|
|
309
|
-
export const css = (strings, ...values) => {
|
|
310
|
-
let cssText = '';
|
|
311
|
-
const signalMap = new Map();
|
|
312
|
-
const signalBindingRegex = /(\{\{signal:.+\}\})/;
|
|
313
|
-
strings.forEach((string, i) => {
|
|
314
|
-
let value = values[i] ?? '';
|
|
315
|
-
if (typeof value === 'function') {
|
|
316
|
-
const uniqueKey = crypto.randomUUID();
|
|
317
|
-
signalMap.set(uniqueKey, value);
|
|
318
|
-
value = isServer ? value() : `{{signal:${uniqueKey}}}`;
|
|
319
|
-
}
|
|
320
|
-
if (typeof value === 'object' && value !== null) {
|
|
321
|
-
console.error('Objects are not valid in CSS values. Received:', value);
|
|
322
|
-
value = '';
|
|
323
|
-
}
|
|
324
|
-
cssText += string + String(value);
|
|
325
|
-
});
|
|
326
|
-
if (isServer) {
|
|
327
|
-
// @ts-expect-error // return a plain string for server-side rendering
|
|
328
|
-
return cssText;
|
|
329
|
-
}
|
|
330
|
-
const stylesheet = adoptedStylesSupported ? new CSSStyleSheet() : document.createElement('style');
|
|
331
|
-
const textList = cssText.split(signalBindingRegex);
|
|
332
|
-
createEffect(() => {
|
|
333
|
-
const newCSSTextList = [];
|
|
334
|
-
for (const text of textList) {
|
|
335
|
-
const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, '$1');
|
|
336
|
-
const signal = uniqueKey !== text ? signalMap.get(uniqueKey) : null;
|
|
337
|
-
const newValue = signal !== null ? signal() : text;
|
|
338
|
-
const newText = String(newValue);
|
|
339
|
-
newCSSTextList.push(newText);
|
|
340
|
-
}
|
|
341
|
-
const newCSSText = newCSSTextList.join('');
|
|
342
|
-
if (isCSSStyleSheet(stylesheet)) {
|
|
343
|
-
stylesheet.replace(newCSSText).catch(console.error);
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
stylesheet.textContent = newCSSText;
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
return stylesheet;
|
|
350
|
-
};
|
package/dist/server-side.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { createSignal } from './signals';
|
|
2
|
-
import { NOOP } from './utilities';
|
|
3
|
-
export const isServer = typeof window === 'undefined';
|
|
4
|
-
export const serverDefineFns = new Set();
|
|
5
|
-
export const onServerDefine = (fn) => {
|
|
6
|
-
serverDefineFns.add(fn);
|
|
7
|
-
};
|
|
8
|
-
export const serverDefine = ({ tagName, serverRender, options, elementResult, scopedRegistry, parentRegistry, }) => {
|
|
9
|
-
if (parentRegistry !== undefined) {
|
|
10
|
-
if (parentRegistry.getTagName(elementResult) !== tagName.toUpperCase()) {
|
|
11
|
-
parentRegistry.define(tagName, elementResult);
|
|
12
|
-
}
|
|
13
|
-
parentRegistry.__serverRenderOpts.set(tagName, { serverRender, ...options });
|
|
14
|
-
if (parentRegistry.scoped)
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
for (const fn of serverDefineFns) {
|
|
18
|
-
let result = serverRender(getServerRenderArgs(tagName));
|
|
19
|
-
result = wrapTemplate({
|
|
20
|
-
tagName,
|
|
21
|
-
serverRender,
|
|
22
|
-
options,
|
|
23
|
-
});
|
|
24
|
-
if (scopedRegistry !== undefined) {
|
|
25
|
-
for (const [scopedTagName, scopedRenderOptions] of scopedRegistry.__serverRenderOpts) {
|
|
26
|
-
const { serverRender, ...scopedOptions } = scopedRenderOptions;
|
|
27
|
-
let template = serverRender(getServerRenderArgs(scopedTagName, scopedRegistry));
|
|
28
|
-
template = wrapTemplate({
|
|
29
|
-
tagName: scopedTagName,
|
|
30
|
-
serverRender,
|
|
31
|
-
options: scopedOptions,
|
|
32
|
-
});
|
|
33
|
-
result = insertTemplates(scopedTagName, template, result);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
fn(tagName, result);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
export const serverCss = new Map();
|
|
40
|
-
export const getServerRenderArgs = (tagName, registry) => ({
|
|
41
|
-
get elementRef() {
|
|
42
|
-
return new Proxy({}, {
|
|
43
|
-
get: () => {
|
|
44
|
-
const error = new Error('The `elementRef` property is not available on the server.');
|
|
45
|
-
console.error(error);
|
|
46
|
-
throw error;
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
},
|
|
50
|
-
get root() {
|
|
51
|
-
return new Proxy({}, {
|
|
52
|
-
get: () => {
|
|
53
|
-
const error = new Error('The `root` property is not available on the server.');
|
|
54
|
-
console.error(error);
|
|
55
|
-
throw error;
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
},
|
|
59
|
-
get internals() {
|
|
60
|
-
return new Proxy({}, {
|
|
61
|
-
get: () => {
|
|
62
|
-
const error = new Error('The `internals` property is not available on the server.');
|
|
63
|
-
console.error(error);
|
|
64
|
-
throw error;
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
},
|
|
68
|
-
attributeChangedCallback: NOOP,
|
|
69
|
-
connectedCallback: NOOP,
|
|
70
|
-
disconnectedCallback: NOOP,
|
|
71
|
-
adoptedCallback: NOOP,
|
|
72
|
-
formDisabledCallback: NOOP,
|
|
73
|
-
formResetCallback: NOOP,
|
|
74
|
-
formStateRestoreCallback: NOOP,
|
|
75
|
-
formAssociatedCallback: NOOP,
|
|
76
|
-
clientOnlyCallback: NOOP,
|
|
77
|
-
customCallback: () => '',
|
|
78
|
-
getter: (fn) => {
|
|
79
|
-
const _fn = () => fn();
|
|
80
|
-
_fn.getter = true;
|
|
81
|
-
return _fn;
|
|
82
|
-
},
|
|
83
|
-
attrSignals: new Proxy({}, { get: (_, attr) => createSignal(`{{attr:${String(attr)}}}`) }),
|
|
84
|
-
propSignals: new Proxy({}, { get: () => createSignal(null) }),
|
|
85
|
-
refs: {},
|
|
86
|
-
// @ts-expect-error // this will be a string for SSR, but this is true for internal cases only.
|
|
87
|
-
// The end user will see the public type, which is either a CSSStyleSheet or HTMLStyleElement.
|
|
88
|
-
adoptStyleSheet: (cssStr) => {
|
|
89
|
-
const _serverCss = registry !== undefined ? registry.__serverCss : serverCss;
|
|
90
|
-
const cssArr = _serverCss.get(tagName) ?? [];
|
|
91
|
-
cssArr.push(cssStr);
|
|
92
|
-
_serverCss.set(tagName, cssArr);
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
export const wrapTemplate = ({ tagName, serverRender, options }) => {
|
|
96
|
-
const { registry } = options.shadowRootOptions;
|
|
97
|
-
const scopedRegistry = registry !== undefined && 'scoped' in registry ? registry : undefined;
|
|
98
|
-
const initialRenderString = serverRender(getServerRenderArgs(tagName, scopedRegistry));
|
|
99
|
-
const _serverCss = scopedRegistry?.__serverCss ?? serverCss;
|
|
100
|
-
const cssRenderString = (_serverCss.get(tagName) ?? []).map((cssStr) => `<style>${cssStr}</style>`).join('');
|
|
101
|
-
const finalScopedRenderString = options.attachShadow
|
|
102
|
-
? /* html */ `
|
|
103
|
-
<template
|
|
104
|
-
shadowrootmode="${options.shadowRootOptions.mode}"
|
|
105
|
-
shadowrootdelegatesfocus="${options.shadowRootOptions.delegatesFocus}"
|
|
106
|
-
shadowrootclonable="${options.shadowRootOptions.clonable}"
|
|
107
|
-
shadowrootserializable="${options.shadowRootOptions.serializable}"
|
|
108
|
-
>
|
|
109
|
-
${cssRenderString + initialRenderString}
|
|
110
|
-
</template>
|
|
111
|
-
`
|
|
112
|
-
: cssRenderString + initialRenderString;
|
|
113
|
-
return finalScopedRenderString;
|
|
114
|
-
};
|
|
115
|
-
export const insertTemplates = (tagName, template, inputString) => {
|
|
116
|
-
return inputString.replace(new RegExp(`(<\s*${tagName}([^>]*)>)`, 'gm'), ($1, _, $3) => {
|
|
117
|
-
const attrs = $3
|
|
118
|
-
.split(/(?<=")\s+/)
|
|
119
|
-
.filter((attr) => attr.trim() !== '')
|
|
120
|
-
.map((attr) => {
|
|
121
|
-
const [_key, _value] = attr.split('=');
|
|
122
|
-
const key = _key.trim();
|
|
123
|
-
const value = _value?.replace(/"/g, '') ?? '';
|
|
124
|
-
return [key, value];
|
|
125
|
-
});
|
|
126
|
-
let scopedResult = template;
|
|
127
|
-
for (const [key, value] of attrs) {
|
|
128
|
-
scopedResult = scopedResult.replace(new RegExp(`{{attr:${key}}}`, 'gm'), value);
|
|
129
|
-
}
|
|
130
|
-
return $1 + scopedResult;
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
export const clientOnlyCallback = (fn) => {
|
|
134
|
-
if (!isServer)
|
|
135
|
-
return fn();
|
|
136
|
-
};
|
package/dist/signals.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
let subscriber = null;
|
|
2
|
-
/**
|
|
3
|
-
* Create a signal with an initial value.
|
|
4
|
-
* @example
|
|
5
|
-
* ```ts
|
|
6
|
-
* const [getCount, setCount] = createSignal(0);
|
|
7
|
-
* const increment = () => setCount(getCount() + 1);
|
|
8
|
-
* const decrement = () => setCount(getCount() - 1);
|
|
9
|
-
* ```
|
|
10
|
-
*/
|
|
11
|
-
export const createSignal = (initVal, options) => {
|
|
12
|
-
const subscribers = new Set();
|
|
13
|
-
let value = initVal;
|
|
14
|
-
const getter = (getterOptions) => {
|
|
15
|
-
if (subscriber !== null) {
|
|
16
|
-
subscribers.add(subscriber);
|
|
17
|
-
}
|
|
18
|
-
if (options?.debugMode === true || getterOptions?.debugMode === true) {
|
|
19
|
-
let label = 'anonymous signal';
|
|
20
|
-
if (options?.label !== undefined) {
|
|
21
|
-
label = `(${options.label})`;
|
|
22
|
-
if (getterOptions?.label !== undefined) {
|
|
23
|
-
label += ` ${getterOptions.label}`;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
else if (getterOptions?.label !== undefined) {
|
|
27
|
-
label = getterOptions.label;
|
|
28
|
-
}
|
|
29
|
-
console.log('Signal retrieved:', { value, subscribers, label });
|
|
30
|
-
}
|
|
31
|
-
return value;
|
|
32
|
-
};
|
|
33
|
-
getter.getter = true;
|
|
34
|
-
let stackLength = 0;
|
|
35
|
-
const setter = (newValue, setterOptions) => {
|
|
36
|
-
stackLength++;
|
|
37
|
-
queueMicrotask(() => stackLength--);
|
|
38
|
-
if (stackLength > 1000) {
|
|
39
|
-
console.error(new Error('Signal setter stack overflow detected. Possible infinite loop. Bailing out.'));
|
|
40
|
-
stackLength = 0;
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const isObject = typeof newValue === 'object' && newValue !== null;
|
|
44
|
-
if (!isObject && value === newValue)
|
|
45
|
-
return;
|
|
46
|
-
if (isObject && typeof value === 'object' && value !== null) {
|
|
47
|
-
if (JSON.stringify(value) === JSON.stringify(newValue))
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const oldValue = value;
|
|
51
|
-
value = newValue;
|
|
52
|
-
for (const fn of subscribers) {
|
|
53
|
-
try {
|
|
54
|
-
fn();
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
console.error('Error in subscriber:', { error, oldValue, newValue, fn });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (options?.debugMode === true || setterOptions?.debugMode === true) {
|
|
61
|
-
let label = 'anonymous signal';
|
|
62
|
-
if (options?.label !== undefined) {
|
|
63
|
-
label = `(${options.label})`;
|
|
64
|
-
if (setterOptions?.label !== undefined) {
|
|
65
|
-
label += ` ${setterOptions.label}`;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
else if (setterOptions?.label !== undefined) {
|
|
69
|
-
label = setterOptions.label;
|
|
70
|
-
}
|
|
71
|
-
console.log('Signal set:', { oldValue, newValue, subscribers, label });
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
return [getter, setter];
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* Create a derived signal that depends on other signals.
|
|
78
|
-
* @example
|
|
79
|
-
* ```ts
|
|
80
|
-
* const [getCount, setCount] = createSignal(0);
|
|
81
|
-
* const doubleCount = derived(() => getCount() * 2);
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
export const derived = (fn, options) => {
|
|
85
|
-
const [getter, setter] = createSignal(undefined, options);
|
|
86
|
-
createEffect(() => {
|
|
87
|
-
try {
|
|
88
|
-
setter(fn());
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
console.error('Error in derived signal:', { error, fn });
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
return getter;
|
|
95
|
-
};
|
|
96
|
-
/**
|
|
97
|
-
* Create an effect that runs when signals change.
|
|
98
|
-
* @example
|
|
99
|
-
* ```ts
|
|
100
|
-
* const [getCount, setCount] = createSignal(0);
|
|
101
|
-
* createEffect(() => {
|
|
102
|
-
* console.log('Count:', getCount());
|
|
103
|
-
* });
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
|
-
export const createEffect = (fn) => {
|
|
107
|
-
subscriber = fn;
|
|
108
|
-
try {
|
|
109
|
-
fn();
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
console.error('Error in effect:', { error, fn });
|
|
113
|
-
}
|
|
114
|
-
subscriber = null;
|
|
115
|
-
};
|
package/dist/utilities.js
DELETED