smbls 3.14.2 → 3.14.6

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,125 +0,0 @@
1
- 'use strict'
2
-
3
- import { isArray, isFunction, isObject, window, isDevelopment, overwriteDeep, deepDestringifyFunctions } from '@symbo.ls/utils'
4
-
5
- const IS_DEVELOPMENT =
6
- window && window.location
7
- ? window.location.host.includes('dev.')
8
- : isDevelopment()
9
-
10
- const SERVER_URL = IS_DEVELOPMENT
11
- ? 'http://localhost:8080/get'
12
- : 'https://api.symbols.app/get'
13
-
14
- const defaultOptions = {
15
- endpoint: SERVER_URL
16
- }
17
-
18
- const fetchRemote = async (key, options = defaultOptions) => {
19
- const baseUrl = options.endpoint || SERVER_URL
20
- const route = options.serviceRoute
21
- ? isArray(options.serviceRoute)
22
- ? options.serviceRoute.map((v) => v.toLowerCase() + '=true').join('&')
23
- : options.serviceRoute
24
- : ''
25
-
26
- let response
27
- try {
28
- response = await globalThis.fetch(baseUrl + '/' + '?' + route, {
29
- method: 'GET',
30
- headers: {
31
- 'Content-Type': 'application/json',
32
- 'X-AppKey': key,
33
- 'X-Metadata': options.metadata
34
- }
35
- })
36
-
37
- return await response.json()
38
- } catch (e) {
39
- if (isFunction(options.onError)) return options.onError(e)
40
- else console.error(e)
41
- }
42
- }
43
-
44
- const fetchProject = async (key, options) => {
45
- const { editor } = options
46
-
47
- if (editor && editor.remote) {
48
- const data = await fetchRemote(key, editor)
49
- const evalData =
50
- IS_DEVELOPMENT || options.isDevelopment
51
- ? deepDestringifyFunctions(data)
52
- : deepDestringifyFunctions(data.releases[0])
53
-
54
- if (editor.serviceRoute) {
55
- if (isArray(editor.serviceRoute)) {
56
- editor.serviceRoute.forEach((route) => {
57
- overwriteDeep(options[route], evalData[route.toLowerCase()])
58
- })
59
- } else {
60
- overwriteDeep(options[editor.serviceRoute], evalData)
61
- }
62
- } else {
63
- ;[
64
- 'state',
65
- 'designSystem',
66
- 'components',
67
- 'snippets',
68
- 'pages',
69
- 'utils',
70
- 'files',
71
- 'assets',
72
- 'packages',
73
- 'functions',
74
- 'globalScope'
75
- ].forEach((key) => {
76
- overwriteDeep(options[key], evalData[key.toLowerCase()])
77
- })
78
- }
79
- }
80
-
81
- return options
82
- }
83
-
84
- const fetchProjectAsync = async (key, options, callback) => {
85
- const { editor } = options
86
-
87
- if (editor && editor.remote) {
88
- const data = await fetchRemote(key, editor)
89
- const evalData =
90
- IS_DEVELOPMENT || options.isDevelopment
91
- ? deepDestringifyFunctions(data)
92
- : deepDestringifyFunctions(data.releases[0])
93
- callback(evalData)
94
- }
95
- }
96
-
97
- export const fetchSync = async (key, options) => {
98
- if (key && options.editor) {
99
- try {
100
- if (!options.editor.async) await fetchProject(key, options)
101
- } catch (e) {
102
- console.error(e)
103
- }
104
- }
105
- }
106
-
107
- export const fetchAsync = (app, key, options, callback) => {
108
- if (key && options.editor) {
109
- try {
110
- if (options.editor.async) {
111
- fetchProjectAsync(key, options, callback || ((data) => {
112
- const designSystem = data.designSystem
113
- if (isObject(designSystem)) {
114
- options.utils.init(designSystem)
115
- }
116
- if (isObject(data.state)) {
117
- app.state.set(data.state)
118
- }
119
- }))
120
- }
121
- } catch (e) {
122
- console.error(e)
123
- }
124
- }
125
- }
package/src/hydrate.js DELETED
@@ -1,495 +0,0 @@
1
- 'use strict'
2
-
3
- /**
4
- * Client-side hydration — reconnects pre-rendered HTML (with data-br keys)
5
- * to a live DOMQL element tree, attaches events, and fires lifecycle hooks.
6
- *
7
- * This module is part of the smbls browser bundle (IIFE / browser build).
8
- * It is intentionally self-contained — no imports — so it can be bundled
9
- * without pulling in any server-only dependencies.
10
- *
11
- * After hydration the DOMQL tree owns every DOM node:
12
- * - el.node points to the real (pre-rendered) DOM element
13
- * - node.ref points back to the DOMQL element
14
- * - CSS classes are generated via @symbo.ls/css and applied
15
- * - DOM events (click, input, etc.) are bound
16
- * - on.render / on.renderRouter / on.done / on.create callbacks fire
17
- */
18
-
19
- // ── DOM scanning ──────────────────────────────────────────────────────────────
20
-
21
- /**
22
- * Collects all elements with data-br attributes from the document.
23
- * Returns a map of brKey → DOM node.
24
- */
25
- export const collectBrNodes = (root) => {
26
- const container = root || document
27
- const nodes = container.querySelectorAll('[data-br]')
28
- const map = {}
29
- nodes.forEach(node => {
30
- map[node.getAttribute('data-br')] = node
31
- })
32
- return map
33
- }
34
-
35
- // ── Registry helpers ──────────────────────────────────────────────────────────
36
-
37
- /**
38
- * Assign __brKey values to skeleton elements from the BR registry.
39
- * The registry maps element path (dot-separated key chain) to brKey.
40
- *
41
- * Example registry: { '__root': 'br-0', 'Header': 'br-5', 'Header.Nav': 'br-8' }
42
- */
43
- export const assignBrKeysFromRegistry = (element, registry, path = '') => {
44
- if (!element || !element.__ref) return
45
-
46
- const lookupKey = path || '__root'
47
- const brKey = registry[lookupKey]
48
- if (brKey !== undefined) element.__ref.__brKey = brKey
49
-
50
- if (element.__ref.__children) {
51
- for (const childKey of element.__ref.__children) {
52
- const child = element[childKey]
53
- if (child && child.__ref) {
54
- const childPath = path ? `${path}.${childKey}` : childKey
55
- assignBrKeysFromRegistry(child, registry, childPath)
56
- }
57
- }
58
- }
59
- }
60
-
61
- // ── Main hydration ────────────────────────────────────────────────────────────
62
-
63
- /**
64
- * Walks a DOMQL element tree that was created with onlyResolveExtends.
65
- * For each element with a __brKey, attaches the matching real DOM node,
66
- * renders CSS via @symbo.ls/css, binds DOM events, and fires lifecycle hooks.
67
- *
68
- * @param {object} element - Root DOMQL element (skeleton)
69
- * @param {object} [options]
70
- * @param {Element|Document} [options.root] - Root to scan for data-br nodes
71
- * @param {boolean} [options.events=true] - Attach DOM events
72
- * @param {boolean} [options.renderEvents=true] - Fire on.render / on.renderRouter
73
- * @param {object} [options.cssEngine] - CSS engine instance ({ css }) for class generation
74
- * @param {object} [options.designSystem] - Design system (color, media tokens)
75
- * @returns {{ element, linked: number, unlinked: number }}
76
- */
77
- export const hydrate = (element, options = {}) => {
78
- const {
79
- root,
80
- events: attachEvents = true,
81
- renderEvents: fireRenderEvents = true,
82
- cssEngine,
83
- designSystem
84
- } = options
85
-
86
- const brNodes = collectBrNodes(root)
87
- const colorMap = designSystem?.color || {}
88
- const mediaMap = designSystem?.media || {}
89
- let linked = 0
90
- let unlinked = 0
91
-
92
- const walk = (el) => {
93
- if (!el || !el.__ref) return
94
-
95
- const brKey = el.__ref.__brKey
96
- if (brKey) {
97
- const node = brNodes[brKey]
98
- if (node) {
99
- el.node = node
100
- node.ref = el
101
-
102
- if (cssEngine) {
103
- renderCSS(el, cssEngine, colorMap, mediaMap)
104
- }
105
-
106
- if (attachEvents) {
107
- bindEvents(el)
108
- }
109
-
110
- linked++
111
- } else {
112
- unlinked++
113
- }
114
- }
115
-
116
- if (el.__ref.__children) {
117
- for (const childKey of el.__ref.__children) {
118
- const child = el[childKey]
119
- if (child && child.__ref) walk(child)
120
- }
121
- }
122
- }
123
-
124
- walk(element)
125
-
126
- if (fireRenderEvents) {
127
- fireLifecycle(element)
128
- }
129
-
130
- return { element, linked, unlinked }
131
- }
132
-
133
- // ── CSS rendering ─────────────────────────────────────────────────────────────
134
-
135
- /**
136
- * Renders CSS for an element: resolves props into a CSS object,
137
- * resolves design system values (colors, media queries, pseudo-classes),
138
- * generates a CSS class name, and applies it to the DOM node.
139
- */
140
- const renderCSS = (el, cssEngine, colorMap, mediaMap) => {
141
- const { node } = el
142
- if (!node) return
143
-
144
- const css = {}
145
- let hasCss = false
146
-
147
- for (const key in el) {
148
- const val = el[key]
149
-
150
- // @media breakpoint objects: @mobile, @tablet, etc.
151
- if (key.charCodeAt(0) === 64) {
152
- const breakpoint = mediaMap[key.slice(1)]
153
- if (breakpoint && typeof val === 'object') {
154
- const mediaCss = resolvePropsToCSS(val, colorMap)
155
- if (Object.keys(mediaCss).length) {
156
- css[breakpoint] = mediaCss
157
- hasCss = true
158
- }
159
- }
160
- continue
161
- }
162
-
163
- // :pseudo-class objects: :hover, :focus, :active, etc.
164
- if (key.charCodeAt(0) === 58) {
165
- if (typeof val === 'object') {
166
- const pseudoCss = resolvePropsToCSS(val, colorMap)
167
- if (Object.keys(pseudoCss).length) {
168
- css['&' + key] = pseudoCss
169
- hasCss = true
170
- }
171
- }
172
- continue
173
- }
174
-
175
- // Resolve DOMQL shorthands (flow, align, round, boxSize, etc.)
176
- const expanded = resolveShorthand(key, val)
177
- if (expanded) {
178
- for (const ek in expanded) {
179
- css[ek] = resolveValue(ek, expanded[ek], colorMap)
180
- }
181
- hasCss = true
182
- continue
183
- }
184
-
185
- // Skip non-CSS props
186
- if (!isCSS(key)) continue
187
-
188
- css[key] = resolveValue(key, val, colorMap)
189
- hasCss = true
190
- }
191
-
192
- // Inject CSS from extends chain (e.g. extends: 'Flex' → display: flex)
193
- const extsCss = getExtendsCSS(el)
194
- if (extsCss) {
195
- for (const [k, v] of Object.entries(extsCss)) {
196
- if (!css[k]) { css[k] = v; hasCss = true }
197
- }
198
- }
199
-
200
- // Handle element.style object
201
- if (el.style && typeof el.style === 'object') {
202
- Object.assign(css, el.style)
203
- hasCss = true
204
- }
205
-
206
- if (!hasCss) return
207
-
208
- const cssClass = cssEngine.css(css)
209
-
210
- const classes = []
211
- if (cssClass) classes.push(cssClass)
212
-
213
- // Preserve key-based classname (_key → class)
214
- if (typeof el.key === 'string' && el.key.charCodeAt(0) === 95 && el.key.charCodeAt(1) !== 95) {
215
- classes.push(el.key.slice(1))
216
- }
217
-
218
- if (el.class) classes.push(el.class)
219
- if (el.attr?.class) classes.push(el.attr.class)
220
-
221
- const classlist = el.classlist
222
- if (classlist) {
223
- if (typeof classlist === 'string') classes.push(classlist)
224
- else if (typeof classlist === 'object') {
225
- for (const k in classlist) {
226
- const v = classlist[k]
227
- if (typeof v === 'boolean' && v) classes.push(k)
228
- else if (typeof v === 'string') classes.push(v)
229
- else if (typeof v === 'object' && v) classes.push(cssEngine.css(v))
230
- }
231
- }
232
- }
233
-
234
- if (classes.length) {
235
- node.setAttribute('class', classes.join(' '))
236
- }
237
-
238
- // Clean up leaked CSS prop attributes from SSR
239
- for (const key in el) {
240
- if (isCSS(key) && node.hasAttribute(key)) {
241
- node.removeAttribute(key)
242
- }
243
- }
244
- }
245
-
246
- const resolvePropsToCSS = (propsObj, colorMap) => {
247
- const css = {}
248
- for (const key in propsObj) {
249
- const expanded = resolveShorthand(key, propsObj[key])
250
- if (expanded) {
251
- for (const ek in expanded) css[ek] = resolveValue(ek, expanded[ek], colorMap)
252
- continue
253
- }
254
- if (!isCSS(key)) continue
255
- css[key] = resolveValue(key, propsObj[key], colorMap)
256
- }
257
- return css
258
- }
259
-
260
- const COLOR_PROPS = new Set([
261
- 'color', 'background', 'backgroundColor', 'borderColor',
262
- 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor',
263
- 'outlineColor', 'fill', 'stroke', 'caretColor', 'columnRuleColor',
264
- 'textDecorationColor', 'boxShadow', 'textShadow'
265
- ])
266
-
267
- const resolveValue = (key, val, colorMap) => {
268
- if (typeof val !== 'string') return val
269
- if (COLOR_PROPS.has(key) && colorMap[val]) return colorMap[val]
270
- return val
271
- }
272
-
273
- const NON_CSS_PROPS = new Set([
274
- 'href', 'src', 'alt', 'title', 'id', 'name', 'type', 'value', 'placeholder',
275
- 'target', 'rel', 'loading', 'srcset', 'sizes', 'media', 'role', 'tabindex',
276
- 'for', 'action', 'method', 'enctype', 'autocomplete', 'autofocus',
277
- 'theme', '__element', 'update'
278
- ])
279
-
280
- // Map of component names to their implicit CSS from extends
281
- const EXTENDS_CSS = {
282
- Flex: { display: 'flex' },
283
- InlineFlex: { display: 'inline-flex' },
284
- Grid: { display: 'grid' },
285
- InlineGrid: { display: 'inline-grid' },
286
- Block: { display: 'block' },
287
- Inline: { display: 'inline' }
288
- }
289
-
290
- const getExtendsCSS = (el) => {
291
- const exts = el.__ref?.__extends
292
- if (!exts || !Array.isArray(exts)) return null
293
- for (const ext of exts) {
294
- if (EXTENDS_CSS[ext]) return EXTENDS_CSS[ext]
295
- }
296
- return null
297
- }
298
-
299
- // DOMQL shorthand props that expand to multiple CSS properties
300
- const resolveShorthand = (key, val) => {
301
- if (typeof val === 'undefined' || val === null) return null
302
-
303
- // Flex shorthands
304
- if (key === 'flow' && typeof val === 'string') {
305
- let [direction, wrap] = (val || 'row').split(' ')
306
- if (val.startsWith('x') || val === 'row') direction = 'row'
307
- if (val.startsWith('y') || val === 'column') direction = 'column'
308
- return { display: 'flex', flexFlow: (direction || '') + ' ' + (wrap || '') }
309
- }
310
- if (key === 'wrap') return { display: 'flex', flexWrap: val }
311
- if ((key === 'align' || key === 'flexAlign') && typeof val === 'string') {
312
- const [alignItems, justifyContent] = val.split(' ')
313
- return { display: 'flex', alignItems, justifyContent }
314
- }
315
- if (key === 'gridAlign' && typeof val === 'string') {
316
- const [alignItems, justifyContent] = val.split(' ')
317
- return { display: 'grid', alignItems, justifyContent }
318
- }
319
- if (key === 'flexFlow' && typeof val === 'string') {
320
- let [direction, wrap] = (val || 'row').split(' ')
321
- if (val.startsWith('x') || val === 'row') direction = 'row'
322
- if (val.startsWith('y') || val === 'column') direction = 'column'
323
- return { display: 'flex', flexFlow: (direction || '') + ' ' + (wrap || '') }
324
- }
325
- if (key === 'flexWrap') return { display: 'flex', flexWrap: val }
326
-
327
- // Box/size shorthands
328
- if (key === 'round' || (key === 'borderRadius' && val)) {
329
- return { borderRadius: typeof val === 'number' ? val + 'px' : val }
330
- }
331
- if (key === 'boxSize' && typeof val === 'string') {
332
- const [height, width] = val.split(' ')
333
- return { height, width: width || height }
334
- }
335
- if (key === 'widthRange' && typeof val === 'string') {
336
- const [minWidth, maxWidth] = val.split(' ')
337
- return { minWidth, maxWidth: maxWidth || minWidth }
338
- }
339
- if (key === 'heightRange' && typeof val === 'string') {
340
- const [minHeight, maxHeight] = val.split(' ')
341
- return { minHeight, maxHeight: maxHeight || minHeight }
342
- }
343
-
344
- // Grid aliases
345
- if (key === 'column') return { gridColumn: val }
346
- if (key === 'columns') return { gridTemplateColumns: val }
347
- if (key === 'templateColumns') return { gridTemplateColumns: val }
348
- if (key === 'row') return { gridRow: val }
349
- if (key === 'rows') return { gridTemplateRows: val }
350
- if (key === 'templateRows') return { gridTemplateRows: val }
351
- if (key === 'area') return { gridArea: val }
352
- if (key === 'template') return { gridTemplate: val }
353
- if (key === 'templateAreas') return { gridTemplateAreas: val }
354
- if (key === 'autoColumns') return { gridAutoColumns: val }
355
- if (key === 'autoRows') return { gridAutoRows: val }
356
- if (key === 'autoFlow') return { gridAutoFlow: val }
357
- if (key === 'columnStart') return { gridColumnStart: val }
358
- if (key === 'rowStart') return { gridRowStart: val }
359
-
360
- return null
361
- }
362
-
363
- const isCSS = (key) => {
364
- const ch = key.charCodeAt(0)
365
- if (ch === 95 || ch === 64 || ch === 58) return false // _, @, :
366
- if (ch >= 65 && ch <= 90) return false // uppercase = component ref
367
- if (NON_CSS_PROPS.has(key)) return false
368
- return CSS_PROPERTIES.has(key)
369
- }
370
-
371
- const CSS_PROPERTIES = new Set([
372
- 'display', 'position', 'top', 'right', 'bottom', 'left',
373
- 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',
374
- 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft',
375
- 'marginBlock', 'marginInline',
376
- 'padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
377
- 'paddingBlock', 'paddingInline',
378
- 'border', 'borderTop', 'borderRight', 'borderBottom', 'borderLeft',
379
- 'borderRadius', 'borderColor', 'borderWidth', 'borderStyle',
380
- 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth',
381
- 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle',
382
- 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor',
383
- 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius',
384
- 'background', 'backgroundColor', 'backgroundImage', 'backgroundSize', 'backgroundPosition',
385
- 'backgroundRepeat', 'backgroundAttachment',
386
- 'color', 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle',
387
- 'lineHeight', 'letterSpacing', 'textAlign', 'textDecoration', 'textTransform',
388
- 'textIndent', 'textOverflow', 'textShadow',
389
- 'opacity', 'overflow', 'overflowX', 'overflowY',
390
- 'zIndex', 'cursor', 'pointerEvents', 'userSelect',
391
- 'flex', 'flexDirection', 'flexWrap', 'flexFlow', 'flexGrow', 'flexShrink', 'flexBasis',
392
- 'alignItems', 'alignContent', 'alignSelf',
393
- 'justifyContent', 'justifyItems', 'justifySelf',
394
- 'gap', 'rowGap', 'columnGap',
395
- 'gridTemplateColumns', 'gridTemplateRows', 'gridColumn', 'gridRow',
396
- 'gridArea', 'gridAutoFlow', 'gridAutoColumns', 'gridAutoRows',
397
- 'inset',
398
- 'inlineSize', 'blockSize', 'minInlineSize', 'maxInlineSize', 'minBlockSize', 'maxBlockSize',
399
- 'paddingBlockStart', 'paddingBlockEnd', 'paddingInlineStart', 'paddingInlineEnd',
400
- 'marginBlockStart', 'marginBlockEnd', 'marginInlineStart', 'marginInlineEnd',
401
- 'transform', 'transformOrigin', 'transition',
402
- 'animation', 'animationName', 'animationDuration', 'animationDelay',
403
- 'animationTimingFunction', 'animationFillMode', 'animationIterationCount',
404
- 'animationPlayState', 'animationDirection',
405
- 'gridTemplate', 'gridTemplateAreas', 'gridColumnStart', 'gridRowStart',
406
- 'boxShadow', 'outline', 'outlineColor', 'outlineWidth', 'outlineStyle', 'outlineOffset',
407
- 'whiteSpace', 'wordBreak', 'wordWrap', 'overflowWrap',
408
- 'visibility', 'boxSizing', 'objectFit', 'objectPosition',
409
- 'filter', 'backdropFilter', 'mixBlendMode',
410
- 'fill', 'stroke', 'strokeWidth',
411
- 'listStyle', 'listStyleType', 'listStylePosition',
412
- 'counterReset', 'counterIncrement', 'content',
413
- 'aspectRatio', 'resize', 'appearance',
414
- 'scrollBehavior', 'scrollMargin', 'scrollPadding',
415
- 'willChange', 'contain', 'isolation',
416
- 'caretColor', 'accentColor',
417
- 'columnCount', 'columnGap', 'columnRuleColor', 'columnRuleStyle', 'columnRuleWidth',
418
- 'textDecorationColor', 'textDecorationStyle', 'textDecorationThickness',
419
- 'clipPath', 'shapeOutside'
420
- ])
421
-
422
- // ── Event binding ─────────────────────────────────────────────────────────────
423
-
424
- const DOMQL_LIFECYCLE = new Set([
425
- 'render', 'create', 'init', 'start', 'complete', 'done',
426
- 'beforeClassAssign', 'attachNode', 'stateInit', 'stateCreated',
427
- 'renderRouter', 'lazyLoad', 'error'
428
- ])
429
-
430
- /**
431
- * Binds DOM events from element's onX properties onto the real node.
432
- */
433
- const bindEvents = (el) => {
434
- const { node } = el
435
- if (!node) return
436
-
437
- if (!el.__ref.__eventCleanup) el.__ref.__eventCleanup = []
438
-
439
- // v3.14: event handlers are flat on element as onX properties
440
- for (const key in el) {
441
- if (key.length <= 2 || key[0] !== 'o' || key[1] !== 'n') continue
442
- if (typeof el[key] !== 'function') continue
443
- const third = key[2]
444
- if (third !== third.toUpperCase()) continue
445
- const eventName = third.toLowerCase() + key.slice(3)
446
- if (DOMQL_LIFECYCLE.has(eventName)) continue
447
- addListener(node, eventName, el[key], el)
448
- }
449
- }
450
-
451
- const addListener = (node, eventName, handler, el) => {
452
- const listener = (event) => {
453
- try {
454
- handler.call(el, event, el, el.state, el.context)
455
- } catch (e) {
456
- console.warn('[smbls hydrate]', eventName, e.message)
457
- }
458
- }
459
- node.addEventListener(eventName, listener)
460
- el.__ref.__eventCleanup.push(() => node.removeEventListener(eventName, listener))
461
- }
462
-
463
- // ── Lifecycle ─────────────────────────────────────────────────────────────────
464
-
465
- /**
466
- * Walks the tree and fires onRender, onRenderRouter, onDone, onCreate
467
- * lifecycle events — the same ones that fire during normal DOMQL create.
468
- */
469
- const fireLifecycle = (el) => {
470
- if (!el || !el.__ref || !el.node) return
471
-
472
- fireEvent(el.onRender, el)
473
- fireEvent(el.onRenderRouter, el)
474
- fireEvent(el.onDone, el)
475
- fireEvent(el.onCreate, el)
476
-
477
- if (el.__ref.__children) {
478
- for (const childKey of el.__ref.__children) {
479
- const child = el[childKey]
480
- if (child && child.__ref) fireLifecycle(child)
481
- }
482
- }
483
- }
484
-
485
- const fireEvent = (fn, el) => {
486
- if (typeof fn !== 'function') return
487
- try {
488
- const result = fn.call(el, el, el.state, el.context)
489
- if (result && typeof result.then === 'function') {
490
- result.catch(() => {})
491
- }
492
- } catch (e) {
493
- console.warn('[smbls hydrate]', el.key, e.message)
494
- }
495
- }