@rettangoli/fe 0.0.14 → 1.0.0-rc3
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/README.md +94 -9
- package/package.json +9 -3
- package/src/cli/blank/blank.handlers.js +5 -2
- package/src/cli/blank/blank.schema.yaml +13 -0
- package/src/cli/blank/blank.view.yaml +0 -11
- package/src/cli/build.js +56 -24
- package/src/cli/check.js +53 -0
- package/src/cli/contracts.js +149 -0
- package/src/cli/index.js +5 -11
- package/src/cli/scaffold.js +6 -0
- package/src/cli/watch.js +3 -2
- package/src/core/contracts/componentFiles.js +119 -0
- package/src/core/runtime/componentOrchestrator.js +158 -0
- package/src/core/runtime/componentRuntime.js +54 -0
- package/src/core/runtime/constants.js +27 -0
- package/src/core/runtime/events.js +198 -0
- package/src/core/runtime/globalListeners.js +87 -0
- package/src/core/runtime/lifecycle.js +124 -0
- package/src/core/runtime/methods.js +40 -0
- package/src/core/runtime/payload.js +3 -0
- package/src/core/runtime/props.js +79 -0
- package/src/core/runtime/refs.js +70 -0
- package/src/core/runtime/store.js +42 -0
- package/src/core/schema/validateSchemaContract.js +46 -0
- package/src/core/style/yamlToCss.js +44 -0
- package/src/core/view/bindings.js +198 -0
- package/src/core/view/refs.js +234 -0
- package/src/createComponent.js +42 -527
- package/src/index.js +0 -2
- package/src/parser.js +101 -277
- package/src/web/componentDom.js +49 -0
- package/src/web/componentUpdateHook.js +43 -0
- package/src/web/createWebComponentClass.js +151 -0
- package/src/web/scheduler.js +6 -0
package/src/parser.js
CHANGED
|
@@ -1,65 +1,25 @@
|
|
|
1
|
-
import { render as jemplRender } from
|
|
1
|
+
import { parseAndRender as jemplParseAndRender, render as jemplRender } from "jempl";
|
|
2
2
|
|
|
3
3
|
import { flattenArrays } from './common.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (current) {
|
|
24
|
-
parts.push(current);
|
|
25
|
-
current = '';
|
|
26
|
-
}
|
|
27
|
-
inBrackets = true;
|
|
28
|
-
} else if (inBrackets && char === ']') {
|
|
29
|
-
if (current) {
|
|
30
|
-
// Remove quotes if present and add the key
|
|
31
|
-
if ((current.startsWith('"') && current.endsWith('"')) ||
|
|
32
|
-
(current.startsWith("'") && current.endsWith("'"))) {
|
|
33
|
-
parts.push(current.slice(1, -1));
|
|
34
|
-
} else {
|
|
35
|
-
// Numeric index or unquoted string
|
|
36
|
-
const numValue = Number(current);
|
|
37
|
-
parts.push(isNaN(numValue) ? current : numValue);
|
|
38
|
-
}
|
|
39
|
-
current = '';
|
|
40
|
-
}
|
|
41
|
-
inBrackets = false;
|
|
42
|
-
quoteChar = null;
|
|
43
|
-
} else if (inBrackets && (char === '"' || char === "'")) {
|
|
44
|
-
if (!quoteChar) {
|
|
45
|
-
quoteChar = char;
|
|
46
|
-
} else if (char === quoteChar) {
|
|
47
|
-
quoteChar = null;
|
|
48
|
-
}
|
|
49
|
-
current += char;
|
|
50
|
-
} else {
|
|
51
|
-
current += char;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (current) {
|
|
56
|
-
parts.push(current);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return parts.reduce((acc, part) => acc && acc[part], obj);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export const parseView = ({ h, template, viewData, refs, handlers }) => {
|
|
4
|
+
import { parseNodeBindings } from './core/view/bindings.js';
|
|
5
|
+
import {
|
|
6
|
+
createRefMatchers,
|
|
7
|
+
resolveBestRefMatcher,
|
|
8
|
+
validateElementIdForRefs,
|
|
9
|
+
} from './core/view/refs.js';
|
|
10
|
+
import {
|
|
11
|
+
createConfiguredEventListener,
|
|
12
|
+
getEventRateLimitState,
|
|
13
|
+
} from "./core/runtime/events.js";
|
|
14
|
+
|
|
15
|
+
export const parseView = ({
|
|
16
|
+
h,
|
|
17
|
+
template,
|
|
18
|
+
viewData,
|
|
19
|
+
refs,
|
|
20
|
+
handlers,
|
|
21
|
+
createComponentUpdateHook,
|
|
22
|
+
}) => {
|
|
63
23
|
const result = jemplRender(template, viewData, {});
|
|
64
24
|
|
|
65
25
|
// Flatten the array carefully to maintain structure
|
|
@@ -71,6 +31,7 @@ export const parseView = ({ h, template, viewData, refs, handlers }) => {
|
|
|
71
31
|
refs,
|
|
72
32
|
handlers,
|
|
73
33
|
viewData,
|
|
34
|
+
createComponentUpdateHook,
|
|
74
35
|
});
|
|
75
36
|
|
|
76
37
|
const vdom = h("div", { style: { display: "contents" } }, childNodes);
|
|
@@ -91,13 +52,16 @@ export const createVirtualDom = ({
|
|
|
91
52
|
items,
|
|
92
53
|
refs = {},
|
|
93
54
|
handlers = {},
|
|
94
|
-
viewData = {}
|
|
55
|
+
viewData = {},
|
|
56
|
+
createComponentUpdateHook,
|
|
95
57
|
}) => {
|
|
96
58
|
if (!Array.isArray(items)) {
|
|
97
|
-
|
|
98
|
-
return [h("div", {}, [])];
|
|
59
|
+
throw new Error("[Parser] Input to createVirtualDom must be an array, got " + typeof items);
|
|
99
60
|
}
|
|
100
61
|
|
|
62
|
+
const refMatchers = createRefMatchers(refs);
|
|
63
|
+
const hasIdRefMatchers = refMatchers.some((refMatcher) => refMatcher.targetType === "id");
|
|
64
|
+
|
|
101
65
|
function processItems(currentItems, parentPath = "") {
|
|
102
66
|
return currentItems
|
|
103
67
|
.map((item, index) => {
|
|
@@ -156,69 +120,20 @@ export const createVirtualDom = ({
|
|
|
156
120
|
const tagName = selector.split(/[.#]/)[0];
|
|
157
121
|
const isWebComponent = tagName.includes("-");
|
|
158
122
|
|
|
159
|
-
// 1. Parse
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
props[propName] = lodashGet(viewData, valuePathName);
|
|
174
|
-
} else if (match[1].startsWith("?")) {
|
|
175
|
-
// Handle conditional boolean attributes
|
|
176
|
-
const attrName = match[1].substring(1);
|
|
177
|
-
const attrValue = match[2] || match[3] || match[4];
|
|
178
|
-
|
|
179
|
-
// Convert string values to boolean
|
|
180
|
-
let evalValue;
|
|
181
|
-
if (attrValue === "true") {
|
|
182
|
-
evalValue = true;
|
|
183
|
-
} else if (attrValue === "false") {
|
|
184
|
-
evalValue = false;
|
|
185
|
-
} else {
|
|
186
|
-
// Try to get from viewData if it's not a literal boolean
|
|
187
|
-
evalValue = lodashGet(viewData, attrValue);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Only add attribute if value is truthy
|
|
191
|
-
if (evalValue) {
|
|
192
|
-
attrs[attrName] = "";
|
|
193
|
-
}
|
|
194
|
-
} else {
|
|
195
|
-
attrs[match[1]] = match[2] || match[3] || match[4];
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Then, handle boolean attributes without values
|
|
200
|
-
// Remove all processed attribute-value pairs from the string first
|
|
201
|
-
let remainingAttrsString = attrsString;
|
|
202
|
-
const processedMatches = [];
|
|
203
|
-
let tempMatch;
|
|
204
|
-
const tempAttrRegex = /(\S+?)=(?:\"([^\"]*)\"|\'([^\']*)\'|([^\s]+))/g;
|
|
205
|
-
while ((tempMatch = tempAttrRegex.exec(attrsString)) !== null) {
|
|
206
|
-
processedMatches.push(tempMatch[0]);
|
|
207
|
-
}
|
|
208
|
-
// Remove all matched attribute=value pairs
|
|
209
|
-
processedMatches.forEach(match => {
|
|
210
|
-
remainingAttrsString = remainingAttrsString.replace(match, ' ');
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
const booleanAttrRegex = /\b(\S+?)(?=\s|$)/g;
|
|
214
|
-
let boolMatch;
|
|
215
|
-
while ((boolMatch = booleanAttrRegex.exec(remainingAttrsString)) !== null) {
|
|
216
|
-
const attrName = boolMatch[1];
|
|
217
|
-
// Skip if already processed or starts with . (prop) or contains =
|
|
218
|
-
if (!processedAttrs.has(attrName) && !attrName.startsWith(".") && !attrName.includes("=")) {
|
|
219
|
-
attrs[attrName] = "";
|
|
220
|
-
}
|
|
221
|
-
}
|
|
123
|
+
// 1. Parse selector bindings into attrs/props.
|
|
124
|
+
let attrs;
|
|
125
|
+
let props;
|
|
126
|
+
try {
|
|
127
|
+
({ attrs, props } = parseNodeBindings({
|
|
128
|
+
attrsString,
|
|
129
|
+
viewData,
|
|
130
|
+
tagName,
|
|
131
|
+
isWebComponent,
|
|
132
|
+
}));
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`[Parser] Failed to parse bindings for selector '${selector}' with attrs '${attrsString}': ${error.message}`,
|
|
136
|
+
);
|
|
222
137
|
}
|
|
223
138
|
|
|
224
139
|
// 2. Handle ID from selector string (e.g., tag#id)
|
|
@@ -233,36 +148,33 @@ export const createVirtualDom = ({
|
|
|
233
148
|
}
|
|
234
149
|
|
|
235
150
|
// 3. Determine elementIdForRefs (this ID will be used for matching refs keys)
|
|
236
|
-
//
|
|
151
|
+
// Only explicit IDs participate in id-based ref matching.
|
|
237
152
|
let elementIdForRefs = null;
|
|
238
153
|
if (attrs.id) {
|
|
239
|
-
// Check the definitive id from attrs object
|
|
154
|
+
// Check the definitive id from attrs object.
|
|
240
155
|
elementIdForRefs = attrs.id;
|
|
241
|
-
} else if (isWebComponent) {
|
|
242
|
-
// Fallback for web components that don't end up with an 'id' attribute
|
|
243
|
-
elementIdForRefs = tagName;
|
|
244
156
|
}
|
|
245
157
|
|
|
158
|
+
const selectorClassMatches = selector.match(/\.([^.#]+)/g) || [];
|
|
159
|
+
const selectorClassNames = selectorClassMatches.map((classMatch) => classMatch.substring(1));
|
|
160
|
+
const attributeClassNames = typeof attrs.class === "string"
|
|
161
|
+
? attrs.class.split(/\s+/).filter(Boolean)
|
|
162
|
+
: [];
|
|
163
|
+
const classNamesForRefs = [...new Set([...selectorClassNames, ...attributeClassNames])];
|
|
164
|
+
|
|
246
165
|
// Extract classes and ID from selector (if not a web component)
|
|
247
166
|
const classObj = Object.create(null); // Using Object.create(null) to avoid prototype issues
|
|
248
167
|
let elementId = null;
|
|
249
168
|
|
|
250
169
|
if (!isWebComponent) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const className = classMatch.substring(1);
|
|
255
|
-
classObj[className] = true;
|
|
256
|
-
});
|
|
257
|
-
}
|
|
170
|
+
selectorClassNames.forEach((className) => {
|
|
171
|
+
classObj[className] = true;
|
|
172
|
+
});
|
|
258
173
|
|
|
259
174
|
const idMatch = selector.match(/#([^.#\s]+)/);
|
|
260
175
|
if (idMatch) {
|
|
261
176
|
elementId = idMatch[1];
|
|
262
177
|
}
|
|
263
|
-
} else {
|
|
264
|
-
// For web components, use the tag name as the element ID for event binding
|
|
265
|
-
elementId = tagName;
|
|
266
178
|
}
|
|
267
179
|
|
|
268
180
|
// Determine children or text content
|
|
@@ -283,78 +195,44 @@ export const createVirtualDom = ({
|
|
|
283
195
|
// Apply event listeners
|
|
284
196
|
const eventHandlers = Object.create(null);
|
|
285
197
|
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
.replace(/\\\*/g, ".*") +
|
|
295
|
-
"$";
|
|
296
|
-
try {
|
|
297
|
-
const regex = new RegExp(pattern);
|
|
298
|
-
if (regex.test(elementIdForRefs)) {
|
|
299
|
-
matchingRefKeys.push(refKey);
|
|
300
|
-
}
|
|
301
|
-
} catch (e) {
|
|
302
|
-
// Keep this warning for invalid regex patterns
|
|
303
|
-
console.warn(
|
|
304
|
-
`[Parser] Invalid regex pattern created from refKey '${refKey}': ${pattern}`,
|
|
305
|
-
e,
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
} else {
|
|
309
|
-
if (elementIdForRefs === refKey) {
|
|
310
|
-
matchingRefKeys.push(refKey);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
198
|
+
if (refMatchers.length > 0) {
|
|
199
|
+
if (hasIdRefMatchers && elementIdForRefs) {
|
|
200
|
+
validateElementIdForRefs(elementIdForRefs);
|
|
201
|
+
}
|
|
202
|
+
const bestMatchRef = resolveBestRefMatcher({
|
|
203
|
+
elementIdForRefs,
|
|
204
|
+
classNames: classNamesForRefs,
|
|
205
|
+
refMatchers,
|
|
313
206
|
});
|
|
314
207
|
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const bIsExact = !b.includes("*");
|
|
319
|
-
if (aIsExact && !bIsExact) return -1;
|
|
320
|
-
if (!aIsExact && bIsExact) return 1;
|
|
321
|
-
return b.length - a.length;
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
const bestMatchRefKey = matchingRefKeys[0];
|
|
208
|
+
if (bestMatchRef) {
|
|
209
|
+
const bestMatchRefKey = bestMatchRef.refKey;
|
|
210
|
+
const matchIdentity = bestMatchRef.matchedValue || elementIdForRefs || bestMatchRefKey;
|
|
325
211
|
|
|
326
|
-
if (
|
|
327
|
-
const eventListeners =
|
|
212
|
+
if (bestMatchRef.refConfig && bestMatchRef.refConfig.eventListeners) {
|
|
213
|
+
const eventListeners = bestMatchRef.refConfig.eventListeners;
|
|
214
|
+
const eventRateLimitState = getEventRateLimitState(handlers);
|
|
328
215
|
Object.entries(eventListeners).forEach(
|
|
329
216
|
([eventType, eventConfig]) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
217
|
+
const stateKey = `${bestMatchRefKey}:${matchIdentity}:${eventType}`;
|
|
218
|
+
const listener = createConfiguredEventListener({
|
|
219
|
+
eventType,
|
|
220
|
+
eventConfig,
|
|
221
|
+
refKey: bestMatchRefKey,
|
|
222
|
+
handlers,
|
|
223
|
+
eventRateLimitState,
|
|
224
|
+
stateKey,
|
|
225
|
+
parseAndRenderFn: jemplParseAndRender,
|
|
226
|
+
onMissingHandler: (missingHandlerName) => {
|
|
227
|
+
console.warn(
|
|
228
|
+
`[Parser] Handler '${missingHandlerName}' for refKey '${bestMatchRefKey}' (matching '${matchIdentity}') is referenced but not found in available handlers.`,
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
if (!listener) {
|
|
342
233
|
return;
|
|
343
234
|
}
|
|
344
|
-
|
|
345
|
-
if (eventConfig.handler && handlers[eventConfig.handler]) {
|
|
346
|
-
eventHandlers[eventType] = (event) => {
|
|
347
|
-
handlers[eventConfig.handler]({
|
|
348
|
-
...eventConfig.payload,
|
|
349
|
-
_event: event,
|
|
350
|
-
});
|
|
351
|
-
};
|
|
352
|
-
} else if (eventConfig.handler) {
|
|
353
|
-
// Keep this warning for missing handlers
|
|
354
|
-
console.warn(
|
|
355
|
-
`[Parser] Handler '${eventConfig.handler}' for refKey '${bestMatchRefKey}' (matching elementId '${elementIdForRefs}') is referenced but not found in available handlers.`,
|
|
356
|
-
);
|
|
357
|
-
}
|
|
235
|
+
eventHandlers[eventType] = listener;
|
|
358
236
|
},
|
|
359
237
|
);
|
|
360
238
|
}
|
|
@@ -374,10 +252,10 @@ export const createVirtualDom = ({
|
|
|
374
252
|
: String(index);
|
|
375
253
|
snabbdomData.key = `${selector}-${itemPath}`;
|
|
376
254
|
|
|
377
|
-
// Include
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
snabbdomData.key +=
|
|
255
|
+
// Include prop keys in key for better change detection
|
|
256
|
+
const propKeys = Object.keys(props);
|
|
257
|
+
if (propKeys.length > 0) {
|
|
258
|
+
snabbdomData.key += `-p:${propKeys.join(",")}`;
|
|
381
259
|
}
|
|
382
260
|
}
|
|
383
261
|
|
|
@@ -396,77 +274,23 @@ export const createVirtualDom = ({
|
|
|
396
274
|
snabbdomData.props = props;
|
|
397
275
|
}
|
|
398
276
|
|
|
399
|
-
//
|
|
400
|
-
if (isWebComponent) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// Check if props have changed
|
|
409
|
-
const propsChanged =
|
|
410
|
-
JSON.stringify(oldProps) !== JSON.stringify(newProps);
|
|
411
|
-
|
|
412
|
-
// Check if attrs have changed
|
|
413
|
-
const attrsChanged =
|
|
414
|
-
JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs);
|
|
415
|
-
|
|
416
|
-
if (propsChanged || attrsChanged) {
|
|
417
|
-
// Set isDirty attribute and trigger re-render
|
|
418
|
-
const element = vnode.elm;
|
|
419
|
-
if (
|
|
420
|
-
element &&
|
|
421
|
-
element.render &&
|
|
422
|
-
typeof element.render === "function"
|
|
423
|
-
) {
|
|
424
|
-
element.setAttribute("isDirty", "true");
|
|
425
|
-
requestAnimationFrame(() => {
|
|
426
|
-
element.render();
|
|
427
|
-
element.removeAttribute("isDirty");
|
|
428
|
-
// Call the specific component's handleOnUpdate instead of the parent's onUpdate
|
|
429
|
-
if (element.handlers && element.handlers.handleOnUpdate) {
|
|
430
|
-
const deps = {
|
|
431
|
-
...(element.deps),
|
|
432
|
-
store: element.store,
|
|
433
|
-
render: element.render.bind(element),
|
|
434
|
-
handlers: element.handlers,
|
|
435
|
-
dispatchEvent: element.dispatchEvent.bind(element),
|
|
436
|
-
refIds: element.refIds || {},
|
|
437
|
-
getRefIds: () => element.refIds || {},
|
|
438
|
-
};
|
|
439
|
-
element.handlers.handleOnUpdate(deps, {
|
|
440
|
-
oldProps,
|
|
441
|
-
newProps,
|
|
442
|
-
oldAttrs,
|
|
443
|
-
newAttrs,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
},
|
|
450
|
-
};
|
|
277
|
+
// Hook behavior is injected so parser core stays environment-agnostic.
|
|
278
|
+
if (isWebComponent && typeof createComponentUpdateHook === "function") {
|
|
279
|
+
const componentHook = createComponentUpdateHook({
|
|
280
|
+
selector,
|
|
281
|
+
tagName,
|
|
282
|
+
});
|
|
283
|
+
if (componentHook) {
|
|
284
|
+
snabbdomData.hook = componentHook;
|
|
285
|
+
}
|
|
451
286
|
}
|
|
452
287
|
|
|
453
288
|
try {
|
|
454
|
-
|
|
455
|
-
if (isWebComponent) {
|
|
456
|
-
// For web components, we need to use just the tag name
|
|
457
|
-
return h(tagName, snabbdomData, childrenOrText);
|
|
458
|
-
} else {
|
|
459
|
-
// For regular elements, we can use the original selector or just the tag
|
|
460
|
-
return h(tagName, snabbdomData, childrenOrText);
|
|
461
|
-
}
|
|
289
|
+
return h(tagName, snabbdomData, childrenOrText);
|
|
462
290
|
} catch (error) {
|
|
463
|
-
|
|
464
|
-
tagName
|
|
465
|
-
|
|
466
|
-
childrenOrText,
|
|
467
|
-
});
|
|
468
|
-
// Fallback to a simple div
|
|
469
|
-
return h("div", {}, ["Error creating element"]);
|
|
291
|
+
throw new Error(
|
|
292
|
+
`[Parser] Error creating virtual node for '${tagName}': ${error.message}`,
|
|
293
|
+
);
|
|
470
294
|
}
|
|
471
295
|
})
|
|
472
296
|
.filter(Boolean);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const COMMON_LINK_STYLE_TEXT = `
|
|
2
|
+
a, a:link, a:visited, a:hover, a:active {
|
|
3
|
+
display: contents;
|
|
4
|
+
color: inherit;
|
|
5
|
+
text-decoration: none;
|
|
6
|
+
background: none;
|
|
7
|
+
border: none;
|
|
8
|
+
padding: 0;
|
|
9
|
+
margin: 0;
|
|
10
|
+
font: inherit;
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
}
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export const initializeComponentDom = ({
|
|
16
|
+
host,
|
|
17
|
+
cssText,
|
|
18
|
+
createStyleSheet = () => new CSSStyleSheet(),
|
|
19
|
+
createElement = (tagName) => document.createElement(tagName),
|
|
20
|
+
}) => {
|
|
21
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
22
|
+
|
|
23
|
+
const commonStyleSheet = createStyleSheet();
|
|
24
|
+
commonStyleSheet.replaceSync(COMMON_LINK_STYLE_TEXT);
|
|
25
|
+
|
|
26
|
+
const adoptedStyleSheets = [commonStyleSheet];
|
|
27
|
+
|
|
28
|
+
if (cssText) {
|
|
29
|
+
const styleSheet = createStyleSheet();
|
|
30
|
+
styleSheet.replaceSync(cssText);
|
|
31
|
+
adoptedStyleSheets.push(styleSheet);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
shadow.adoptedStyleSheets = adoptedStyleSheets;
|
|
35
|
+
|
|
36
|
+
const renderTarget = createElement("div");
|
|
37
|
+
renderTarget.style.cssText = "display: contents;";
|
|
38
|
+
shadow.appendChild(renderTarget);
|
|
39
|
+
if (!renderTarget.parentNode) {
|
|
40
|
+
host.appendChild(renderTarget);
|
|
41
|
+
}
|
|
42
|
+
host.style.display = "contents";
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
shadow,
|
|
46
|
+
renderTarget,
|
|
47
|
+
adoptedStyleSheets,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { scheduleFrame } from "./scheduler.js";
|
|
2
|
+
|
|
3
|
+
export const createWebComponentUpdateHook = ({
|
|
4
|
+
scheduleFrameFn = scheduleFrame,
|
|
5
|
+
} = {}) => {
|
|
6
|
+
return {
|
|
7
|
+
update: (oldVnode, vnode) => {
|
|
8
|
+
const oldProps = oldVnode.data?.props || {};
|
|
9
|
+
const newProps = vnode.data?.props || {};
|
|
10
|
+
|
|
11
|
+
const propsChanged = JSON.stringify(oldProps) !== JSON.stringify(newProps);
|
|
12
|
+
if (!propsChanged) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const element = vnode.elm;
|
|
17
|
+
if (!element || typeof element.render !== "function") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
element.setAttribute("isDirty", "true");
|
|
22
|
+
scheduleFrameFn(() => {
|
|
23
|
+
element.render();
|
|
24
|
+
element.removeAttribute("isDirty");
|
|
25
|
+
|
|
26
|
+
if (element.handlers && element.handlers.handleOnUpdate) {
|
|
27
|
+
const deps = {
|
|
28
|
+
...(element.deps),
|
|
29
|
+
store: element.store,
|
|
30
|
+
render: element.render.bind(element),
|
|
31
|
+
handlers: element.handlers,
|
|
32
|
+
dispatchEvent: element.dispatchEvent.bind(element),
|
|
33
|
+
refs: element.refIds || {},
|
|
34
|
+
};
|
|
35
|
+
element.handlers.handleOnUpdate(deps, {
|
|
36
|
+
oldProps,
|
|
37
|
+
newProps,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|