smbls 3.8.1 → 3.8.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.
@@ -1 +1 @@
1
- {"name":"smbls","version":"3.8.0"}
1
+ {"name":"smbls","version":"3.8.1"}
@@ -27,6 +27,7 @@ import { polyglotPlugin } from "@symbo.ls/polyglot";
27
27
  import { polyglotFunctions } from "@symbo.ls/polyglot/functions";
28
28
  import { helmetPlugin } from "@symbo.ls/helmet";
29
29
  import { fetchPlugin } from "@symbo.ls/fetch";
30
+ import { hydrate, assignBrKeysFromRegistry } from "./hydrate.js";
30
31
  const prepareContext = async (app, context = {}) => {
31
32
  const key = context.key = context.key || (isString(app) ? app : "smblsapp");
32
33
  context.define = context.define || defaultDefine;
@@ -122,6 +123,57 @@ const createDomqlElement = async (app, ctx) => {
122
123
  const hydrateFromBrender = async (app, ctx, doc, win) => {
123
124
  const parentNode = ctx.parent || doc.body;
124
125
  const domqlCreate = DOM.default && DOM.default.create || DOM.create;
126
+ const brRegistry = win.__BR_REGISTRY__ || {};
127
+ const hasRegistry = Object.keys(brRegistry).length > 0;
128
+ if (hasRegistry) {
129
+ const ssrShell = parentNode.querySelector ? parentNode.querySelector(":scope > [data-ssr]") || doc.querySelector("[data-ssr]") : null;
130
+ if (ssrShell) {
131
+ const frag = doc.createDocumentFragment ? doc.createDocumentFragment() : null;
132
+ if (frag) {
133
+ while (ssrShell.firstChild) frag.appendChild(ssrShell.firstChild);
134
+ parentNode.insertBefore(frag, ssrShell);
135
+ ssrShell.remove();
136
+ }
137
+ }
138
+ let skeleton;
139
+ try {
140
+ skeleton = await domqlCreate(app, parentNode, ctx.key, {
141
+ verbose: ctx.verbose,
142
+ ...ctx.domqlOptions,
143
+ onlyResolveExtends: true
144
+ });
145
+ } catch (e) {
146
+ console.warn("[smbls] Skeleton creation failed, falling back to full render:", e.message);
147
+ return fallbackRender(domqlCreate, app, parentNode, ctx, win);
148
+ }
149
+ if (!skeleton) {
150
+ return fallbackRender(domqlCreate, app, parentNode, ctx, win);
151
+ }
152
+ assignBrKeysFromRegistry(skeleton, brRegistry);
153
+ const result = hydrate(skeleton, {
154
+ root: doc,
155
+ emotion: ctx.emotion,
156
+ designSystem: ctx.designSystem,
157
+ events: true,
158
+ renderEvents: true
159
+ });
160
+ if (result.linked === 0 && result.unlinked > 0) {
161
+ console.warn(
162
+ `[smbls] Hydration linked 0/${result.linked + result.unlinked} elements \u2014 falling back to full render. Pre-rendered structure may have changed.`
163
+ );
164
+ return fallbackRender(domqlCreate, app, parentNode, ctx, win);
165
+ }
166
+ if (result.unlinked > 0) {
167
+ console.debug(
168
+ `[smbls] Hydration: ${result.linked} linked, ${result.unlinked} unlinked elements`
169
+ );
170
+ }
171
+ cleanup(win);
172
+ return skeleton;
173
+ }
174
+ return fallbackRender(domqlCreate, app, parentNode, ctx, win);
175
+ };
176
+ const fallbackRender = async (domqlCreate, app, parentNode, ctx, win) => {
125
177
  const toRemove = [];
126
178
  for (const child of parentNode.childNodes) {
127
179
  if (child.nodeType === 1) {
@@ -133,12 +185,16 @@ const hydrateFromBrender = async (app, ctx, doc, win) => {
133
185
  toRemove.forEach((n) => n.remove());
134
186
  const smblsApp = await domqlCreate(app, parentNode, ctx.key, {
135
187
  verbose: ctx.verbose,
136
- ...ctx.domqlOptions,
137
- onlyResolveExtends: false
188
+ ...ctx.domqlOptions
138
189
  });
139
- delete win.__BRENDER__;
190
+ cleanup(win);
140
191
  return smblsApp;
141
192
  };
193
+ const cleanup = (win) => {
194
+ if (!win) return;
195
+ delete win.__BRENDER__;
196
+ delete win.__BR_REGISTRY__;
197
+ };
142
198
  export {
143
199
  createDomqlElement,
144
200
  prepareContext
@@ -0,0 +1,558 @@
1
+ const collectBrNodes = (root) => {
2
+ const container = root || document;
3
+ const nodes = container.querySelectorAll("[data-br]");
4
+ const map = {};
5
+ nodes.forEach((node) => {
6
+ map[node.getAttribute("data-br")] = node;
7
+ });
8
+ return map;
9
+ };
10
+ const assignBrKeysFromRegistry = (element, registry, path = "") => {
11
+ if (!element || !element.__ref) return;
12
+ const lookupKey = path || "__root";
13
+ const brKey = registry[lookupKey];
14
+ if (brKey !== void 0) element.__ref.__brKey = brKey;
15
+ if (element.__ref.__children) {
16
+ for (const childKey of element.__ref.__children) {
17
+ const child = element[childKey];
18
+ if (child && child.__ref) {
19
+ const childPath = path ? `${path}.${childKey}` : childKey;
20
+ assignBrKeysFromRegistry(child, registry, childPath);
21
+ }
22
+ }
23
+ }
24
+ };
25
+ const hydrate = (element, options = {}) => {
26
+ const {
27
+ root,
28
+ events: attachEvents = true,
29
+ renderEvents: fireRenderEvents = true,
30
+ emotion,
31
+ designSystem
32
+ } = options;
33
+ const brNodes = collectBrNodes(root);
34
+ const colorMap = designSystem?.color || {};
35
+ const mediaMap = designSystem?.media || {};
36
+ let linked = 0;
37
+ let unlinked = 0;
38
+ const walk = (el) => {
39
+ if (!el || !el.__ref) return;
40
+ const brKey = el.__ref.__brKey;
41
+ if (brKey) {
42
+ const node = brNodes[brKey];
43
+ if (node) {
44
+ el.node = node;
45
+ node.ref = el;
46
+ if (emotion) {
47
+ renderCSS(el, emotion, colorMap, mediaMap);
48
+ }
49
+ if (attachEvents) {
50
+ bindEvents(el);
51
+ }
52
+ linked++;
53
+ } else {
54
+ unlinked++;
55
+ }
56
+ }
57
+ if (el.__ref.__children) {
58
+ for (const childKey of el.__ref.__children) {
59
+ const child = el[childKey];
60
+ if (child && child.__ref) walk(child);
61
+ }
62
+ }
63
+ };
64
+ walk(element);
65
+ if (fireRenderEvents) {
66
+ fireLifecycle(element);
67
+ }
68
+ return { element, linked, unlinked };
69
+ };
70
+ const renderCSS = (el, emotion, colorMap, mediaMap) => {
71
+ const { node, props } = el;
72
+ if (!node || !props) return;
73
+ const css = {};
74
+ let hasCss = false;
75
+ for (const key in props) {
76
+ const val = props[key];
77
+ if (key.charCodeAt(0) === 64) {
78
+ const breakpoint = mediaMap[key.slice(1)];
79
+ if (breakpoint && typeof val === "object") {
80
+ const mediaCss = resolvePropsToCSS(val, colorMap);
81
+ if (Object.keys(mediaCss).length) {
82
+ css[breakpoint] = mediaCss;
83
+ hasCss = true;
84
+ }
85
+ }
86
+ continue;
87
+ }
88
+ if (key.charCodeAt(0) === 58) {
89
+ if (typeof val === "object") {
90
+ const pseudoCss = resolvePropsToCSS(val, colorMap);
91
+ if (Object.keys(pseudoCss).length) {
92
+ css["&" + key] = pseudoCss;
93
+ hasCss = true;
94
+ }
95
+ }
96
+ continue;
97
+ }
98
+ const expanded = resolveShorthand(key, val);
99
+ if (expanded) {
100
+ for (const ek in expanded) {
101
+ css[ek] = resolveValue(ek, expanded[ek], colorMap);
102
+ }
103
+ hasCss = true;
104
+ continue;
105
+ }
106
+ if (!isCSS(key)) continue;
107
+ css[key] = resolveValue(key, val, colorMap);
108
+ hasCss = true;
109
+ }
110
+ const extsCss = getExtendsCSS(el);
111
+ if (extsCss) {
112
+ for (const [k, v] of Object.entries(extsCss)) {
113
+ if (!css[k]) {
114
+ css[k] = v;
115
+ hasCss = true;
116
+ }
117
+ }
118
+ }
119
+ if (el.style && typeof el.style === "object") {
120
+ Object.assign(css, el.style);
121
+ hasCss = true;
122
+ }
123
+ if (!hasCss) return;
124
+ const emotionClass = emotion.css(css);
125
+ const classes = [];
126
+ if (emotionClass) classes.push(emotionClass);
127
+ if (typeof el.key === "string" && el.key.charCodeAt(0) === 95 && el.key.charCodeAt(1) !== 95) {
128
+ classes.push(el.key.slice(1));
129
+ }
130
+ if (props.class) classes.push(props.class);
131
+ if (el.attr?.class) classes.push(el.attr.class);
132
+ const classlist = el.classlist;
133
+ if (classlist) {
134
+ if (typeof classlist === "string") classes.push(classlist);
135
+ else if (typeof classlist === "object") {
136
+ for (const k in classlist) {
137
+ const v = classlist[k];
138
+ if (typeof v === "boolean" && v) classes.push(k);
139
+ else if (typeof v === "string") classes.push(v);
140
+ else if (typeof v === "object" && v) classes.push(emotion.css(v));
141
+ }
142
+ }
143
+ }
144
+ if (classes.length) {
145
+ node.setAttribute("class", classes.join(" "));
146
+ }
147
+ for (const key in props) {
148
+ if (isCSS(key) && node.hasAttribute(key)) {
149
+ node.removeAttribute(key);
150
+ }
151
+ }
152
+ };
153
+ const resolvePropsToCSS = (propsObj, colorMap) => {
154
+ const css = {};
155
+ for (const key in propsObj) {
156
+ const expanded = resolveShorthand(key, propsObj[key]);
157
+ if (expanded) {
158
+ for (const ek in expanded) css[ek] = resolveValue(ek, expanded[ek], colorMap);
159
+ continue;
160
+ }
161
+ if (!isCSS(key)) continue;
162
+ css[key] = resolveValue(key, propsObj[key], colorMap);
163
+ }
164
+ return css;
165
+ };
166
+ const COLOR_PROPS = /* @__PURE__ */ new Set([
167
+ "color",
168
+ "background",
169
+ "backgroundColor",
170
+ "borderColor",
171
+ "borderTopColor",
172
+ "borderRightColor",
173
+ "borderBottomColor",
174
+ "borderLeftColor",
175
+ "outlineColor",
176
+ "fill",
177
+ "stroke",
178
+ "caretColor",
179
+ "columnRuleColor",
180
+ "textDecorationColor",
181
+ "boxShadow",
182
+ "textShadow"
183
+ ]);
184
+ const resolveValue = (key, val, colorMap) => {
185
+ if (typeof val !== "string") return val;
186
+ if (COLOR_PROPS.has(key) && colorMap[val]) return colorMap[val];
187
+ return val;
188
+ };
189
+ const NON_CSS_PROPS = /* @__PURE__ */ new Set([
190
+ "href",
191
+ "src",
192
+ "alt",
193
+ "title",
194
+ "id",
195
+ "name",
196
+ "type",
197
+ "value",
198
+ "placeholder",
199
+ "target",
200
+ "rel",
201
+ "loading",
202
+ "srcset",
203
+ "sizes",
204
+ "media",
205
+ "role",
206
+ "tabindex",
207
+ "for",
208
+ "action",
209
+ "method",
210
+ "enctype",
211
+ "autocomplete",
212
+ "autofocus",
213
+ "theme",
214
+ "__element",
215
+ "update"
216
+ ]);
217
+ const EXTENDS_CSS = {
218
+ Flex: { display: "flex" },
219
+ InlineFlex: { display: "inline-flex" },
220
+ Grid: { display: "grid" },
221
+ InlineGrid: { display: "inline-grid" },
222
+ Block: { display: "block" },
223
+ Inline: { display: "inline" }
224
+ };
225
+ const getExtendsCSS = (el) => {
226
+ const exts = el.__ref?.__extends;
227
+ if (!exts || !Array.isArray(exts)) return null;
228
+ for (const ext of exts) {
229
+ if (EXTENDS_CSS[ext]) return EXTENDS_CSS[ext];
230
+ }
231
+ return null;
232
+ };
233
+ const resolveShorthand = (key, val) => {
234
+ if (typeof val === "undefined" || val === null) return null;
235
+ if (key === "flow" && typeof val === "string") {
236
+ let [direction, wrap] = (val || "row").split(" ");
237
+ if (val.startsWith("x") || val === "row") direction = "row";
238
+ if (val.startsWith("y") || val === "column") direction = "column";
239
+ return { display: "flex", flexFlow: (direction || "") + " " + (wrap || "") };
240
+ }
241
+ if (key === "wrap") return { display: "flex", flexWrap: val };
242
+ if ((key === "align" || key === "flexAlign") && typeof val === "string") {
243
+ const [alignItems, justifyContent] = val.split(" ");
244
+ return { display: "flex", alignItems, justifyContent };
245
+ }
246
+ if (key === "gridAlign" && typeof val === "string") {
247
+ const [alignItems, justifyContent] = val.split(" ");
248
+ return { display: "grid", alignItems, justifyContent };
249
+ }
250
+ if (key === "flexFlow" && typeof val === "string") {
251
+ let [direction, wrap] = (val || "row").split(" ");
252
+ if (val.startsWith("x") || val === "row") direction = "row";
253
+ if (val.startsWith("y") || val === "column") direction = "column";
254
+ return { display: "flex", flexFlow: (direction || "") + " " + (wrap || "") };
255
+ }
256
+ if (key === "flexWrap") return { display: "flex", flexWrap: val };
257
+ if (key === "round" || key === "borderRadius" && val) {
258
+ return { borderRadius: typeof val === "number" ? val + "px" : val };
259
+ }
260
+ if (key === "boxSize" && typeof val === "string") {
261
+ const [height, width] = val.split(" ");
262
+ return { height, width: width || height };
263
+ }
264
+ if (key === "widthRange" && typeof val === "string") {
265
+ const [minWidth, maxWidth] = val.split(" ");
266
+ return { minWidth, maxWidth: maxWidth || minWidth };
267
+ }
268
+ if (key === "heightRange" && typeof val === "string") {
269
+ const [minHeight, maxHeight] = val.split(" ");
270
+ return { minHeight, maxHeight: maxHeight || minHeight };
271
+ }
272
+ if (key === "column") return { gridColumn: val };
273
+ if (key === "columns") return { gridTemplateColumns: val };
274
+ if (key === "templateColumns") return { gridTemplateColumns: val };
275
+ if (key === "row") return { gridRow: val };
276
+ if (key === "rows") return { gridTemplateRows: val };
277
+ if (key === "templateRows") return { gridTemplateRows: val };
278
+ if (key === "area") return { gridArea: val };
279
+ if (key === "template") return { gridTemplate: val };
280
+ if (key === "templateAreas") return { gridTemplateAreas: val };
281
+ if (key === "autoColumns") return { gridAutoColumns: val };
282
+ if (key === "autoRows") return { gridAutoRows: val };
283
+ if (key === "autoFlow") return { gridAutoFlow: val };
284
+ if (key === "columnStart") return { gridColumnStart: val };
285
+ if (key === "rowStart") return { gridRowStart: val };
286
+ return null;
287
+ };
288
+ const isCSS = (key) => {
289
+ const ch = key.charCodeAt(0);
290
+ if (ch === 95 || ch === 64 || ch === 58) return false;
291
+ if (ch >= 65 && ch <= 90) return false;
292
+ if (NON_CSS_PROPS.has(key)) return false;
293
+ return CSS_PROPERTIES.has(key);
294
+ };
295
+ const CSS_PROPERTIES = /* @__PURE__ */ new Set([
296
+ "display",
297
+ "position",
298
+ "top",
299
+ "right",
300
+ "bottom",
301
+ "left",
302
+ "width",
303
+ "height",
304
+ "minWidth",
305
+ "maxWidth",
306
+ "minHeight",
307
+ "maxHeight",
308
+ "margin",
309
+ "marginTop",
310
+ "marginRight",
311
+ "marginBottom",
312
+ "marginLeft",
313
+ "marginBlock",
314
+ "marginInline",
315
+ "padding",
316
+ "paddingTop",
317
+ "paddingRight",
318
+ "paddingBottom",
319
+ "paddingLeft",
320
+ "paddingBlock",
321
+ "paddingInline",
322
+ "border",
323
+ "borderTop",
324
+ "borderRight",
325
+ "borderBottom",
326
+ "borderLeft",
327
+ "borderRadius",
328
+ "borderColor",
329
+ "borderWidth",
330
+ "borderStyle",
331
+ "borderTopWidth",
332
+ "borderRightWidth",
333
+ "borderBottomWidth",
334
+ "borderLeftWidth",
335
+ "borderTopStyle",
336
+ "borderRightStyle",
337
+ "borderBottomStyle",
338
+ "borderLeftStyle",
339
+ "borderTopColor",
340
+ "borderRightColor",
341
+ "borderBottomColor",
342
+ "borderLeftColor",
343
+ "borderTopLeftRadius",
344
+ "borderTopRightRadius",
345
+ "borderBottomLeftRadius",
346
+ "borderBottomRightRadius",
347
+ "background",
348
+ "backgroundColor",
349
+ "backgroundImage",
350
+ "backgroundSize",
351
+ "backgroundPosition",
352
+ "backgroundRepeat",
353
+ "backgroundAttachment",
354
+ "color",
355
+ "fontSize",
356
+ "fontWeight",
357
+ "fontFamily",
358
+ "fontStyle",
359
+ "lineHeight",
360
+ "letterSpacing",
361
+ "textAlign",
362
+ "textDecoration",
363
+ "textTransform",
364
+ "textIndent",
365
+ "textOverflow",
366
+ "textShadow",
367
+ "opacity",
368
+ "overflow",
369
+ "overflowX",
370
+ "overflowY",
371
+ "zIndex",
372
+ "cursor",
373
+ "pointerEvents",
374
+ "userSelect",
375
+ "flex",
376
+ "flexDirection",
377
+ "flexWrap",
378
+ "flexFlow",
379
+ "flexGrow",
380
+ "flexShrink",
381
+ "flexBasis",
382
+ "alignItems",
383
+ "alignContent",
384
+ "alignSelf",
385
+ "justifyContent",
386
+ "justifyItems",
387
+ "justifySelf",
388
+ "gap",
389
+ "rowGap",
390
+ "columnGap",
391
+ "gridTemplateColumns",
392
+ "gridTemplateRows",
393
+ "gridColumn",
394
+ "gridRow",
395
+ "gridArea",
396
+ "gridAutoFlow",
397
+ "gridAutoColumns",
398
+ "gridAutoRows",
399
+ "inset",
400
+ "inlineSize",
401
+ "blockSize",
402
+ "minInlineSize",
403
+ "maxInlineSize",
404
+ "minBlockSize",
405
+ "maxBlockSize",
406
+ "paddingBlockStart",
407
+ "paddingBlockEnd",
408
+ "paddingInlineStart",
409
+ "paddingInlineEnd",
410
+ "marginBlockStart",
411
+ "marginBlockEnd",
412
+ "marginInlineStart",
413
+ "marginInlineEnd",
414
+ "transform",
415
+ "transformOrigin",
416
+ "transition",
417
+ "animation",
418
+ "animationName",
419
+ "animationDuration",
420
+ "animationDelay",
421
+ "animationTimingFunction",
422
+ "animationFillMode",
423
+ "animationIterationCount",
424
+ "animationPlayState",
425
+ "animationDirection",
426
+ "gridTemplate",
427
+ "gridTemplateAreas",
428
+ "gridColumnStart",
429
+ "gridRowStart",
430
+ "boxShadow",
431
+ "outline",
432
+ "outlineColor",
433
+ "outlineWidth",
434
+ "outlineStyle",
435
+ "outlineOffset",
436
+ "whiteSpace",
437
+ "wordBreak",
438
+ "wordWrap",
439
+ "overflowWrap",
440
+ "visibility",
441
+ "boxSizing",
442
+ "objectFit",
443
+ "objectPosition",
444
+ "filter",
445
+ "backdropFilter",
446
+ "mixBlendMode",
447
+ "fill",
448
+ "stroke",
449
+ "strokeWidth",
450
+ "listStyle",
451
+ "listStyleType",
452
+ "listStylePosition",
453
+ "counterReset",
454
+ "counterIncrement",
455
+ "content",
456
+ "aspectRatio",
457
+ "resize",
458
+ "appearance",
459
+ "scrollBehavior",
460
+ "scrollMargin",
461
+ "scrollPadding",
462
+ "willChange",
463
+ "contain",
464
+ "isolation",
465
+ "caretColor",
466
+ "accentColor",
467
+ "columnCount",
468
+ "columnGap",
469
+ "columnRuleColor",
470
+ "columnRuleStyle",
471
+ "columnRuleWidth",
472
+ "textDecorationColor",
473
+ "textDecorationStyle",
474
+ "textDecorationThickness",
475
+ "clipPath",
476
+ "shapeOutside"
477
+ ]);
478
+ const DOMQL_LIFECYCLE = /* @__PURE__ */ new Set([
479
+ "render",
480
+ "create",
481
+ "init",
482
+ "start",
483
+ "complete",
484
+ "done",
485
+ "beforeClassAssign",
486
+ "attachNode",
487
+ "stateInit",
488
+ "stateCreated",
489
+ "renderRouter",
490
+ "lazyLoad",
491
+ "error"
492
+ ]);
493
+ const bindEvents = (el) => {
494
+ const { node, on, props } = el;
495
+ if (!node) return;
496
+ const handled = /* @__PURE__ */ new Set();
497
+ if (on) {
498
+ for (const param in on) {
499
+ if (DOMQL_LIFECYCLE.has(param)) continue;
500
+ if (typeof on[param] !== "function") continue;
501
+ handled.add(param);
502
+ addListener(node, param, on[param], el);
503
+ }
504
+ }
505
+ if (props) {
506
+ for (const key in props) {
507
+ if (key.length <= 2 || key[0] !== "o" || key[1] !== "n") continue;
508
+ if (typeof props[key] !== "function") continue;
509
+ const third = key[2];
510
+ if (third !== third.toUpperCase()) continue;
511
+ const eventName = third.toLowerCase() + key.slice(3);
512
+ if (handled.has(eventName) || DOMQL_LIFECYCLE.has(eventName)) continue;
513
+ addListener(node, eventName, props[key], el);
514
+ }
515
+ }
516
+ };
517
+ const addListener = (node, eventName, handler, el) => {
518
+ node.addEventListener(eventName, (event) => {
519
+ const result = handler.call(el, event, el, el.state, el.context);
520
+ if (result && typeof result.then === "function") {
521
+ result.catch(() => {
522
+ });
523
+ }
524
+ });
525
+ };
526
+ const fireLifecycle = (el) => {
527
+ if (!el || !el.__ref || !el.node) return;
528
+ const on = el.on;
529
+ if (on) {
530
+ fireEvent(on.render, el);
531
+ fireEvent(on.renderRouter, el);
532
+ fireEvent(on.done, el);
533
+ fireEvent(on.create, el);
534
+ }
535
+ if (el.__ref.__children) {
536
+ for (const childKey of el.__ref.__children) {
537
+ const child = el[childKey];
538
+ if (child && child.__ref) fireLifecycle(child);
539
+ }
540
+ }
541
+ };
542
+ const fireEvent = (fn, el) => {
543
+ if (typeof fn !== "function") return;
544
+ try {
545
+ const result = fn.call(el, el, el.state, el.context);
546
+ if (result && typeof result.then === "function") {
547
+ result.catch(() => {
548
+ });
549
+ }
550
+ } catch (e) {
551
+ console.warn("[smbls hydrate]", el.key, e.message);
552
+ }
553
+ };
554
+ export {
555
+ assignBrKeysFromRegistry,
556
+ collectBrNodes,
557
+ hydrate
558
+ };