fusee-framework 2.7.0 → 2.7.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.
Files changed (61) hide show
  1. package/README.md +98 -26
  2. package/bin/create-fusee-linux +0 -0
  3. package/bin/create-fusee-mac-arm64 +0 -0
  4. package/bin/create-fusee-mac-intel +0 -0
  5. package/bin/create-fusee-win.exe +0 -0
  6. package/framework/auto-import-preset.js +20 -12
  7. package/framework/auto-imports.d.ts +68 -39
  8. package/framework/core/compiler.js +310 -310
  9. package/framework/core/component-core.js +445 -0
  10. package/framework/core/component.js +11 -440
  11. package/framework/core/composable.js +29 -29
  12. package/framework/core/h.js +119 -44
  13. package/framework/core/jsx-helpers.js +11 -2
  14. package/framework/core/jsx-runtime.js +73 -73
  15. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm +0 -0
  16. package/framework/core/store.js +333 -333
  17. package/framework/globals.js +42 -0
  18. package/framework/index.js +22 -55
  19. package/framework/router/file-router.js +184 -184
  20. package/framework/server/actions.js +183 -183
  21. package/framework/server/generate-manifest.js +3 -3
  22. package/framework/types/component.d.ts +97 -86
  23. package/framework/types/index.d.ts +118 -117
  24. package/package.json +83 -80
  25. package/framework/core/rust-compiler/Cargo.toml +0 -31
  26. package/framework/core/rust-compiler/README.md +0 -69
  27. package/framework/core/rust-compiler/pkg/README.md +0 -69
  28. package/framework/core/rust-compiler/pkg/fusee_compiler.d.ts +0 -6
  29. package/framework/core/rust-compiler/pkg/fusee_compiler.js +0 -9
  30. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.js +0 -137
  31. package/framework/core/rust-compiler/pkg/fusee_compiler_bg.wasm.d.ts +0 -11
  32. package/framework/core/rust-compiler/pkg/package.json +0 -18
  33. package/framework/core/rust-compiler/src/ast.rs +0 -274
  34. package/framework/core/rust-compiler/src/errors.rs +0 -204
  35. package/framework/core/rust-compiler/src/generator.rs +0 -583
  36. package/framework/core/rust-compiler/src/lexer.rs +0 -414
  37. package/framework/core/rust-compiler/src/lib.rs +0 -87
  38. package/framework/core/rust-compiler/src/main_compiler.rs +0 -165
  39. package/framework/core/rust-compiler/src/parser.rs +0 -516
  40. package/framework/core/rust-compiler/src/ssr_generator.rs +0 -688
  41. package/framework/core/rust-compiler/src/transformer.rs +0 -562
  42. package/framework/core/rust-compiler/src/utils.rs +0 -10
  43. package/framework/engine-go/actions.go +0 -112
  44. package/framework/engine-go/cmd/server/main.go +0 -233
  45. package/framework/engine-go/comet/comet.go +0 -41
  46. package/framework/engine-go/comet/fragment.go +0 -51
  47. package/framework/engine-go/dispatcher.go +0 -120
  48. package/framework/engine-go/elements.go +0 -248
  49. package/framework/engine-go/evalexpr/evalexpr.go +0 -733
  50. package/framework/engine-go/go.mod +0 -3
  51. package/framework/engine-go/hydration.go +0 -43
  52. package/framework/engine-go/load-helpers.go +0 -29
  53. package/framework/engine-go/manifest.go +0 -41
  54. package/framework/engine-go/reactive.go +0 -123
  55. package/framework/engine-go/render.go +0 -635
  56. package/framework/engine-go/reqcontext.go +0 -48
  57. package/framework/engine-go/router.go +0 -85
  58. package/framework/engine-go/rustast/rustast.go +0 -317
  59. package/framework/engine-go/ssr.go +0 -103
  60. package/framework/engine-go/static-assets.go +0 -139
  61. package/framework/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
@@ -1,311 +1,311 @@
1
- import { effect, batch } from './signal.js'
2
- import { processDirectives } from './directives.js'
3
- import { parseSlots } from './component.js'
4
- import { resolveElementProperty } from './element-props.js'
5
- import {
6
- evaluateExpression,
7
- parseInterpolation,
8
- sanitizeAttr,
9
- MUSTACHE_RE
10
- } from './evaluator.js'
11
-
12
- export function mountTemplate(template, container, context, components) {
13
- const effects = []
14
-
15
- // Lower component tags before the browser parses the template as HTML.
16
- let processed = transformComponentTags(template, components)
17
-
18
- for (const name of Object.keys(components)) {
19
- const escapedName = escapeRegExp(name)
20
- const reWithSlot = new RegExp(
21
- `\\{\\{\\s*${escapedName}(\\s[^}]*?|\\s*)\\}\\}([\\s\\S]*?)\\{\\{\\s*\\/${escapedName}\\s*\\}\\}`,
22
- 'g'
23
- )
24
- const reNoSlot = new RegExp(`\\{\\{\\s*${escapedName}(\\s+.*?|\\s*)\\}\\}`, 'g')
25
-
26
- processed = processed.replace(reWithSlot, (match, propsStr, slotContent) => {
27
- const attrs = parseComponentAttrs(propsStr || '')
28
- const encodedSlot = encodeURIComponent(slotContent.trim())
29
- return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
30
- })
31
-
32
- processed = processed.replace(reNoSlot, (match, propsStr) => {
33
- const attrs = parseComponentAttrs(propsStr || '')
34
- return `<div data-component="${name}"${attrs}></div>`
35
- })
36
- }
37
-
38
- container.innerHTML = rewriteBindingAttrs(processed)
39
- compileNode(container, context, components, effects)
40
-
41
- return { effects }
42
- }
43
-
44
- const TAG_RE = /<[a-zA-Z][a-zA-Z0-9-]*(?:"[^"]*"|'[^']*'|[^'">])*>/g
45
- const BINDING_IN_TAG_RE = /("[^"]*"|'[^']*')|(\s):([a-zA-Z][a-zA-Z0-9-]*)=/g
46
-
47
- /**
48
- * Rewrites every `:attr=` inside an element tag to `data-bind-attr=`, since `:` is not a
49
- * valid attribute name for the HTML parser. Quoted values are skipped, so expressions
50
- * containing ` :x=` or `>` are left intact. Runs after component tags are lowered, so
51
- * component props keep their own `data-bind-prop-` encoding.
52
- */
53
- function rewriteBindingAttrs(template) {
54
- return template.replace(TAG_RE, tag => tag.replace(BINDING_IN_TAG_RE, (match, quoted, space, name) =>
55
- quoted ? quoted : `${space}data-bind-${name}=`
56
- ))
57
- }
58
-
59
- function transformComponentTags(template, components) {
60
- let processed = template
61
- const componentNames = Object.keys(components).sort((a, b) => b.length - a.length)
62
-
63
- for (const name of componentNames) {
64
- const escapedName = escapeRegExp(name)
65
- const withChildren = new RegExp(
66
- `<${escapedName}(\\s[^>]*)?>([\\s\\S]*?)<\\/${escapedName}\\s*>`,
67
- 'gi'
68
- )
69
- const selfClosing = new RegExp(
70
- `<${escapedName}(\\s[^>]*?)?\\s*/>`,
71
- 'gi'
72
- )
73
-
74
- processed = processed.replace(withChildren, (match, propsStr, slotContent) => {
75
- const attrs = parseComponentAttrs(propsStr || '')
76
- const encodedSlot = encodeURIComponent(slotContent.trim())
77
- return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
78
- })
79
-
80
- processed = processed.replace(selfClosing, (match, propsStr) => {
81
- const attrs = parseComponentAttrs(propsStr || '')
82
- return `<div data-component="${name}"${attrs}></div>`
83
- })
84
- }
85
-
86
- return processed
87
- }
88
-
89
- function escapeRegExp(value) {
90
- return value.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')
91
- }
92
-
93
- function parseComponentAttrs(propsStr) {
94
- let attrs = ''
95
- if (!propsStr || !propsStr.trim()) return attrs
96
-
97
- const attrRegex = /(@?:?[a-zA-Z0-9-]+)="([^"]*)"/g
98
- let attrMatch
99
- while ((attrMatch = attrRegex.exec(propsStr)) !== null) {
100
- const attrName = attrMatch[1]
101
- const attrValue = attrMatch[2]
102
-
103
- if (attrName.startsWith('@')) {
104
- attrs += ` data-on-${attrName.slice(1).toLowerCase()}="${attrValue}"`
105
- } else if (attrName.startsWith(':')) {
106
- attrs += ` data-bind-prop-${attrName.slice(1).toLowerCase()}="${attrValue}"`
107
- } else {
108
- attrs += ` data-prop-${attrName.toLowerCase()}="${attrValue}"`
109
- }
110
- }
111
- return attrs
112
- }
113
-
114
- export function compileNode(node, context, components, effects) {
115
- if (node.nodeType === 1 && node.hasAttribute('f-once')) {
116
- node.removeAttribute('f-once')
117
- const temporaryEffects = []
118
-
119
- processDirectives(node, context, components, temporaryEffects)
120
- processBindingAttrs(node, context, temporaryEffects)
121
- processMustaches(node, context, temporaryEffects)
122
- bindComponents(node, components, context, temporaryEffects)
123
-
124
- let child = node.firstChild
125
- while (child) {
126
- compileOnce(child, context, components, temporaryEffects)
127
- child = child.nextSibling
128
- }
129
-
130
- for (let i = temporaryEffects.length - 1; i >= 0; i--) {
131
- const cleanup = temporaryEffects[i]
132
- if (typeof cleanup === 'function') cleanup()
133
- }
134
- return
135
- }
136
-
137
- if (node.nodeType === 1) {
138
- if (processDirectives(node, context, components, effects)) return
139
- processBindingAttrs(node, context, effects)
140
- bindComponents(node, components, context, effects)
141
- } else if (node.nodeType === 3) {
142
- processMustaches(node, context, effects)
143
- }
144
-
145
- let child = node.firstChild
146
- while (child) {
147
- compileNode(child, context, components, effects)
148
- child = child.nextSibling
149
- }
150
- }
151
-
152
- function compileOnce(node, context, components, effects) {
153
- if (node.nodeType === 1) {
154
- if (processDirectives(node, context, components, effects)) return
155
- processBindingAttrs(node, context, effects)
156
- bindComponents(node, components, context, effects)
157
- } else if (node.nodeType === 3) {
158
- processMustaches(node, context, effects)
159
- }
160
-
161
- let child = node.firstChild
162
- while (child) {
163
- compileOnce(child, context, components, effects)
164
- child = child.nextSibling
165
- }
166
- }
167
-
168
- function processMustaches(node, context, effects) {
169
- if (node.nodeType !== 3) return
170
- const text = node.textContent
171
- if (!MUSTACHE_RE.test(text)) return
172
-
173
- const parts = parseInterpolation(text)
174
- const fragment = document.createDocumentFragment()
175
-
176
- for (const part of parts) {
177
- if (part.type === 'static') {
178
- fragment.appendChild(document.createTextNode(part.value))
179
- } else {
180
- const reactiveNode = document.createTextNode('')
181
- fragment.appendChild(reactiveNode)
182
- const key = part.key
183
- const e = effect(() => {
184
- const resolved = evaluateExpression(key, context)
185
- reactiveNode.textContent = String(resolved ?? '')
186
- })
187
- effects.push(e)
188
- }
189
- }
190
- node.parentNode.replaceChild(fragment, node)
191
- }
192
-
193
- function processBindingAttrs(el, context, effects) {
194
- if (el.nodeType !== 1) return
195
- for (const attr of [...el.attributes]) {
196
- if (attr.name.startsWith('data-bind-') && !attr.name.startsWith('data-bind-prop-')) {
197
- const realAttr = attr.name.slice(10)
198
- const expr = attr.value
199
- el.removeAttribute(attr.name)
200
-
201
- const e = effect(() => {
202
- let resolved = evaluateExpression(expr, context)
203
- const elementProp = realAttr !== 'class' && realAttr !== 'style'
204
- ? resolveElementProperty(el, realAttr, resolved)
205
- : null
206
- if (elementProp) {
207
- el[elementProp] = resolved
208
- } else if (realAttr === 'class') {
209
- if (!el._staticClass) el._staticClass = el.getAttribute('class') || ''
210
- let dynamicClass = ''
211
- if (Array.isArray(resolved)) dynamicClass = resolved.filter(Boolean).join(' ')
212
- else if (resolved !== null && typeof resolved === 'object') {
213
- dynamicClass = Object.entries(resolved).filter(([_, v]) => !!v).map(([k]) => k).join(' ')
214
- } else dynamicClass = String(resolved ?? '')
215
- el.className = (el._staticClass + ' ' + dynamicClass).trim()
216
- } else if (realAttr === 'style') {
217
- if (resolved !== null && typeof resolved === 'object') {
218
- for (const k in resolved) el.style[k] = resolved[k]
219
- } else el.style.cssText = String(resolved ?? '')
220
- } else {
221
- if (typeof resolved === 'boolean') {
222
- resolved ? el.setAttribute(realAttr, '') : el.removeAttribute(realAttr)
223
- } else el.setAttribute(realAttr, sanitizeAttr(realAttr, String(resolved)))
224
- }
225
- })
226
- effects.push(e)
227
- continue
228
- }
229
-
230
- if (MUSTACHE_RE.test(attr.value)) {
231
- const attrName = attr.name
232
- const parts = parseInterpolation(attr.value)
233
- el.removeAttribute(attrName)
234
- const e = effect(() => {
235
- let result = ''
236
- for (const part of parts) {
237
- result += part.type === 'static' ? part.value : String(evaluateExpression(part.key, context) ?? '')
238
- }
239
- el.setAttribute(attrName, sanitizeAttr(attrName, result))
240
- })
241
- effects.push(e)
242
- }
243
- }
244
- }
245
-
246
- function bindComponents(el, components, context, effects) {
247
- if (el.nodeType !== 1) return
248
- const name = el.getAttribute('data-component')
249
- if (!name) return
250
-
251
- el.removeAttribute('data-component')
252
-
253
- const ComponentFn = components[name]
254
- if (!ComponentFn) return
255
-
256
- const props = {}
257
- const listeners = {}
258
-
259
- for (const attr of [...el.attributes]) {
260
- const attrName = attr.name.toLowerCase()
261
-
262
- if (attrName.startsWith('data-prop-')) {
263
- props[attrName.slice(10)] = attr.value
264
-
265
- } else if (attrName.startsWith('data-bind-prop-')) {
266
- const propName = attrName.slice(15)
267
- const expression = attr.value.trim()
268
-
269
- if (expression in context) {
270
- Object.defineProperty(props, propName, {
271
- get() {
272
- const val = context[expression]
273
- return typeof val === 'function' && val.isSignal ? val() : val
274
- },
275
- enumerable: true,
276
- configurable: true
277
- })
278
- } else {
279
- Object.defineProperty(props, propName, {
280
- get() { return evaluateExpression(expression, context) },
281
- enumerable: true,
282
- configurable: true
283
- })
284
- }
285
-
286
- } else if (attrName.startsWith('data-on-')) {
287
- const eventName = attrName.slice(8)
288
- const handlerExpr = attr.value.trim()
289
-
290
- listeners[eventName] = (...args) => {
291
- const handler = context[handlerExpr]
292
- if (typeof handler === 'function') {
293
- handler(...args)
294
- } else {
295
- evaluateExpression(handlerExpr, context)
296
- }
297
- }
298
- }
299
- }
300
-
301
- const rawSlot = el.getAttribute('data-slot')
302
- const slots = rawSlot ? parseSlots(decodeURIComponent(rawSlot)) : {}
303
- el.removeAttribute('data-slot')
304
-
305
- const childComponent = ComponentFn(props, { listeners, slots, parent: context._instance })
306
- childComponent.render(el)
307
-
308
- if (effects) {
309
- effects.push(() => childComponent.unmount())
310
- }
1
+ import { effect, batch } from './signal.js'
2
+ import { processDirectives } from './directives.js'
3
+ import { parseSlots } from './component-core.js'
4
+ import { resolveElementProperty } from './element-props.js'
5
+ import {
6
+ evaluateExpression,
7
+ parseInterpolation,
8
+ sanitizeAttr,
9
+ MUSTACHE_RE
10
+ } from './evaluator.js'
11
+
12
+ export function mountTemplate(template, container, context, components) {
13
+ const effects = []
14
+
15
+ // Lower component tags before the browser parses the template as HTML.
16
+ let processed = transformComponentTags(template, components)
17
+
18
+ for (const name of Object.keys(components)) {
19
+ const escapedName = escapeRegExp(name)
20
+ const reWithSlot = new RegExp(
21
+ `\\{\\{\\s*${escapedName}(\\s[^}]*?|\\s*)\\}\\}([\\s\\S]*?)\\{\\{\\s*\\/${escapedName}\\s*\\}\\}`,
22
+ 'g'
23
+ )
24
+ const reNoSlot = new RegExp(`\\{\\{\\s*${escapedName}(\\s+.*?|\\s*)\\}\\}`, 'g')
25
+
26
+ processed = processed.replace(reWithSlot, (match, propsStr, slotContent) => {
27
+ const attrs = parseComponentAttrs(propsStr || '')
28
+ const encodedSlot = encodeURIComponent(slotContent.trim())
29
+ return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
30
+ })
31
+
32
+ processed = processed.replace(reNoSlot, (match, propsStr) => {
33
+ const attrs = parseComponentAttrs(propsStr || '')
34
+ return `<div data-component="${name}"${attrs}></div>`
35
+ })
36
+ }
37
+
38
+ container.innerHTML = rewriteBindingAttrs(processed)
39
+ compileNode(container, context, components, effects)
40
+
41
+ return { effects }
42
+ }
43
+
44
+ const TAG_RE = /<[a-zA-Z][a-zA-Z0-9-]*(?:"[^"]*"|'[^']*'|[^'">])*>/g
45
+ const BINDING_IN_TAG_RE = /("[^"]*"|'[^']*')|(\s):([a-zA-Z][a-zA-Z0-9-]*)=/g
46
+
47
+ /**
48
+ * Rewrites every `:attr=` inside an element tag to `data-bind-attr=`, since `:` is not a
49
+ * valid attribute name for the HTML parser. Quoted values are skipped, so expressions
50
+ * containing ` :x=` or `>` are left intact. Runs after component tags are lowered, so
51
+ * component props keep their own `data-bind-prop-` encoding.
52
+ */
53
+ function rewriteBindingAttrs(template) {
54
+ return template.replace(TAG_RE, tag => tag.replace(BINDING_IN_TAG_RE, (match, quoted, space, name) =>
55
+ quoted ? quoted : `${space}data-bind-${name}=`
56
+ ))
57
+ }
58
+
59
+ function transformComponentTags(template, components) {
60
+ let processed = template
61
+ const componentNames = Object.keys(components).sort((a, b) => b.length - a.length)
62
+
63
+ for (const name of componentNames) {
64
+ const escapedName = escapeRegExp(name)
65
+ const withChildren = new RegExp(
66
+ `<${escapedName}(\\s[^>]*)?>([\\s\\S]*?)<\\/${escapedName}\\s*>`,
67
+ 'gi'
68
+ )
69
+ const selfClosing = new RegExp(
70
+ `<${escapedName}(\\s[^>]*?)?\\s*/>`,
71
+ 'gi'
72
+ )
73
+
74
+ processed = processed.replace(withChildren, (match, propsStr, slotContent) => {
75
+ const attrs = parseComponentAttrs(propsStr || '')
76
+ const encodedSlot = encodeURIComponent(slotContent.trim())
77
+ return `<div data-component="${name}"${attrs} data-slot="${encodedSlot}"></div>`
78
+ })
79
+
80
+ processed = processed.replace(selfClosing, (match, propsStr) => {
81
+ const attrs = parseComponentAttrs(propsStr || '')
82
+ return `<div data-component="${name}"${attrs}></div>`
83
+ })
84
+ }
85
+
86
+ return processed
87
+ }
88
+
89
+ function escapeRegExp(value) {
90
+ return value.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')
91
+ }
92
+
93
+ function parseComponentAttrs(propsStr) {
94
+ let attrs = ''
95
+ if (!propsStr || !propsStr.trim()) return attrs
96
+
97
+ const attrRegex = /(@?:?[a-zA-Z0-9-]+)="([^"]*)"/g
98
+ let attrMatch
99
+ while ((attrMatch = attrRegex.exec(propsStr)) !== null) {
100
+ const attrName = attrMatch[1]
101
+ const attrValue = attrMatch[2]
102
+
103
+ if (attrName.startsWith('@')) {
104
+ attrs += ` data-on-${attrName.slice(1).toLowerCase()}="${attrValue}"`
105
+ } else if (attrName.startsWith(':')) {
106
+ attrs += ` data-bind-prop-${attrName.slice(1).toLowerCase()}="${attrValue}"`
107
+ } else {
108
+ attrs += ` data-prop-${attrName.toLowerCase()}="${attrValue}"`
109
+ }
110
+ }
111
+ return attrs
112
+ }
113
+
114
+ export function compileNode(node, context, components, effects) {
115
+ if (node.nodeType === 1 && node.hasAttribute('f-once')) {
116
+ node.removeAttribute('f-once')
117
+ const temporaryEffects = []
118
+
119
+ processDirectives(node, context, components, temporaryEffects)
120
+ processBindingAttrs(node, context, temporaryEffects)
121
+ processMustaches(node, context, temporaryEffects)
122
+ bindComponents(node, components, context, temporaryEffects)
123
+
124
+ let child = node.firstChild
125
+ while (child) {
126
+ compileOnce(child, context, components, temporaryEffects)
127
+ child = child.nextSibling
128
+ }
129
+
130
+ for (let i = temporaryEffects.length - 1; i >= 0; i--) {
131
+ const cleanup = temporaryEffects[i]
132
+ if (typeof cleanup === 'function') cleanup()
133
+ }
134
+ return
135
+ }
136
+
137
+ if (node.nodeType === 1) {
138
+ if (processDirectives(node, context, components, effects)) return
139
+ processBindingAttrs(node, context, effects)
140
+ bindComponents(node, components, context, effects)
141
+ } else if (node.nodeType === 3) {
142
+ processMustaches(node, context, effects)
143
+ }
144
+
145
+ let child = node.firstChild
146
+ while (child) {
147
+ compileNode(child, context, components, effects)
148
+ child = child.nextSibling
149
+ }
150
+ }
151
+
152
+ function compileOnce(node, context, components, effects) {
153
+ if (node.nodeType === 1) {
154
+ if (processDirectives(node, context, components, effects)) return
155
+ processBindingAttrs(node, context, effects)
156
+ bindComponents(node, components, context, effects)
157
+ } else if (node.nodeType === 3) {
158
+ processMustaches(node, context, effects)
159
+ }
160
+
161
+ let child = node.firstChild
162
+ while (child) {
163
+ compileOnce(child, context, components, effects)
164
+ child = child.nextSibling
165
+ }
166
+ }
167
+
168
+ function processMustaches(node, context, effects) {
169
+ if (node.nodeType !== 3) return
170
+ const text = node.textContent
171
+ if (!MUSTACHE_RE.test(text)) return
172
+
173
+ const parts = parseInterpolation(text)
174
+ const fragment = document.createDocumentFragment()
175
+
176
+ for (const part of parts) {
177
+ if (part.type === 'static') {
178
+ fragment.appendChild(document.createTextNode(part.value))
179
+ } else {
180
+ const reactiveNode = document.createTextNode('')
181
+ fragment.appendChild(reactiveNode)
182
+ const key = part.key
183
+ const e = effect(() => {
184
+ const resolved = evaluateExpression(key, context)
185
+ reactiveNode.textContent = String(resolved ?? '')
186
+ })
187
+ effects.push(e)
188
+ }
189
+ }
190
+ node.parentNode.replaceChild(fragment, node)
191
+ }
192
+
193
+ function processBindingAttrs(el, context, effects) {
194
+ if (el.nodeType !== 1) return
195
+ for (const attr of [...el.attributes]) {
196
+ if (attr.name.startsWith('data-bind-') && !attr.name.startsWith('data-bind-prop-')) {
197
+ const realAttr = attr.name.slice(10)
198
+ const expr = attr.value
199
+ el.removeAttribute(attr.name)
200
+
201
+ const e = effect(() => {
202
+ let resolved = evaluateExpression(expr, context)
203
+ const elementProp = realAttr !== 'class' && realAttr !== 'style'
204
+ ? resolveElementProperty(el, realAttr, resolved)
205
+ : null
206
+ if (elementProp) {
207
+ el[elementProp] = resolved
208
+ } else if (realAttr === 'class') {
209
+ if (!el._staticClass) el._staticClass = el.getAttribute('class') || ''
210
+ let dynamicClass = ''
211
+ if (Array.isArray(resolved)) dynamicClass = resolved.filter(Boolean).join(' ')
212
+ else if (resolved !== null && typeof resolved === 'object') {
213
+ dynamicClass = Object.entries(resolved).filter(([_, v]) => !!v).map(([k]) => k).join(' ')
214
+ } else dynamicClass = String(resolved ?? '')
215
+ el.className = (el._staticClass + ' ' + dynamicClass).trim()
216
+ } else if (realAttr === 'style') {
217
+ if (resolved !== null && typeof resolved === 'object') {
218
+ for (const k in resolved) el.style[k] = resolved[k]
219
+ } else el.style.cssText = String(resolved ?? '')
220
+ } else {
221
+ if (typeof resolved === 'boolean') {
222
+ resolved ? el.setAttribute(realAttr, '') : el.removeAttribute(realAttr)
223
+ } else el.setAttribute(realAttr, sanitizeAttr(realAttr, String(resolved)))
224
+ }
225
+ })
226
+ effects.push(e)
227
+ continue
228
+ }
229
+
230
+ if (MUSTACHE_RE.test(attr.value)) {
231
+ const attrName = attr.name
232
+ const parts = parseInterpolation(attr.value)
233
+ el.removeAttribute(attrName)
234
+ const e = effect(() => {
235
+ let result = ''
236
+ for (const part of parts) {
237
+ result += part.type === 'static' ? part.value : String(evaluateExpression(part.key, context) ?? '')
238
+ }
239
+ el.setAttribute(attrName, sanitizeAttr(attrName, result))
240
+ })
241
+ effects.push(e)
242
+ }
243
+ }
244
+ }
245
+
246
+ function bindComponents(el, components, context, effects) {
247
+ if (el.nodeType !== 1) return
248
+ const name = el.getAttribute('data-component')
249
+ if (!name) return
250
+
251
+ el.removeAttribute('data-component')
252
+
253
+ const ComponentFn = components[name]
254
+ if (!ComponentFn) return
255
+
256
+ const props = {}
257
+ const listeners = {}
258
+
259
+ for (const attr of [...el.attributes]) {
260
+ const attrName = attr.name.toLowerCase()
261
+
262
+ if (attrName.startsWith('data-prop-')) {
263
+ props[attrName.slice(10)] = attr.value
264
+
265
+ } else if (attrName.startsWith('data-bind-prop-')) {
266
+ const propName = attrName.slice(15)
267
+ const expression = attr.value.trim()
268
+
269
+ if (expression in context) {
270
+ Object.defineProperty(props, propName, {
271
+ get() {
272
+ const val = context[expression]
273
+ return typeof val === 'function' && val.isSignal ? val() : val
274
+ },
275
+ enumerable: true,
276
+ configurable: true
277
+ })
278
+ } else {
279
+ Object.defineProperty(props, propName, {
280
+ get() { return evaluateExpression(expression, context) },
281
+ enumerable: true,
282
+ configurable: true
283
+ })
284
+ }
285
+
286
+ } else if (attrName.startsWith('data-on-')) {
287
+ const eventName = attrName.slice(8)
288
+ const handlerExpr = attr.value.trim()
289
+
290
+ listeners[eventName] = (...args) => {
291
+ const handler = context[handlerExpr]
292
+ if (typeof handler === 'function') {
293
+ handler(...args)
294
+ } else {
295
+ evaluateExpression(handlerExpr, context)
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ const rawSlot = el.getAttribute('data-slot')
302
+ const slots = rawSlot ? parseSlots(decodeURIComponent(rawSlot)) : {}
303
+ el.removeAttribute('data-slot')
304
+
305
+ const childComponent = ComponentFn(props, { listeners, slots, parent: context._instance })
306
+ childComponent.render(el)
307
+
308
+ if (effects) {
309
+ effects.push(() => childComponent.unmount())
310
+ }
311
311
  }