@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/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/brender",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
7
7
|
"main": "./dist/cjs/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"import": "./
|
|
10
|
+
"import": "./index.js",
|
|
11
11
|
"require": "./dist/cjs/index.js",
|
|
12
|
-
"default": "./
|
|
12
|
+
"default": "./index.js"
|
|
13
13
|
},
|
|
14
14
|
"./hydrate": {
|
|
15
15
|
"import": "./hydrate.js",
|
package/render.js
CHANGED
|
@@ -4,6 +4,65 @@ import { extractMetadata, generateHeadHtml } from './metadata.js'
|
|
|
4
4
|
import { hydrate } from './hydrate.js'
|
|
5
5
|
import { parseHTML } from 'linkedom'
|
|
6
6
|
|
|
7
|
+
// ── Minimal uikit stubs ──────────────────────────────────────────────────────
|
|
8
|
+
// Lightweight versions of uikit components so DOMQL can resolve extends chains
|
|
9
|
+
// (tag, display, attrs) without importing the full @symbo.ls/uikit package.
|
|
10
|
+
const UIKIT_STUBS = {
|
|
11
|
+
Box: {},
|
|
12
|
+
Focusable: {},
|
|
13
|
+
Block: { display: 'block' },
|
|
14
|
+
Inline: { display: 'inline' },
|
|
15
|
+
Flex: { display: 'flex' },
|
|
16
|
+
InlineFlex: { display: 'inline-flex' },
|
|
17
|
+
Grid: { display: 'grid' },
|
|
18
|
+
InlineGrid: { display: 'inline-grid' },
|
|
19
|
+
Link: {
|
|
20
|
+
tag: 'a',
|
|
21
|
+
attr: {
|
|
22
|
+
href: (el) => el.props?.href,
|
|
23
|
+
target: (el) => el.props?.target,
|
|
24
|
+
rel: (el) => el.props?.rel
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
A: { extends: 'Link' },
|
|
28
|
+
RouteLink: { extends: 'Link' },
|
|
29
|
+
Img: {
|
|
30
|
+
tag: 'img',
|
|
31
|
+
attr: {
|
|
32
|
+
src: (el) => {
|
|
33
|
+
let src = el.props?.src
|
|
34
|
+
if (typeof src === 'string' && src.includes('{{')) {
|
|
35
|
+
src = el.call('replaceLiteralsWithObjectFields', src, el.state)
|
|
36
|
+
}
|
|
37
|
+
return src
|
|
38
|
+
},
|
|
39
|
+
alt: (el) => el.props?.alt,
|
|
40
|
+
loading: (el) => el.props?.loading
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
Image: { extends: 'Img' },
|
|
44
|
+
Button: { tag: 'button' },
|
|
45
|
+
FocusableComponent: { tag: 'button' },
|
|
46
|
+
Form: { tag: 'form' },
|
|
47
|
+
Input: { tag: 'input' },
|
|
48
|
+
TextArea: { tag: 'textarea' },
|
|
49
|
+
Textarea: { tag: 'textarea' },
|
|
50
|
+
Select: { tag: 'select' },
|
|
51
|
+
Label: { tag: 'label' },
|
|
52
|
+
Iframe: { tag: 'iframe' },
|
|
53
|
+
Video: { tag: 'video' },
|
|
54
|
+
Audio: { tag: 'audio' },
|
|
55
|
+
Canvas: { tag: 'canvas' },
|
|
56
|
+
Span: { tag: 'span' },
|
|
57
|
+
P: { tag: 'p' },
|
|
58
|
+
H1: { tag: 'h1' },
|
|
59
|
+
H2: { tag: 'h2' },
|
|
60
|
+
H3: { tag: 'h3' },
|
|
61
|
+
H4: { tag: 'h4' },
|
|
62
|
+
H5: { tag: 'h5' },
|
|
63
|
+
H6: { tag: 'h6' }
|
|
64
|
+
}
|
|
65
|
+
|
|
7
66
|
/**
|
|
8
67
|
* Renders a Symbols/DomQL project to HTML on the server.
|
|
9
68
|
*
|
|
@@ -88,15 +147,35 @@ export const renderElement = async (elementDef, options = {}) => {
|
|
|
88
147
|
const body = document.body
|
|
89
148
|
|
|
90
149
|
const { create } = await import('@domql/element')
|
|
150
|
+
const domqlUtils = await import('@domql/utils')
|
|
151
|
+
|
|
152
|
+
// Merge minimal uikit stubs so DOMQL resolves extends chains
|
|
153
|
+
// (e.g. extends: 'Link' → tag: 'a', extends: 'Flex' → display: flex)
|
|
154
|
+
const components = { ...UIKIT_STUBS, ...(context.components || {}) }
|
|
155
|
+
|
|
156
|
+
// Register utility functions so element.call() can resolve them
|
|
157
|
+
// (e.g. replaceLiteralsWithObjectFields for {{ }} templates)
|
|
158
|
+
const utils = {
|
|
159
|
+
...domqlUtils,
|
|
160
|
+
...(context.utils || {}),
|
|
161
|
+
...(context.functions || {})
|
|
162
|
+
}
|
|
91
163
|
|
|
92
164
|
resetKeys()
|
|
93
165
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
166
|
+
let element
|
|
167
|
+
try {
|
|
168
|
+
element = create(elementDef, { node: body }, 'root', {
|
|
169
|
+
context: { document, window, ...context, components, utils }
|
|
170
|
+
})
|
|
171
|
+
} catch (err) {
|
|
172
|
+
// Lifecycle events (onRender, onDone, etc.) may throw in SSR
|
|
173
|
+
// because they access browser-only APIs. The DOM tree is built
|
|
174
|
+
// before these fire, so we can still extract HTML.
|
|
175
|
+
}
|
|
97
176
|
|
|
98
177
|
assignKeys(body)
|
|
99
|
-
const registry = mapKeysToElements(element)
|
|
178
|
+
const registry = element ? mapKeysToElements(element) : {}
|
|
100
179
|
const html = body.innerHTML
|
|
101
180
|
|
|
102
181
|
return { html, registry, element }
|
|
@@ -198,6 +277,120 @@ ${result.html}
|
|
|
198
277
|
return { html, route, brKeyCount: result.brKeyCount }
|
|
199
278
|
}
|
|
200
279
|
|
|
280
|
+
// ── Design system token resolution ──────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
const LETTER_TO_INDEX = {
|
|
283
|
+
U: -6, V: -5, W: -4, X: -3, Y: -2, Z: -1,
|
|
284
|
+
A: 0, B: 1, C: 2, D: 3, E: 4, F: 5, G: 6, H: 7, I: 8, J: 9,
|
|
285
|
+
K: 10, L: 11, M: 12, N: 13, O: 14, P: 15
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const SPACING_PROPS = new Set([
|
|
289
|
+
'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
|
|
290
|
+
'paddingBlock', 'paddingInline', 'paddingBlockStart', 'paddingBlockEnd',
|
|
291
|
+
'paddingInlineStart', 'paddingInlineEnd',
|
|
292
|
+
'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
|
|
293
|
+
'marginBlock', 'marginInline', 'marginBlockStart', 'marginBlockEnd',
|
|
294
|
+
'marginInlineStart', 'marginInlineEnd',
|
|
295
|
+
'gap', 'rowGap', 'columnGap',
|
|
296
|
+
'top', 'right', 'bottom', 'left',
|
|
297
|
+
'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',
|
|
298
|
+
'flexBasis', 'fontSize', 'lineHeight', 'letterSpacing',
|
|
299
|
+
'borderWidth', 'borderRadius', 'outlineWidth', 'outlineOffset',
|
|
300
|
+
'inset', 'insetBlock', 'insetInline',
|
|
301
|
+
'boxSize', 'round'
|
|
302
|
+
])
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Resolves a spacing token like 'B2', 'A', 'E3' to a px/em value.
|
|
306
|
+
* Uses base * ratio^index for main steps (A=0, B=1, etc.)
|
|
307
|
+
* and sub-ratio interpolation for sub-steps (B1, B2, B3).
|
|
308
|
+
*/
|
|
309
|
+
const resolveSpacingToken = (token, spacingConfig) => {
|
|
310
|
+
if (!token || typeof token !== 'string') return null
|
|
311
|
+
if (!spacingConfig) return null
|
|
312
|
+
|
|
313
|
+
const base = spacingConfig.base || 16
|
|
314
|
+
const ratio = spacingConfig.ratio || 1.618
|
|
315
|
+
const unit = spacingConfig.unit || 'px'
|
|
316
|
+
const hasSubSequence = spacingConfig.subSequence !== false
|
|
317
|
+
|
|
318
|
+
// Handle compound values like 'B2 - -' or 'A1 B C1'
|
|
319
|
+
if (token.includes(' ')) {
|
|
320
|
+
const parts = token.split(' ').map(part => {
|
|
321
|
+
if (part === '-' || part === '') return part
|
|
322
|
+
return resolveSpacingToken(part, spacingConfig) || part
|
|
323
|
+
})
|
|
324
|
+
return parts.join(' ')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Skip CSS keywords and values with units
|
|
328
|
+
if (/^(none|auto|inherit|initial|unset|0)$/i.test(token)) return null
|
|
329
|
+
if (/\d+(px|em|rem|%|vh|vw|vmin|vmax|ch|ex|cm|mm|in|pt|pc|fr|s|ms)$/i.test(token)) return null
|
|
330
|
+
// Skip hex colors, rgb(), etc.
|
|
331
|
+
if (/^(#|rgb|hsl|var\()/i.test(token)) return null
|
|
332
|
+
|
|
333
|
+
const isNegative = token.startsWith('-')
|
|
334
|
+
const abs = isNegative ? token.slice(1) : token
|
|
335
|
+
|
|
336
|
+
// Match letter + optional digit: A, B, B2, E3, etc.
|
|
337
|
+
const m = abs.match(/^([A-Z])(\d)?$/i)
|
|
338
|
+
if (!m) return null
|
|
339
|
+
|
|
340
|
+
const letter = m[1].toUpperCase()
|
|
341
|
+
const subStep = m[2] ? parseInt(m[2]) : 0
|
|
342
|
+
const idx = LETTER_TO_INDEX[letter]
|
|
343
|
+
if (idx === undefined) return null
|
|
344
|
+
|
|
345
|
+
let value = base * Math.pow(ratio, idx)
|
|
346
|
+
|
|
347
|
+
if (subStep > 0 && hasSubSequence) {
|
|
348
|
+
const next = base * Math.pow(ratio, idx + 1)
|
|
349
|
+
const diff = next - value
|
|
350
|
+
const subRatio = diff / ratio
|
|
351
|
+
// Sub-steps: 1 = value + (diff - subRatio), 2 = midpoint, 3 = value + subRatio
|
|
352
|
+
const first = next - subRatio
|
|
353
|
+
const second = value + subRatio
|
|
354
|
+
const middle = (first + second) / 2
|
|
355
|
+
const subs = (~~next - ~~value > 16) ? [first, middle, second] : [first, second]
|
|
356
|
+
if (subStep <= subs.length) {
|
|
357
|
+
value = subs[subStep - 1]
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const rounded = Math.round(value * 100) / 100
|
|
362
|
+
const sign = isNegative ? '-' : ''
|
|
363
|
+
return `${sign}${rounded}${unit}`
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Kebab-case versions of spacing props for post-shorthand resolution
|
|
367
|
+
const SPACING_PROPS_KEBAB = new Set(
|
|
368
|
+
[...SPACING_PROPS].map(k => k.replace(/[A-Z]/g, m => '-' + m.toLowerCase()))
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Try to resolve a CSS value through the design system.
|
|
373
|
+
* Returns the resolved value or the original if not a token.
|
|
374
|
+
*/
|
|
375
|
+
const resolveDSValue = (key, val, ds) => {
|
|
376
|
+
if (typeof val !== 'string') return val
|
|
377
|
+
|
|
378
|
+
// Color resolution
|
|
379
|
+
if (CSS_COLOR_PROPS.has(key)) {
|
|
380
|
+
const colorMap = ds?.color || {}
|
|
381
|
+
if (colorMap[val]) return colorMap[val]
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Spacing resolution (check both camelCase and kebab-case keys)
|
|
385
|
+
if (SPACING_PROPS.has(key) || SPACING_PROPS_KEBAB.has(key)) {
|
|
386
|
+
const spacing = ds?.spacing || {}
|
|
387
|
+
const resolved = resolveSpacingToken(val, spacing)
|
|
388
|
+
if (resolved) return resolved
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return val
|
|
392
|
+
}
|
|
393
|
+
|
|
201
394
|
// ── CSS helpers ─────────────────────────────────────────────────────────────
|
|
202
395
|
|
|
203
396
|
const CSS_COLOR_PROPS = new Set([
|
|
@@ -210,42 +403,106 @@ const NON_CSS_PROPS = new Set([
|
|
|
210
403
|
'href', 'src', 'alt', 'title', 'id', 'name', 'type', 'value', 'placeholder',
|
|
211
404
|
'target', 'rel', 'loading', 'srcset', 'sizes', 'media', 'role', 'tabindex',
|
|
212
405
|
'for', 'action', 'method', 'enctype', 'autocomplete', 'autofocus',
|
|
213
|
-
'theme', '__element', 'update'
|
|
406
|
+
'theme', '__element', 'update',
|
|
407
|
+
'childrenAs', 'childExtends', 'childProps', 'children'
|
|
214
408
|
])
|
|
215
409
|
|
|
216
410
|
const camelToKebab = (str) => str.replace(/[A-Z]/g, m => '-' + m.toLowerCase())
|
|
217
411
|
|
|
218
412
|
const resolveShorthand = (key, val) => {
|
|
219
|
-
if (
|
|
413
|
+
if (typeof val === 'undefined' || val === null) return null
|
|
414
|
+
|
|
415
|
+
// Flex shorthands
|
|
416
|
+
if (key === 'flow' && typeof val === 'string') {
|
|
417
|
+
let [direction, wrap] = (val || 'row').split(' ')
|
|
418
|
+
if (val.startsWith('x') || val === 'row') direction = 'row'
|
|
419
|
+
if (val.startsWith('y') || val === 'column') direction = 'column'
|
|
420
|
+
return { display: 'flex', 'flex-flow': (direction || '') + ' ' + (wrap || '') }
|
|
421
|
+
}
|
|
422
|
+
if (key === 'wrap') {
|
|
423
|
+
return { display: 'flex', 'flex-wrap': val }
|
|
424
|
+
}
|
|
425
|
+
if ((key === 'align' || key === 'flexAlign') && typeof val === 'string') {
|
|
220
426
|
const [alignItems, justifyContent] = val.split(' ')
|
|
221
|
-
|
|
427
|
+
const result = { display: 'flex', 'align-items': alignItems }
|
|
428
|
+
if (justifyContent) result['justify-content'] = justifyContent
|
|
429
|
+
return result
|
|
222
430
|
}
|
|
223
431
|
if (key === 'gridAlign' && typeof val === 'string') {
|
|
224
432
|
const [alignItems, justifyContent] = val.split(' ')
|
|
225
|
-
|
|
433
|
+
const result = { display: 'grid', 'align-items': alignItems }
|
|
434
|
+
if (justifyContent) result['justify-content'] = justifyContent
|
|
435
|
+
return result
|
|
436
|
+
}
|
|
437
|
+
if (key === 'flexFlow' && typeof val === 'string') {
|
|
438
|
+
let [direction, wrap] = (val || 'row').split(' ')
|
|
439
|
+
if (val.startsWith('x') || val === 'row') direction = 'row'
|
|
440
|
+
if (val.startsWith('y') || val === 'column') direction = 'column'
|
|
441
|
+
return { display: 'flex', 'flex-flow': (direction || '') + ' ' + (wrap || '') }
|
|
442
|
+
}
|
|
443
|
+
if (key === 'flexWrap') {
|
|
444
|
+
return { display: 'flex', 'flex-wrap': val }
|
|
226
445
|
}
|
|
227
|
-
|
|
446
|
+
|
|
447
|
+
// Background image shorthand
|
|
448
|
+
if (key === 'backgroundImage' && typeof val === 'string' && !val.startsWith('url(') && !val.startsWith('linear-gradient') && !val.startsWith('radial-gradient') && !val.startsWith('none')) {
|
|
449
|
+
return { 'background-image': `url(${val})` }
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Box/size shorthands
|
|
453
|
+
if (key === 'round' || (key === 'borderRadius' && val)) {
|
|
228
454
|
return { 'border-radius': typeof val === 'number' ? val + 'px' : val }
|
|
229
455
|
}
|
|
230
|
-
if (key === 'boxSize' && val) {
|
|
231
|
-
|
|
456
|
+
if (key === 'boxSize' && typeof val === 'string') {
|
|
457
|
+
const [height, width] = val.split(' ')
|
|
458
|
+
return { height, width: width || height }
|
|
459
|
+
}
|
|
460
|
+
if (key === 'widthRange' && typeof val === 'string') {
|
|
461
|
+
const [minWidth, maxWidth] = val.split(' ')
|
|
462
|
+
return { 'min-width': minWidth, 'max-width': maxWidth || minWidth }
|
|
232
463
|
}
|
|
464
|
+
if (key === 'heightRange' && typeof val === 'string') {
|
|
465
|
+
const [minHeight, maxHeight] = val.split(' ')
|
|
466
|
+
return { 'min-height': minHeight, 'max-height': maxHeight || minHeight }
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Grid aliases
|
|
470
|
+
if (key === 'column') return { 'grid-column': val }
|
|
471
|
+
if (key === 'columns') return { 'grid-template-columns': val }
|
|
472
|
+
if (key === 'templateColumns') return { 'grid-template-columns': val }
|
|
473
|
+
if (key === 'row') return { 'grid-row': val }
|
|
474
|
+
if (key === 'rows') return { 'grid-template-rows': val }
|
|
475
|
+
if (key === 'templateRows') return { 'grid-template-rows': val }
|
|
476
|
+
if (key === 'area') return { 'grid-area': val }
|
|
477
|
+
if (key === 'template') return { 'grid-template': val }
|
|
478
|
+
if (key === 'templateAreas') return { 'grid-template-areas': val }
|
|
479
|
+
if (key === 'autoColumns') return { 'grid-auto-columns': val }
|
|
480
|
+
if (key === 'autoRows') return { 'grid-auto-rows': val }
|
|
481
|
+
if (key === 'autoFlow') return { 'grid-auto-flow': val }
|
|
482
|
+
if (key === 'columnStart') return { 'grid-column-start': val }
|
|
483
|
+
if (key === 'rowStart') return { 'grid-row-start': val }
|
|
484
|
+
|
|
233
485
|
return null
|
|
234
486
|
}
|
|
235
487
|
|
|
236
|
-
const resolveInnerProps = (obj,
|
|
488
|
+
const resolveInnerProps = (obj, ds) => {
|
|
237
489
|
const result = {}
|
|
238
490
|
for (const k in obj) {
|
|
239
491
|
const v = obj[k]
|
|
240
492
|
const expanded = resolveShorthand(k, v)
|
|
241
|
-
if (expanded) {
|
|
493
|
+
if (expanded) {
|
|
494
|
+
for (const ek in expanded) {
|
|
495
|
+
result[ek] = resolveDSValue(ek, expanded[ek], ds)
|
|
496
|
+
}
|
|
497
|
+
continue
|
|
498
|
+
}
|
|
242
499
|
if (typeof v !== 'string' && typeof v !== 'number') continue
|
|
243
|
-
result[camelToKebab(k)] =
|
|
500
|
+
result[camelToKebab(k)] = resolveDSValue(k, v, ds)
|
|
244
501
|
}
|
|
245
502
|
return result
|
|
246
503
|
}
|
|
247
504
|
|
|
248
|
-
const buildCSSFromProps = (props,
|
|
505
|
+
const buildCSSFromProps = (props, ds, mediaMap) => {
|
|
249
506
|
const base = {}
|
|
250
507
|
const mediaRules = {}
|
|
251
508
|
const pseudoRules = {}
|
|
@@ -256,14 +513,14 @@ const buildCSSFromProps = (props, colorMap, mediaMap) => {
|
|
|
256
513
|
if (key.charCodeAt(0) === 64 && typeof val === 'object') {
|
|
257
514
|
const bp = mediaMap?.[key.slice(1)]
|
|
258
515
|
if (bp) {
|
|
259
|
-
const inner = resolveInnerProps(val,
|
|
516
|
+
const inner = resolveInnerProps(val, ds)
|
|
260
517
|
if (Object.keys(inner).length) mediaRules[bp] = inner
|
|
261
518
|
}
|
|
262
519
|
continue
|
|
263
520
|
}
|
|
264
521
|
|
|
265
522
|
if (key.charCodeAt(0) === 58 && typeof val === 'object') {
|
|
266
|
-
const inner = resolveInnerProps(val,
|
|
523
|
+
const inner = resolveInnerProps(val, ds)
|
|
267
524
|
if (Object.keys(inner).length) pseudoRules[key] = inner
|
|
268
525
|
continue
|
|
269
526
|
}
|
|
@@ -273,9 +530,14 @@ const buildCSSFromProps = (props, colorMap, mediaMap) => {
|
|
|
273
530
|
if (NON_CSS_PROPS.has(key)) continue
|
|
274
531
|
|
|
275
532
|
const expanded = resolveShorthand(key, val)
|
|
276
|
-
if (expanded) {
|
|
533
|
+
if (expanded) {
|
|
534
|
+
for (const ek in expanded) {
|
|
535
|
+
base[ek] = resolveDSValue(ek, expanded[ek], ds)
|
|
536
|
+
}
|
|
537
|
+
continue
|
|
538
|
+
}
|
|
277
539
|
|
|
278
|
-
base[camelToKebab(key)] =
|
|
540
|
+
base[camelToKebab(key)] = resolveDSValue(key, val, ds)
|
|
279
541
|
}
|
|
280
542
|
|
|
281
543
|
return { base, mediaRules, pseudoRules }
|
|
@@ -300,8 +562,26 @@ const renderCSSRule = (selector, { base, mediaRules, pseudoRules }) => {
|
|
|
300
562
|
return lines.join('\n')
|
|
301
563
|
}
|
|
302
564
|
|
|
565
|
+
// Map of component names to their implicit CSS from extends
|
|
566
|
+
const EXTENDS_CSS = {
|
|
567
|
+
Flex: { display: 'flex' },
|
|
568
|
+
InlineFlex: { display: 'inline-flex' },
|
|
569
|
+
Grid: { display: 'grid' },
|
|
570
|
+
InlineGrid: { display: 'inline-grid' },
|
|
571
|
+
Block: { display: 'block' },
|
|
572
|
+
Inline: { display: 'inline' }
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const getExtendsCSS = (el) => {
|
|
576
|
+
const exts = el.__ref?.__extends
|
|
577
|
+
if (!exts || !Array.isArray(exts)) return null
|
|
578
|
+
for (const ext of exts) {
|
|
579
|
+
if (EXTENDS_CSS[ext]) return EXTENDS_CSS[ext]
|
|
580
|
+
}
|
|
581
|
+
return null
|
|
582
|
+
}
|
|
583
|
+
|
|
303
584
|
const extractCSS = (element, ds) => {
|
|
304
|
-
const colorMap = ds?.color || {}
|
|
305
585
|
const mediaMap = ds?.media || {}
|
|
306
586
|
const animations = ds?.animation || {}
|
|
307
587
|
const rules = []
|
|
@@ -315,7 +595,17 @@ const extractCSS = (element, ds) => {
|
|
|
315
595
|
const cls = el.node.getAttribute?.('class')
|
|
316
596
|
if (cls && !seen.has(cls)) {
|
|
317
597
|
seen.add(cls)
|
|
318
|
-
const cssResult = buildCSSFromProps(props,
|
|
598
|
+
const cssResult = buildCSSFromProps(props, ds, mediaMap)
|
|
599
|
+
|
|
600
|
+
// Inject CSS from extends chain (e.g. extends: 'Flex' → display: flex)
|
|
601
|
+
const extsCss = getExtendsCSS(el)
|
|
602
|
+
if (extsCss) {
|
|
603
|
+
for (const [k, v] of Object.entries(extsCss)) {
|
|
604
|
+
const kebab = camelToKebab(k)
|
|
605
|
+
if (!cssResult.base[kebab]) cssResult.base[kebab] = v
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
319
609
|
const has = Object.keys(cssResult.base).length || Object.keys(cssResult.mediaRules).length || Object.keys(cssResult.pseudoRules).length
|
|
320
610
|
if (has) rules.push(renderCSSRule('.' + cls.split(' ')[0], cssResult))
|
|
321
611
|
|
|
@@ -351,6 +641,7 @@ const generateResetCSS = (reset) => {
|
|
|
351
641
|
if (!reset) return ''
|
|
352
642
|
const rules = []
|
|
353
643
|
for (const [selector, props] of Object.entries(reset)) {
|
|
644
|
+
if (!props || typeof props !== 'object') continue
|
|
354
645
|
const decls = Object.entries(props)
|
|
355
646
|
.map(([k, v]) => `${camelToKebab(k)}: ${v}`)
|
|
356
647
|
.join('; ')
|
|
@@ -366,6 +657,7 @@ const generateFontLinks = (ds) => {
|
|
|
366
657
|
|
|
367
658
|
// Collect font family names from the design system
|
|
368
659
|
for (const val of Object.values(families)) {
|
|
660
|
+
if (typeof val !== 'string') continue
|
|
369
661
|
const match = val.match(/'([^']+)'/)
|
|
370
662
|
if (match) fontNames.add(match[1])
|
|
371
663
|
}
|