@symbo.ls/brender 3.4.11 → 3.5.1
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/cjs/env.js +23 -0
- package/dist/cjs/hydrate.js +97 -4
- package/dist/cjs/load.js +62 -16
- package/dist/cjs/render.js +300 -21
- package/dist/esm/env.js +23 -0
- package/dist/esm/hydrate.js +97 -4
- package/dist/esm/load.js +52 -16
- package/dist/esm/render.js +300 -21
- package/env.js +17 -0
- package/hydrate.js +89 -5
- package/load.js +58 -19
- package/package.json +3 -3
- package/render.js +313 -21
package/dist/esm/render.js
CHANGED
|
@@ -3,6 +3,61 @@ import { resetKeys, assignKeys, mapKeysToElements } from "./keys.js";
|
|
|
3
3
|
import { extractMetadata, generateHeadHtml } from "./metadata.js";
|
|
4
4
|
import { hydrate } from "./hydrate.js";
|
|
5
5
|
import { parseHTML } from "linkedom";
|
|
6
|
+
const UIKIT_STUBS = {
|
|
7
|
+
Box: {},
|
|
8
|
+
Focusable: {},
|
|
9
|
+
Block: { display: "block" },
|
|
10
|
+
Inline: { display: "inline" },
|
|
11
|
+
Flex: { display: "flex" },
|
|
12
|
+
InlineFlex: { display: "inline-flex" },
|
|
13
|
+
Grid: { display: "grid" },
|
|
14
|
+
InlineGrid: { display: "inline-grid" },
|
|
15
|
+
Link: {
|
|
16
|
+
tag: "a",
|
|
17
|
+
attr: {
|
|
18
|
+
href: (el) => el.props?.href,
|
|
19
|
+
target: (el) => el.props?.target,
|
|
20
|
+
rel: (el) => el.props?.rel
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
A: { extends: "Link" },
|
|
24
|
+
RouteLink: { extends: "Link" },
|
|
25
|
+
Img: {
|
|
26
|
+
tag: "img",
|
|
27
|
+
attr: {
|
|
28
|
+
src: (el) => {
|
|
29
|
+
let src = el.props?.src;
|
|
30
|
+
if (typeof src === "string" && src.includes("{{")) {
|
|
31
|
+
src = el.call("replaceLiteralsWithObjectFields", src, el.state);
|
|
32
|
+
}
|
|
33
|
+
return src;
|
|
34
|
+
},
|
|
35
|
+
alt: (el) => el.props?.alt,
|
|
36
|
+
loading: (el) => el.props?.loading
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
Image: { extends: "Img" },
|
|
40
|
+
Button: { tag: "button" },
|
|
41
|
+
FocusableComponent: { tag: "button" },
|
|
42
|
+
Form: { tag: "form" },
|
|
43
|
+
Input: { tag: "input" },
|
|
44
|
+
TextArea: { tag: "textarea" },
|
|
45
|
+
Textarea: { tag: "textarea" },
|
|
46
|
+
Select: { tag: "select" },
|
|
47
|
+
Label: { tag: "label" },
|
|
48
|
+
Iframe: { tag: "iframe" },
|
|
49
|
+
Video: { tag: "video" },
|
|
50
|
+
Audio: { tag: "audio" },
|
|
51
|
+
Canvas: { tag: "canvas" },
|
|
52
|
+
Span: { tag: "span" },
|
|
53
|
+
P: { tag: "p" },
|
|
54
|
+
H1: { tag: "h1" },
|
|
55
|
+
H2: { tag: "h2" },
|
|
56
|
+
H3: { tag: "h3" },
|
|
57
|
+
H4: { tag: "h4" },
|
|
58
|
+
H5: { tag: "h5" },
|
|
59
|
+
H6: { tag: "h6" }
|
|
60
|
+
};
|
|
6
61
|
const render = async (data, options = {}) => {
|
|
7
62
|
const { route = "/", state: stateOverrides, context: contextOverrides } = options;
|
|
8
63
|
const { window, document } = createEnv();
|
|
@@ -42,12 +97,23 @@ const renderElement = async (elementDef, options = {}) => {
|
|
|
42
97
|
const { window, document } = createEnv();
|
|
43
98
|
const body = document.body;
|
|
44
99
|
const { create } = await import("@domql/element");
|
|
100
|
+
const domqlUtils = await import("@domql/utils");
|
|
101
|
+
const components = { ...UIKIT_STUBS, ...context.components || {} };
|
|
102
|
+
const utils = {
|
|
103
|
+
...domqlUtils,
|
|
104
|
+
...context.utils || {},
|
|
105
|
+
...context.functions || {}
|
|
106
|
+
};
|
|
45
107
|
resetKeys();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
108
|
+
let element;
|
|
109
|
+
try {
|
|
110
|
+
element = create(elementDef, { node: body }, "root", {
|
|
111
|
+
context: { document, window, ...context, components, utils }
|
|
112
|
+
});
|
|
113
|
+
} catch (err) {
|
|
114
|
+
}
|
|
49
115
|
assignKeys(body);
|
|
50
|
-
const registry = mapKeysToElements(element);
|
|
116
|
+
const registry = element ? mapKeysToElements(element) : {};
|
|
51
117
|
const html = body.innerHTML;
|
|
52
118
|
return { html, registry, element };
|
|
53
119
|
};
|
|
@@ -112,6 +178,138 @@ ${result.html}
|
|
|
112
178
|
</html>`;
|
|
113
179
|
return { html, route, brKeyCount: result.brKeyCount };
|
|
114
180
|
};
|
|
181
|
+
const LETTER_TO_INDEX = {
|
|
182
|
+
U: -6,
|
|
183
|
+
V: -5,
|
|
184
|
+
W: -4,
|
|
185
|
+
X: -3,
|
|
186
|
+
Y: -2,
|
|
187
|
+
Z: -1,
|
|
188
|
+
A: 0,
|
|
189
|
+
B: 1,
|
|
190
|
+
C: 2,
|
|
191
|
+
D: 3,
|
|
192
|
+
E: 4,
|
|
193
|
+
F: 5,
|
|
194
|
+
G: 6,
|
|
195
|
+
H: 7,
|
|
196
|
+
I: 8,
|
|
197
|
+
J: 9,
|
|
198
|
+
K: 10,
|
|
199
|
+
L: 11,
|
|
200
|
+
M: 12,
|
|
201
|
+
N: 13,
|
|
202
|
+
O: 14,
|
|
203
|
+
P: 15
|
|
204
|
+
};
|
|
205
|
+
const SPACING_PROPS = /* @__PURE__ */ new Set([
|
|
206
|
+
"padding",
|
|
207
|
+
"paddingTop",
|
|
208
|
+
"paddingRight",
|
|
209
|
+
"paddingBottom",
|
|
210
|
+
"paddingLeft",
|
|
211
|
+
"paddingBlock",
|
|
212
|
+
"paddingInline",
|
|
213
|
+
"paddingBlockStart",
|
|
214
|
+
"paddingBlockEnd",
|
|
215
|
+
"paddingInlineStart",
|
|
216
|
+
"paddingInlineEnd",
|
|
217
|
+
"margin",
|
|
218
|
+
"marginTop",
|
|
219
|
+
"marginRight",
|
|
220
|
+
"marginBottom",
|
|
221
|
+
"marginLeft",
|
|
222
|
+
"marginBlock",
|
|
223
|
+
"marginInline",
|
|
224
|
+
"marginBlockStart",
|
|
225
|
+
"marginBlockEnd",
|
|
226
|
+
"marginInlineStart",
|
|
227
|
+
"marginInlineEnd",
|
|
228
|
+
"gap",
|
|
229
|
+
"rowGap",
|
|
230
|
+
"columnGap",
|
|
231
|
+
"top",
|
|
232
|
+
"right",
|
|
233
|
+
"bottom",
|
|
234
|
+
"left",
|
|
235
|
+
"width",
|
|
236
|
+
"height",
|
|
237
|
+
"minWidth",
|
|
238
|
+
"maxWidth",
|
|
239
|
+
"minHeight",
|
|
240
|
+
"maxHeight",
|
|
241
|
+
"flexBasis",
|
|
242
|
+
"fontSize",
|
|
243
|
+
"lineHeight",
|
|
244
|
+
"letterSpacing",
|
|
245
|
+
"borderWidth",
|
|
246
|
+
"borderRadius",
|
|
247
|
+
"outlineWidth",
|
|
248
|
+
"outlineOffset",
|
|
249
|
+
"inset",
|
|
250
|
+
"insetBlock",
|
|
251
|
+
"insetInline",
|
|
252
|
+
"boxSize",
|
|
253
|
+
"round"
|
|
254
|
+
]);
|
|
255
|
+
const resolveSpacingToken = (token, spacingConfig) => {
|
|
256
|
+
if (!token || typeof token !== "string") return null;
|
|
257
|
+
if (!spacingConfig) return null;
|
|
258
|
+
const base = spacingConfig.base || 16;
|
|
259
|
+
const ratio = spacingConfig.ratio || 1.618;
|
|
260
|
+
const unit = spacingConfig.unit || "px";
|
|
261
|
+
const hasSubSequence = spacingConfig.subSequence !== false;
|
|
262
|
+
if (token.includes(" ")) {
|
|
263
|
+
const parts = token.split(" ").map((part) => {
|
|
264
|
+
if (part === "-" || part === "") return part;
|
|
265
|
+
return resolveSpacingToken(part, spacingConfig) || part;
|
|
266
|
+
});
|
|
267
|
+
return parts.join(" ");
|
|
268
|
+
}
|
|
269
|
+
if (/^(none|auto|inherit|initial|unset|0)$/i.test(token)) return null;
|
|
270
|
+
if (/\d+(px|em|rem|%|vh|vw|vmin|vmax|ch|ex|cm|mm|in|pt|pc|fr|s|ms)$/i.test(token)) return null;
|
|
271
|
+
if (/^(#|rgb|hsl|var\()/i.test(token)) return null;
|
|
272
|
+
const isNegative = token.startsWith("-");
|
|
273
|
+
const abs = isNegative ? token.slice(1) : token;
|
|
274
|
+
const m = abs.match(/^([A-Z])(\d)?$/i);
|
|
275
|
+
if (!m) return null;
|
|
276
|
+
const letter = m[1].toUpperCase();
|
|
277
|
+
const subStep = m[2] ? parseInt(m[2]) : 0;
|
|
278
|
+
const idx = LETTER_TO_INDEX[letter];
|
|
279
|
+
if (idx === void 0) return null;
|
|
280
|
+
let value = base * Math.pow(ratio, idx);
|
|
281
|
+
if (subStep > 0 && hasSubSequence) {
|
|
282
|
+
const next = base * Math.pow(ratio, idx + 1);
|
|
283
|
+
const diff = next - value;
|
|
284
|
+
const subRatio = diff / ratio;
|
|
285
|
+
const first = next - subRatio;
|
|
286
|
+
const second = value + subRatio;
|
|
287
|
+
const middle = (first + second) / 2;
|
|
288
|
+
const subs = ~~next - ~~value > 16 ? [first, middle, second] : [first, second];
|
|
289
|
+
if (subStep <= subs.length) {
|
|
290
|
+
value = subs[subStep - 1];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const rounded = Math.round(value * 100) / 100;
|
|
294
|
+
const sign = isNegative ? "-" : "";
|
|
295
|
+
return `${sign}${rounded}${unit}`;
|
|
296
|
+
};
|
|
297
|
+
const SPACING_PROPS_KEBAB = new Set(
|
|
298
|
+
[...SPACING_PROPS].map((k) => k.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()))
|
|
299
|
+
);
|
|
300
|
+
const resolveDSValue = (key, val, ds) => {
|
|
301
|
+
if (typeof val !== "string") return val;
|
|
302
|
+
if (CSS_COLOR_PROPS.has(key)) {
|
|
303
|
+
const colorMap = ds?.color || {};
|
|
304
|
+
if (colorMap[val]) return colorMap[val];
|
|
305
|
+
}
|
|
306
|
+
if (SPACING_PROPS.has(key) || SPACING_PROPS_KEBAB.has(key)) {
|
|
307
|
+
const spacing = ds?.spacing || {};
|
|
308
|
+
const resolved = resolveSpacingToken(val, spacing);
|
|
309
|
+
if (resolved) return resolved;
|
|
310
|
+
}
|
|
311
|
+
return val;
|
|
312
|
+
};
|
|
115
313
|
const CSS_COLOR_PROPS = /* @__PURE__ */ new Set([
|
|
116
314
|
"color",
|
|
117
315
|
"background",
|
|
@@ -151,41 +349,96 @@ const NON_CSS_PROPS = /* @__PURE__ */ new Set([
|
|
|
151
349
|
"autofocus",
|
|
152
350
|
"theme",
|
|
153
351
|
"__element",
|
|
154
|
-
"update"
|
|
352
|
+
"update",
|
|
353
|
+
"childrenAs",
|
|
354
|
+
"childExtends",
|
|
355
|
+
"childProps",
|
|
356
|
+
"children"
|
|
155
357
|
]);
|
|
156
358
|
const camelToKebab = (str) => str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
|
157
359
|
const resolveShorthand = (key, val) => {
|
|
158
|
-
if (
|
|
360
|
+
if (typeof val === "undefined" || val === null) return null;
|
|
361
|
+
if (key === "flow" && typeof val === "string") {
|
|
362
|
+
let [direction, wrap] = (val || "row").split(" ");
|
|
363
|
+
if (val.startsWith("x") || val === "row") direction = "row";
|
|
364
|
+
if (val.startsWith("y") || val === "column") direction = "column";
|
|
365
|
+
return { display: "flex", "flex-flow": (direction || "") + " " + (wrap || "") };
|
|
366
|
+
}
|
|
367
|
+
if (key === "wrap") {
|
|
368
|
+
return { display: "flex", "flex-wrap": val };
|
|
369
|
+
}
|
|
370
|
+
if ((key === "align" || key === "flexAlign") && typeof val === "string") {
|
|
159
371
|
const [alignItems, justifyContent] = val.split(" ");
|
|
160
|
-
|
|
372
|
+
const result = { display: "flex", "align-items": alignItems };
|
|
373
|
+
if (justifyContent) result["justify-content"] = justifyContent;
|
|
374
|
+
return result;
|
|
161
375
|
}
|
|
162
376
|
if (key === "gridAlign" && typeof val === "string") {
|
|
163
377
|
const [alignItems, justifyContent] = val.split(" ");
|
|
164
|
-
|
|
378
|
+
const result = { display: "grid", "align-items": alignItems };
|
|
379
|
+
if (justifyContent) result["justify-content"] = justifyContent;
|
|
380
|
+
return result;
|
|
165
381
|
}
|
|
166
|
-
if (key === "
|
|
382
|
+
if (key === "flexFlow" && typeof val === "string") {
|
|
383
|
+
let [direction, wrap] = (val || "row").split(" ");
|
|
384
|
+
if (val.startsWith("x") || val === "row") direction = "row";
|
|
385
|
+
if (val.startsWith("y") || val === "column") direction = "column";
|
|
386
|
+
return { display: "flex", "flex-flow": (direction || "") + " " + (wrap || "") };
|
|
387
|
+
}
|
|
388
|
+
if (key === "flexWrap") {
|
|
389
|
+
return { display: "flex", "flex-wrap": val };
|
|
390
|
+
}
|
|
391
|
+
if (key === "backgroundImage" && typeof val === "string" && !val.startsWith("url(") && !val.startsWith("linear-gradient") && !val.startsWith("radial-gradient") && !val.startsWith("none")) {
|
|
392
|
+
return { "background-image": `url(${val})` };
|
|
393
|
+
}
|
|
394
|
+
if (key === "round" || key === "borderRadius" && val) {
|
|
167
395
|
return { "border-radius": typeof val === "number" ? val + "px" : val };
|
|
168
396
|
}
|
|
169
|
-
if (key === "boxSize" && val) {
|
|
170
|
-
|
|
397
|
+
if (key === "boxSize" && typeof val === "string") {
|
|
398
|
+
const [height, width] = val.split(" ");
|
|
399
|
+
return { height, width: width || height };
|
|
400
|
+
}
|
|
401
|
+
if (key === "widthRange" && typeof val === "string") {
|
|
402
|
+
const [minWidth, maxWidth] = val.split(" ");
|
|
403
|
+
return { "min-width": minWidth, "max-width": maxWidth || minWidth };
|
|
171
404
|
}
|
|
405
|
+
if (key === "heightRange" && typeof val === "string") {
|
|
406
|
+
const [minHeight, maxHeight] = val.split(" ");
|
|
407
|
+
return { "min-height": minHeight, "max-height": maxHeight || minHeight };
|
|
408
|
+
}
|
|
409
|
+
if (key === "column") return { "grid-column": val };
|
|
410
|
+
if (key === "columns") return { "grid-template-columns": val };
|
|
411
|
+
if (key === "templateColumns") return { "grid-template-columns": val };
|
|
412
|
+
if (key === "row") return { "grid-row": val };
|
|
413
|
+
if (key === "rows") return { "grid-template-rows": val };
|
|
414
|
+
if (key === "templateRows") return { "grid-template-rows": val };
|
|
415
|
+
if (key === "area") return { "grid-area": val };
|
|
416
|
+
if (key === "template") return { "grid-template": val };
|
|
417
|
+
if (key === "templateAreas") return { "grid-template-areas": val };
|
|
418
|
+
if (key === "autoColumns") return { "grid-auto-columns": val };
|
|
419
|
+
if (key === "autoRows") return { "grid-auto-rows": val };
|
|
420
|
+
if (key === "autoFlow") return { "grid-auto-flow": val };
|
|
421
|
+
if (key === "columnStart") return { "grid-column-start": val };
|
|
422
|
+
if (key === "rowStart") return { "grid-row-start": val };
|
|
172
423
|
return null;
|
|
173
424
|
};
|
|
174
|
-
const resolveInnerProps = (obj,
|
|
425
|
+
const resolveInnerProps = (obj, ds) => {
|
|
175
426
|
const result = {};
|
|
176
427
|
for (const k in obj) {
|
|
177
428
|
const v = obj[k];
|
|
178
429
|
const expanded = resolveShorthand(k, v);
|
|
179
430
|
if (expanded) {
|
|
180
|
-
|
|
431
|
+
for (const ek in expanded) {
|
|
432
|
+
result[ek] = resolveDSValue(ek, expanded[ek], ds);
|
|
433
|
+
}
|
|
181
434
|
continue;
|
|
182
435
|
}
|
|
183
436
|
if (typeof v !== "string" && typeof v !== "number") continue;
|
|
184
|
-
result[camelToKebab(k)] =
|
|
437
|
+
result[camelToKebab(k)] = resolveDSValue(k, v, ds);
|
|
185
438
|
}
|
|
186
439
|
return result;
|
|
187
440
|
};
|
|
188
|
-
const buildCSSFromProps = (props,
|
|
441
|
+
const buildCSSFromProps = (props, ds, mediaMap) => {
|
|
189
442
|
const base = {};
|
|
190
443
|
const mediaRules = {};
|
|
191
444
|
const pseudoRules = {};
|
|
@@ -194,13 +447,13 @@ const buildCSSFromProps = (props, colorMap, mediaMap) => {
|
|
|
194
447
|
if (key.charCodeAt(0) === 64 && typeof val === "object") {
|
|
195
448
|
const bp = mediaMap?.[key.slice(1)];
|
|
196
449
|
if (bp) {
|
|
197
|
-
const inner = resolveInnerProps(val,
|
|
450
|
+
const inner = resolveInnerProps(val, ds);
|
|
198
451
|
if (Object.keys(inner).length) mediaRules[bp] = inner;
|
|
199
452
|
}
|
|
200
453
|
continue;
|
|
201
454
|
}
|
|
202
455
|
if (key.charCodeAt(0) === 58 && typeof val === "object") {
|
|
203
|
-
const inner = resolveInnerProps(val,
|
|
456
|
+
const inner = resolveInnerProps(val, ds);
|
|
204
457
|
if (Object.keys(inner).length) pseudoRules[key] = inner;
|
|
205
458
|
continue;
|
|
206
459
|
}
|
|
@@ -209,10 +462,12 @@ const buildCSSFromProps = (props, colorMap, mediaMap) => {
|
|
|
209
462
|
if (NON_CSS_PROPS.has(key)) continue;
|
|
210
463
|
const expanded = resolveShorthand(key, val);
|
|
211
464
|
if (expanded) {
|
|
212
|
-
|
|
465
|
+
for (const ek in expanded) {
|
|
466
|
+
base[ek] = resolveDSValue(ek, expanded[ek], ds);
|
|
467
|
+
}
|
|
213
468
|
continue;
|
|
214
469
|
}
|
|
215
|
-
base[camelToKebab(key)] =
|
|
470
|
+
base[camelToKebab(key)] = resolveDSValue(key, val, ds);
|
|
216
471
|
}
|
|
217
472
|
return { base, mediaRules, pseudoRules };
|
|
218
473
|
};
|
|
@@ -231,8 +486,23 @@ const renderCSSRule = (selector, { base, mediaRules, pseudoRules }) => {
|
|
|
231
486
|
}
|
|
232
487
|
return lines.join("\n");
|
|
233
488
|
};
|
|
489
|
+
const EXTENDS_CSS = {
|
|
490
|
+
Flex: { display: "flex" },
|
|
491
|
+
InlineFlex: { display: "inline-flex" },
|
|
492
|
+
Grid: { display: "grid" },
|
|
493
|
+
InlineGrid: { display: "inline-grid" },
|
|
494
|
+
Block: { display: "block" },
|
|
495
|
+
Inline: { display: "inline" }
|
|
496
|
+
};
|
|
497
|
+
const getExtendsCSS = (el) => {
|
|
498
|
+
const exts = el.__ref?.__extends;
|
|
499
|
+
if (!exts || !Array.isArray(exts)) return null;
|
|
500
|
+
for (const ext of exts) {
|
|
501
|
+
if (EXTENDS_CSS[ext]) return EXTENDS_CSS[ext];
|
|
502
|
+
}
|
|
503
|
+
return null;
|
|
504
|
+
};
|
|
234
505
|
const extractCSS = (element, ds) => {
|
|
235
|
-
const colorMap = ds?.color || {};
|
|
236
506
|
const mediaMap = ds?.media || {};
|
|
237
507
|
const animations = ds?.animation || {};
|
|
238
508
|
const rules = [];
|
|
@@ -245,7 +515,14 @@ const extractCSS = (element, ds) => {
|
|
|
245
515
|
const cls = el.node.getAttribute?.("class");
|
|
246
516
|
if (cls && !seen.has(cls)) {
|
|
247
517
|
seen.add(cls);
|
|
248
|
-
const cssResult = buildCSSFromProps(props,
|
|
518
|
+
const cssResult = buildCSSFromProps(props, ds, mediaMap);
|
|
519
|
+
const extsCss = getExtendsCSS(el);
|
|
520
|
+
if (extsCss) {
|
|
521
|
+
for (const [k, v] of Object.entries(extsCss)) {
|
|
522
|
+
const kebab = camelToKebab(k);
|
|
523
|
+
if (!cssResult.base[kebab]) cssResult.base[kebab] = v;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
249
526
|
const has = Object.keys(cssResult.base).length || Object.keys(cssResult.mediaRules).length || Object.keys(cssResult.pseudoRules).length;
|
|
250
527
|
if (has) rules.push(renderCSSRule("." + cls.split(" ")[0], cssResult));
|
|
251
528
|
const anim = props.animation || props.animationName;
|
|
@@ -279,6 +556,7 @@ const generateResetCSS = (reset) => {
|
|
|
279
556
|
if (!reset) return "";
|
|
280
557
|
const rules = [];
|
|
281
558
|
for (const [selector, props] of Object.entries(reset)) {
|
|
559
|
+
if (!props || typeof props !== "object") continue;
|
|
282
560
|
const decls = Object.entries(props).map(([k, v]) => `${camelToKebab(k)}: ${v}`).join("; ");
|
|
283
561
|
if (decls) rules.push(`${selector} { ${decls}; }`);
|
|
284
562
|
}
|
|
@@ -289,6 +567,7 @@ const generateFontLinks = (ds) => {
|
|
|
289
567
|
const families = ds.font_family || ds.fontFamily || {};
|
|
290
568
|
const fontNames = /* @__PURE__ */ new Set();
|
|
291
569
|
for (const val of Object.values(families)) {
|
|
570
|
+
if (typeof val !== "string") continue;
|
|
292
571
|
const match = val.match(/'([^']+)'/);
|
|
293
572
|
if (match) fontNames.add(match[1]);
|
|
294
573
|
}
|
package/env.js
CHANGED
|
@@ -31,6 +31,23 @@ export const createEnv = (html = '<!DOCTYPE html><html><head></head><body></body
|
|
|
31
31
|
window.scrollTo = () => {}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Storage stubs
|
|
35
|
+
const createStorage = () => {
|
|
36
|
+
const store = {}
|
|
37
|
+
return {
|
|
38
|
+
getItem: (k) => store[k] ?? null,
|
|
39
|
+
setItem: (k, v) => { store[k] = String(v) },
|
|
40
|
+
removeItem: (k) => { delete store[k] },
|
|
41
|
+
clear: () => { for (const k in store) delete store[k] },
|
|
42
|
+
get length () { return Object.keys(store).length },
|
|
43
|
+
key: (i) => Object.keys(store)[i] ?? null
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!window.localStorage) window.localStorage = createStorage()
|
|
47
|
+
if (!window.sessionStorage) window.sessionStorage = createStorage()
|
|
48
|
+
if (!globalThis.localStorage) globalThis.localStorage = window.localStorage
|
|
49
|
+
if (!globalThis.sessionStorage) globalThis.sessionStorage = window.sessionStorage
|
|
50
|
+
|
|
34
51
|
// Expose linkedom constructors on globalThis so @domql/utils isDOMNode
|
|
35
52
|
// can use instanceof checks (it reads from globalThis.Node, etc.)
|
|
36
53
|
globalThis.window = window
|
package/hydrate.js
CHANGED
|
@@ -152,6 +152,14 @@ const renderCSS = (el, emotion, colorMap, mediaMap) => {
|
|
|
152
152
|
hasCss = true
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
// Inject CSS from extends chain (e.g. extends: 'Flex' → display: flex)
|
|
156
|
+
const extsCss = getExtendsCSS(el)
|
|
157
|
+
if (extsCss) {
|
|
158
|
+
for (const [k, v] of Object.entries(extsCss)) {
|
|
159
|
+
if (!css[k]) { css[k] = v; hasCss = true }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
155
163
|
// Handle element.style object
|
|
156
164
|
if (el.style && typeof el.style === 'object') {
|
|
157
165
|
Object.assign(css, el.style)
|
|
@@ -236,9 +244,40 @@ const NON_CSS_PROPS = new Set([
|
|
|
236
244
|
'theme', '__element', 'update'
|
|
237
245
|
])
|
|
238
246
|
|
|
247
|
+
// Map of component names to their implicit CSS from extends
|
|
248
|
+
const EXTENDS_CSS = {
|
|
249
|
+
Flex: { display: 'flex' },
|
|
250
|
+
InlineFlex: { display: 'inline-flex' },
|
|
251
|
+
Grid: { display: 'grid' },
|
|
252
|
+
InlineGrid: { display: 'inline-grid' },
|
|
253
|
+
Block: { display: 'block' },
|
|
254
|
+
Inline: { display: 'inline' }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const getExtendsCSS = (el) => {
|
|
258
|
+
const exts = el.__ref?.__extends
|
|
259
|
+
if (!exts || !Array.isArray(exts)) return null
|
|
260
|
+
for (const ext of exts) {
|
|
261
|
+
if (EXTENDS_CSS[ext]) return EXTENDS_CSS[ext]
|
|
262
|
+
}
|
|
263
|
+
return null
|
|
264
|
+
}
|
|
265
|
+
|
|
239
266
|
// DomQL shorthand props that expand to multiple CSS properties
|
|
240
267
|
const resolveShorthand = (key, val) => {
|
|
241
|
-
if (
|
|
268
|
+
if (typeof val === 'undefined' || val === null) return null
|
|
269
|
+
|
|
270
|
+
// Flex shorthands
|
|
271
|
+
if (key === 'flow' && typeof val === 'string') {
|
|
272
|
+
let [direction, wrap] = (val || 'row').split(' ')
|
|
273
|
+
if (val.startsWith('x') || val === 'row') direction = 'row'
|
|
274
|
+
if (val.startsWith('y') || val === 'column') direction = 'column'
|
|
275
|
+
return { display: 'flex', flexFlow: (direction || '') + ' ' + (wrap || '') }
|
|
276
|
+
}
|
|
277
|
+
if (key === 'wrap') {
|
|
278
|
+
return { display: 'flex', flexWrap: val }
|
|
279
|
+
}
|
|
280
|
+
if ((key === 'align' || key === 'flexAlign') && typeof val === 'string') {
|
|
242
281
|
const [alignItems, justifyContent] = val.split(' ')
|
|
243
282
|
return { display: 'flex', alignItems, justifyContent }
|
|
244
283
|
}
|
|
@@ -246,12 +285,49 @@ const resolveShorthand = (key, val) => {
|
|
|
246
285
|
const [alignItems, justifyContent] = val.split(' ')
|
|
247
286
|
return { display: 'grid', alignItems, justifyContent }
|
|
248
287
|
}
|
|
249
|
-
if (key === '
|
|
288
|
+
if (key === 'flexFlow' && typeof val === 'string') {
|
|
289
|
+
let [direction, wrap] = (val || 'row').split(' ')
|
|
290
|
+
if (val.startsWith('x') || val === 'row') direction = 'row'
|
|
291
|
+
if (val.startsWith('y') || val === 'column') direction = 'column'
|
|
292
|
+
return { display: 'flex', flexFlow: (direction || '') + ' ' + (wrap || '') }
|
|
293
|
+
}
|
|
294
|
+
if (key === 'flexWrap') {
|
|
295
|
+
return { display: 'flex', flexWrap: val }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Box/size shorthands
|
|
299
|
+
if (key === 'round' || (key === 'borderRadius' && val)) {
|
|
250
300
|
return { borderRadius: typeof val === 'number' ? val + 'px' : val }
|
|
251
301
|
}
|
|
252
|
-
if (key === 'boxSize' && val) {
|
|
253
|
-
|
|
302
|
+
if (key === 'boxSize' && typeof val === 'string') {
|
|
303
|
+
const [height, width] = val.split(' ')
|
|
304
|
+
return { height, width: width || height }
|
|
254
305
|
}
|
|
306
|
+
if (key === 'widthRange' && typeof val === 'string') {
|
|
307
|
+
const [minWidth, maxWidth] = val.split(' ')
|
|
308
|
+
return { minWidth, maxWidth: maxWidth || minWidth }
|
|
309
|
+
}
|
|
310
|
+
if (key === 'heightRange' && typeof val === 'string') {
|
|
311
|
+
const [minHeight, maxHeight] = val.split(' ')
|
|
312
|
+
return { minHeight, maxHeight: maxHeight || minHeight }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Grid aliases
|
|
316
|
+
if (key === 'column') return { gridColumn: val }
|
|
317
|
+
if (key === 'columns') return { gridTemplateColumns: val }
|
|
318
|
+
if (key === 'templateColumns') return { gridTemplateColumns: val }
|
|
319
|
+
if (key === 'row') return { gridRow: val }
|
|
320
|
+
if (key === 'rows') return { gridTemplateRows: val }
|
|
321
|
+
if (key === 'templateRows') return { gridTemplateRows: val }
|
|
322
|
+
if (key === 'area') return { gridArea: val }
|
|
323
|
+
if (key === 'template') return { gridTemplate: val }
|
|
324
|
+
if (key === 'templateAreas') return { gridTemplateAreas: val }
|
|
325
|
+
if (key === 'autoColumns') return { gridAutoColumns: val }
|
|
326
|
+
if (key === 'autoRows') return { gridAutoRows: val }
|
|
327
|
+
if (key === 'autoFlow') return { gridAutoFlow: val }
|
|
328
|
+
if (key === 'columnStart') return { gridColumnStart: val }
|
|
329
|
+
if (key === 'rowStart') return { gridRowStart: val }
|
|
330
|
+
|
|
255
331
|
return null
|
|
256
332
|
}
|
|
257
333
|
|
|
@@ -289,7 +365,15 @@ const CSS_PROPERTIES = new Set([
|
|
|
289
365
|
'gap', 'rowGap', 'columnGap',
|
|
290
366
|
'gridTemplateColumns', 'gridTemplateRows', 'gridColumn', 'gridRow',
|
|
291
367
|
'gridArea', 'gridAutoFlow', 'gridAutoColumns', 'gridAutoRows',
|
|
292
|
-
'
|
|
368
|
+
'inset',
|
|
369
|
+
'inlineSize', 'blockSize', 'minInlineSize', 'maxInlineSize', 'minBlockSize', 'maxBlockSize',
|
|
370
|
+
'paddingBlockStart', 'paddingBlockEnd', 'paddingInlineStart', 'paddingInlineEnd',
|
|
371
|
+
'marginBlockStart', 'marginBlockEnd', 'marginInlineStart', 'marginInlineEnd',
|
|
372
|
+
'transform', 'transformOrigin', 'transition',
|
|
373
|
+
'animation', 'animationName', 'animationDuration', 'animationDelay',
|
|
374
|
+
'animationTimingFunction', 'animationFillMode', 'animationIterationCount',
|
|
375
|
+
'animationPlayState', 'animationDirection',
|
|
376
|
+
'gridTemplate', 'gridTemplateAreas', 'gridColumnStart', 'gridRowStart',
|
|
293
377
|
'boxShadow', 'outline', 'outlineColor', 'outlineWidth', 'outlineStyle', 'outlineOffset',
|
|
294
378
|
'whiteSpace', 'wordBreak', 'wordWrap', 'overflowWrap',
|
|
295
379
|
'visibility', 'boxSizing', 'objectFit', 'objectPosition',
|
package/load.js
CHANGED
|
@@ -1,9 +1,56 @@
|
|
|
1
1
|
import { resolve, join } from 'path'
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'fs'
|
|
3
|
+
import { tmpdir } from 'os'
|
|
4
|
+
import { randomBytes } from 'crypto'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Bundles a module entry point with esbuild so that extensionless imports,
|
|
8
|
+
* bare specifiers, and other bundler conventions resolve correctly.
|
|
9
|
+
* Returns the default + named exports of the bundled module, or null on failure.
|
|
10
|
+
*/
|
|
11
|
+
const bundleAndImport = async (entryPath) => {
|
|
12
|
+
if (!existsSync(entryPath)) return null
|
|
13
|
+
|
|
14
|
+
let esbuild
|
|
15
|
+
try {
|
|
16
|
+
esbuild = await import('esbuild')
|
|
17
|
+
} catch {
|
|
18
|
+
// Fallback: try raw import if esbuild is not available
|
|
19
|
+
try { return await import(entryPath) } catch { return null }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const outFile = join(tmpdir(), `brender_${randomBytes(8).toString('hex')}.mjs`)
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await esbuild.build({
|
|
26
|
+
entryPoints: [entryPath],
|
|
27
|
+
bundle: true,
|
|
28
|
+
format: 'esm',
|
|
29
|
+
platform: 'node',
|
|
30
|
+
outfile: outFile,
|
|
31
|
+
write: true,
|
|
32
|
+
logLevel: 'silent',
|
|
33
|
+
// Mark node builtins as external
|
|
34
|
+
external: ['fs', 'path', 'os', 'crypto', 'url', 'http', 'https', 'stream', 'util', 'events', 'buffer', 'child_process', 'worker_threads', 'net', 'tls', 'dns', 'dgram', 'zlib', 'assert', 'querystring', 'string_decoder', 'readline', 'perf_hooks', 'async_hooks', 'v8', 'vm', 'cluster', 'inspector', 'module', 'process', 'tty'],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const mod = await import(`file://${outFile}`)
|
|
38
|
+
return mod
|
|
39
|
+
} catch {
|
|
40
|
+
// Fallback: try raw import
|
|
41
|
+
try { return await import(entryPath) } catch { return null }
|
|
42
|
+
} finally {
|
|
43
|
+
try { unlinkSync(outFile) } catch {}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
2
46
|
|
|
3
47
|
/**
|
|
4
48
|
* Loads a Symbols project from a filesystem path.
|
|
5
49
|
* Expects the standard symbols/ directory structure.
|
|
6
50
|
*
|
|
51
|
+
* Uses esbuild to bundle each module so that extensionless imports
|
|
52
|
+
* and other bundler conventions work in Node.js.
|
|
53
|
+
*
|
|
7
54
|
* Used for prebuild scenarios where brender runs locally
|
|
8
55
|
* against a project directory (e.g. `smbls build --prerender`).
|
|
9
56
|
*
|
|
@@ -13,14 +60,6 @@ import { resolve, join } from 'path'
|
|
|
13
60
|
export const loadProject = async (projectPath) => {
|
|
14
61
|
const symbolsDir = resolve(projectPath, 'symbols')
|
|
15
62
|
|
|
16
|
-
const tryImport = async (modulePath) => {
|
|
17
|
-
try {
|
|
18
|
-
return await import(modulePath)
|
|
19
|
-
} catch {
|
|
20
|
-
return null
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
63
|
const [
|
|
25
64
|
appModule,
|
|
26
65
|
stateModule,
|
|
@@ -34,17 +73,17 @@ export const loadProject = async (projectPath) => {
|
|
|
34
73
|
designSystemModule,
|
|
35
74
|
filesModule
|
|
36
75
|
] = await Promise.all([
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
76
|
+
bundleAndImport(join(symbolsDir, 'app.js')),
|
|
77
|
+
bundleAndImport(join(symbolsDir, 'state.js')),
|
|
78
|
+
bundleAndImport(join(symbolsDir, 'config.js')),
|
|
79
|
+
bundleAndImport(join(symbolsDir, 'dependencies.js')),
|
|
80
|
+
bundleAndImport(join(symbolsDir, 'components', 'index.js')),
|
|
81
|
+
bundleAndImport(join(symbolsDir, 'snippets', 'index.js')),
|
|
82
|
+
bundleAndImport(join(symbolsDir, 'pages', 'index.js')),
|
|
83
|
+
bundleAndImport(join(symbolsDir, 'functions', 'index.js')),
|
|
84
|
+
bundleAndImport(join(symbolsDir, 'methods', 'index.js')),
|
|
85
|
+
bundleAndImport(join(symbolsDir, 'designSystem', 'index.js')),
|
|
86
|
+
bundleAndImport(join(symbolsDir, 'files', 'index.js'))
|
|
48
87
|
])
|
|
49
88
|
|
|
50
89
|
return {
|