@nordcraft/runtime 1.0.0

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 (168) hide show
  1. package/README.md +5 -0
  2. package/dist/api/createAPI.d.ts +20 -0
  3. package/dist/api/createAPI.js +319 -0
  4. package/dist/api/createAPI.js.map +1 -0
  5. package/dist/api/createAPIv2.d.ts +7 -0
  6. package/dist/api/createAPIv2.js +686 -0
  7. package/dist/api/createAPIv2.js.map +1 -0
  8. package/dist/components/createComponent.d.ts +13 -0
  9. package/dist/components/createComponent.js +216 -0
  10. package/dist/components/createComponent.js.map +1 -0
  11. package/dist/components/createElement.d.ts +3 -0
  12. package/dist/components/createElement.js +208 -0
  13. package/dist/components/createElement.js.map +1 -0
  14. package/dist/components/createNode.d.ts +22 -0
  15. package/dist/components/createNode.js +272 -0
  16. package/dist/components/createNode.js.map +1 -0
  17. package/dist/components/createSlot.d.ts +3 -0
  18. package/dist/components/createSlot.js +49 -0
  19. package/dist/components/createSlot.js.map +1 -0
  20. package/dist/components/createText.d.ts +23 -0
  21. package/dist/components/createText.js +68 -0
  22. package/dist/components/createText.js.map +1 -0
  23. package/dist/components/createText.test.d.ts +1 -0
  24. package/dist/components/createText.test.js +113 -0
  25. package/dist/components/createText.test.js.map +1 -0
  26. package/dist/components/renderComponent.d.ts +34 -0
  27. package/dist/components/renderComponent.js +66 -0
  28. package/dist/components/renderComponent.js.map +1 -0
  29. package/dist/context/isContextProvider.d.ts +2 -0
  30. package/dist/context/isContextProvider.js +5 -0
  31. package/dist/context/isContextProvider.js.map +1 -0
  32. package/dist/context/subscribeToContext.d.ts +4 -0
  33. package/dist/context/subscribeToContext.js +93 -0
  34. package/dist/context/subscribeToContext.js.map +1 -0
  35. package/dist/custom-components/components.d.ts +1 -0
  36. package/dist/custom-components/components.js +2 -0
  37. package/dist/custom-components/components.js.map +1 -0
  38. package/dist/custom-components/toddle-portal.d.ts +6 -0
  39. package/dist/custom-components/toddle-portal.js +20 -0
  40. package/dist/custom-components/toddle-portal.js.map +1 -0
  41. package/dist/custom-element/ToddleComponent.d.ts +37 -0
  42. package/dist/custom-element/ToddleComponent.js +244 -0
  43. package/dist/custom-element/ToddleComponent.js.map +1 -0
  44. package/dist/custom-element/defineComponents.d.ts +26 -0
  45. package/dist/custom-element/defineComponents.js +42 -0
  46. package/dist/custom-element/defineComponents.js.map +1 -0
  47. package/dist/custom-element.main.d.ts +3 -0
  48. package/dist/custom-element.main.esm.js +266 -0
  49. package/dist/custom-element.main.esm.js.map +7 -0
  50. package/dist/custom-element.main.js +14 -0
  51. package/dist/custom-element.main.js.map +1 -0
  52. package/dist/debug/logState.d.ts +4 -0
  53. package/dist/debug/logState.js +19 -0
  54. package/dist/debug/logState.js.map +1 -0
  55. package/dist/editor/drag-drop/dragEnded.d.ts +2 -0
  56. package/dist/editor/drag-drop/dragEnded.js +56 -0
  57. package/dist/editor/drag-drop/dragEnded.js.map +1 -0
  58. package/dist/editor/drag-drop/dragMove.d.ts +3 -0
  59. package/dist/editor/drag-drop/dragMove.js +74 -0
  60. package/dist/editor/drag-drop/dragMove.js.map +1 -0
  61. package/dist/editor/drag-drop/dragReorder.d.ts +3 -0
  62. package/dist/editor/drag-drop/dragReorder.js +92 -0
  63. package/dist/editor/drag-drop/dragReorder.js.map +1 -0
  64. package/dist/editor/drag-drop/dragStarted.d.ts +9 -0
  65. package/dist/editor/drag-drop/dragStarted.js +100 -0
  66. package/dist/editor/drag-drop/dragStarted.js.map +1 -0
  67. package/dist/editor/drag-drop/dropHighlight.d.ts +16 -0
  68. package/dist/editor/drag-drop/dropHighlight.js +50 -0
  69. package/dist/editor/drag-drop/dropHighlight.js.map +1 -0
  70. package/dist/editor/drag-drop/getInsertAreas.d.ts +20 -0
  71. package/dist/editor/drag-drop/getInsertAreas.js +220 -0
  72. package/dist/editor/drag-drop/getInsertAreas.js.map +1 -0
  73. package/dist/editor-preview.main.d.ts +19 -0
  74. package/dist/editor-preview.main.js +1303 -0
  75. package/dist/editor-preview.main.js.map +1 -0
  76. package/dist/events/handleAction.d.ts +3 -0
  77. package/dist/events/handleAction.js +307 -0
  78. package/dist/events/handleAction.js.map +1 -0
  79. package/dist/page.main.d.ts +7 -0
  80. package/dist/page.main.esm.js +8 -0
  81. package/dist/page.main.esm.js.map +7 -0
  82. package/dist/page.main.js +395 -0
  83. package/dist/page.main.js.map +1 -0
  84. package/dist/signal/signal.d.ts +19 -0
  85. package/dist/signal/signal.js +65 -0
  86. package/dist/signal/signal.js.map +1 -0
  87. package/dist/styles/style.d.ts +4 -0
  88. package/dist/styles/style.js +196 -0
  89. package/dist/styles/style.js.map +1 -0
  90. package/dist/utils/BatchQueue.d.ts +10 -0
  91. package/dist/utils/BatchQueue.js +25 -0
  92. package/dist/utils/BatchQueue.js.map +1 -0
  93. package/dist/utils/createFormulaCache.d.ts +3 -0
  94. package/dist/utils/createFormulaCache.js +81 -0
  95. package/dist/utils/createFormulaCache.js.map +1 -0
  96. package/dist/utils/findNearestLine.d.ts +13 -0
  97. package/dist/utils/findNearestLine.js +74 -0
  98. package/dist/utils/findNearestLine.js.map +1 -0
  99. package/dist/utils/findNearestLine.test.d.ts +1 -0
  100. package/dist/utils/findNearestLine.test.js +59 -0
  101. package/dist/utils/findNearestLine.test.js.map +1 -0
  102. package/dist/utils/getDragData.d.ts +1 -0
  103. package/dist/utils/getDragData.js +10 -0
  104. package/dist/utils/getDragData.js.map +1 -0
  105. package/dist/utils/getElementTagName.d.ts +3 -0
  106. package/dist/utils/getElementTagName.js +7 -0
  107. package/dist/utils/getElementTagName.js.map +1 -0
  108. package/dist/utils/nodes.d.ts +21 -0
  109. package/dist/utils/nodes.js +89 -0
  110. package/dist/utils/nodes.js.map +1 -0
  111. package/dist/utils/omitStyle.d.ts +2 -0
  112. package/dist/utils/omitStyle.js +13 -0
  113. package/dist/utils/omitStyle.js.map +1 -0
  114. package/dist/utils/rectHasPoint.d.ts +2 -0
  115. package/dist/utils/rectHasPoint.js +4 -0
  116. package/dist/utils/rectHasPoint.js.map +1 -0
  117. package/dist/utils/setAttribute.d.ts +4 -0
  118. package/dist/utils/setAttribute.js +57 -0
  119. package/dist/utils/setAttribute.js.map +1 -0
  120. package/dist/utils/tryStartViewTransition.d.ts +5 -0
  121. package/dist/utils/tryStartViewTransition.js +14 -0
  122. package/dist/utils/tryStartViewTransition.js.map +1 -0
  123. package/dist/utils/url.d.ts +2 -0
  124. package/dist/utils/url.js +36 -0
  125. package/dist/utils/url.js.map +1 -0
  126. package/package.json +25 -0
  127. package/src/api/createAPI.ts +375 -0
  128. package/src/api/createAPIv2.ts +931 -0
  129. package/src/components/createComponent.ts +280 -0
  130. package/src/components/createElement.ts +240 -0
  131. package/src/components/createNode.ts +381 -0
  132. package/src/components/createSlot.ts +61 -0
  133. package/src/components/createText.test.ts +117 -0
  134. package/src/components/createText.ts +104 -0
  135. package/src/components/renderComponent.ts +145 -0
  136. package/src/context/isContextProvider.ts +12 -0
  137. package/src/context/subscribeToContext.ts +135 -0
  138. package/src/custom-components/components.ts +1 -0
  139. package/src/custom-components/toddle-portal.ts +19 -0
  140. package/src/custom-element/ToddleComponent.ts +315 -0
  141. package/src/custom-element/defineComponents.ts +65 -0
  142. package/src/custom-element.main.ts +24 -0
  143. package/src/debug/logState.ts +30 -0
  144. package/src/editor/drag-drop/dragEnded.ts +75 -0
  145. package/src/editor/drag-drop/dragMove.ts +95 -0
  146. package/src/editor/drag-drop/dragReorder.ts +137 -0
  147. package/src/editor/drag-drop/dragStarted.ts +145 -0
  148. package/src/editor/drag-drop/dropHighlight.ts +82 -0
  149. package/src/editor/drag-drop/getInsertAreas.ts +235 -0
  150. package/src/editor/types.d.ts +36 -0
  151. package/src/editor-preview.main.ts +1782 -0
  152. package/src/events/handleAction.ts +387 -0
  153. package/src/page.main.ts +489 -0
  154. package/src/signal/signal.ts +74 -0
  155. package/src/styles/style.ts +254 -0
  156. package/src/types.d.ts +93 -0
  157. package/src/utils/BatchQueue.ts +24 -0
  158. package/src/utils/createFormulaCache.ts +96 -0
  159. package/src/utils/findNearestLine.test.ts +65 -0
  160. package/src/utils/findNearestLine.ts +92 -0
  161. package/src/utils/getDragData.ts +11 -0
  162. package/src/utils/getElementTagName.ts +14 -0
  163. package/src/utils/nodes.ts +125 -0
  164. package/src/utils/omitStyle.ts +19 -0
  165. package/src/utils/rectHasPoint.ts +5 -0
  166. package/src/utils/setAttribute.ts +56 -0
  167. package/src/utils/tryStartViewTransition.ts +32 -0
  168. package/src/utils/url.ts +45 -0
@@ -0,0 +1,280 @@
1
+ import { isLegacyApi, sortApiObjects } from '@nordcraft/core/dist/api/api'
2
+ import type {
3
+ ComponentData,
4
+ ComponentNodeModel,
5
+ SupportedNamespaces,
6
+ } from '@nordcraft/core/dist/component/component.types'
7
+ import { applyFormula } from '@nordcraft/core/dist/formula/formula'
8
+ import type { RequireFields } from '@nordcraft/core/dist/types'
9
+ import { mapObject } from '@nordcraft/core/dist/utils/collections'
10
+ import { isDefined } from '@nordcraft/core/dist/utils/util'
11
+ import { createLegacyAPI } from '../api/createAPI'
12
+ import { createAPI } from '../api/createAPIv2'
13
+ import { isContextProvider } from '../context/isContextProvider'
14
+ import { subscribeToContext } from '../context/subscribeToContext'
15
+ import { registerComponentToLogState } from '../debug/logState'
16
+ import { handleAction } from '../events/handleAction'
17
+ import type { Signal } from '../signal/signal'
18
+ import { signal } from '../signal/signal'
19
+ import type { ComponentChild, ComponentContext, ContextApi } from '../types'
20
+ import { createFormulaCache } from '../utils/createFormulaCache'
21
+ import { renderComponent } from './renderComponent'
22
+
23
+ export type RenderComponentNodeProps = {
24
+ path: string
25
+ node: ComponentNodeModel
26
+ dataSignal: Signal<ComponentData>
27
+ ctx: ComponentContext
28
+ parentElement: Element | ShadowRoot
29
+ instance: Record<string, string>
30
+ namespace?: SupportedNamespaces
31
+ }
32
+
33
+ export function createComponent({
34
+ node,
35
+ path,
36
+ dataSignal,
37
+ ctx,
38
+ parentElement,
39
+ instance,
40
+ namespace,
41
+ }: RenderComponentNodeProps): ReadonlyArray<Element | Text> {
42
+ const nodeLookupKey = [ctx.package, node.name].filter(isDefined).join('/')
43
+ const component = ctx.components?.find((comp) => comp.name === nodeLookupKey)
44
+ if (!component) {
45
+ // eslint-disable-next-line no-console
46
+ console.warn(
47
+ `Could not find component "${nodeLookupKey}" for component "${
48
+ ctx.component.name
49
+ }". Available components are: ["${ctx.components
50
+ .map((c) => c.name)
51
+ .join('", "')}"]`,
52
+ )
53
+ return []
54
+ }
55
+ const attributesSignal = dataSignal.map((data) => {
56
+ return mapObject(node.attrs, ([attr, value]) => [
57
+ attr,
58
+ value?.type !== 'value'
59
+ ? applyFormula(value, {
60
+ data,
61
+ component: ctx.component,
62
+ formulaCache: ctx.formulaCache,
63
+ root: ctx.root,
64
+ package: ctx.package,
65
+ toddle: ctx.toddle,
66
+ env: ctx.env,
67
+ })
68
+ : value?.value,
69
+ ])
70
+ })
71
+
72
+ const componentDataSignal = signal<ComponentData>({
73
+ Location: dataSignal.get().Location,
74
+ Attributes: attributesSignal.get(),
75
+ Apis: mapObject(component.apis, ([name, api]) => [
76
+ name,
77
+ {
78
+ data: null,
79
+ isLoading:
80
+ api.autoFetch &&
81
+ applyFormula(api.autoFetch, {
82
+ data: dataSignal.get(),
83
+ component,
84
+ formulaCache: ctx.formulaCache,
85
+ root: ctx.root,
86
+ package: ctx.package,
87
+ toddle: ctx.toddle,
88
+ env: ctx.env,
89
+ })
90
+ ? true
91
+ : false,
92
+ error: null,
93
+ },
94
+ ]),
95
+ })
96
+ // Subscribe context before calculating variable initial values to ensure they can reference context values
97
+ subscribeToContext(componentDataSignal, component, ctx)
98
+ componentDataSignal.update((data) => ({
99
+ ...data,
100
+ Variables: mapObject(component.variables, ([name, variable]) => [
101
+ name,
102
+ applyFormula(variable.initialValue, {
103
+ // Initial value
104
+ data: componentDataSignal.get(),
105
+ component,
106
+ formulaCache: ctx.formulaCache,
107
+ root: ctx.root,
108
+ package: ctx.package,
109
+ toddle: ctx.toddle,
110
+ env: ctx.env,
111
+ }),
112
+ ]),
113
+ }))
114
+ registerComponentToLogState(component, componentDataSignal)
115
+
116
+ // Call the abort signal if the component's datasignal is destroyed (component unmounted) to cancel any pending requests
117
+ const abortController = new AbortController()
118
+ componentDataSignal.subscribe(() => {}, {
119
+ destroy: () =>
120
+ abortController.abort(`Component ${component.name} unmounted`),
121
+ })
122
+ const formulaCache = createFormulaCache(component)
123
+
124
+ // Note: this function must run procedurally to ensure apis (which are in correct order) can reference each other
125
+ const apis: Record<string, ContextApi> = {}
126
+ sortApiObjects(Object.entries(component.apis)).forEach(([name, api]) => {
127
+ if (isLegacyApi(api)) {
128
+ apis[name] = createLegacyAPI(api, {
129
+ ...ctx,
130
+ apis,
131
+ component,
132
+ dataSignal: componentDataSignal,
133
+ abortSignal: abortController.signal,
134
+ isRootComponent: false,
135
+ formulaCache,
136
+ package: node.package ?? ctx.package,
137
+ triggerEvent: (eventTrigger, data) => {
138
+ const eventHandler = Object.values(node.events).find(
139
+ (e) => e.trigger === eventTrigger,
140
+ )
141
+ if (eventHandler) {
142
+ eventHandler.actions.forEach((action) =>
143
+ handleAction(action, { ...dataSignal.get(), Event: data }, ctx),
144
+ )
145
+ }
146
+ },
147
+ })
148
+ } else {
149
+ apis[name] = createAPI(api, {
150
+ ...ctx,
151
+ apis,
152
+ component,
153
+ dataSignal: componentDataSignal,
154
+ abortSignal: abortController.signal,
155
+ isRootComponent: false,
156
+ formulaCache,
157
+ package: node.package ?? ctx.package,
158
+ triggerEvent: (eventTrigger, data) => {
159
+ const eventHandler = Object.values(node.events).find(
160
+ (e) => e.trigger === eventTrigger,
161
+ )
162
+ if (eventHandler) {
163
+ eventHandler.actions.forEach((action) =>
164
+ handleAction(action, { ...dataSignal.get(), Event: data }, ctx),
165
+ )
166
+ }
167
+ },
168
+ })
169
+ }
170
+ })
171
+ Object.values(apis)
172
+ .filter(
173
+ (api): api is RequireFields<ContextApi, 'triggerActions'> =>
174
+ api.triggerActions !== undefined,
175
+ )
176
+ .forEach((api) => {
177
+ api.triggerActions()
178
+ })
179
+
180
+ const onEvent = (eventTrigger: string, data: any) => {
181
+ const eventHandler = Object.values(node.events).find(
182
+ (e) => e.trigger === eventTrigger,
183
+ )
184
+ if (eventHandler) {
185
+ eventHandler.actions.forEach((action) =>
186
+ handleAction(action, { ...dataSignal.get(), Event: data }, ctx),
187
+ )
188
+ }
189
+ }
190
+
191
+ let providers = ctx.providers
192
+ if (isContextProvider(component)) {
193
+ // Subscribe to exposed formulas and update the component's data signal
194
+ const formulaDataSignals = Object.fromEntries(
195
+ Object.entries(component.formulas ?? {})
196
+ .filter(([, formula]) => formula.exposeInContext)
197
+ .map(([name, formula]) => [
198
+ name,
199
+ componentDataSignal.map((data) =>
200
+ applyFormula(formula.formula, {
201
+ data,
202
+ component,
203
+ formulaCache: ctx.formulaCache,
204
+ root: ctx.root,
205
+ package: ctx.package,
206
+ toddle: ctx.toddle,
207
+ env: ctx.env,
208
+ }),
209
+ ),
210
+ ]),
211
+ )
212
+
213
+ providers = {
214
+ ...providers,
215
+ [component.name]: {
216
+ component,
217
+ formulaDataSignals,
218
+ ctx: {
219
+ ...ctx,
220
+ apis,
221
+ component,
222
+ dataSignal: componentDataSignal,
223
+ abortSignal: abortController.signal,
224
+ triggerEvent: onEvent,
225
+ },
226
+ },
227
+ }
228
+ }
229
+
230
+ const children: Record<string, Array<ComponentChild>> = {}
231
+ for (let i = 0; i < node.children.length; i++) {
232
+ const childId = node.children[i]
233
+ const childNode = ctx.component.nodes[childId]
234
+ const slotName = childNode.slot ?? 'default'
235
+ children[slotName] = children[slotName] ?? []
236
+ children[slotName].push({
237
+ id: childId,
238
+ path: `${path}.${i}[${slotName}]`,
239
+ dataSignal,
240
+ ctx: {
241
+ ...ctx,
242
+ package: node.package ?? ctx.package,
243
+ },
244
+ })
245
+ }
246
+
247
+ attributesSignal.subscribe(
248
+ (Attributes) =>
249
+ componentDataSignal.update((data) => ({
250
+ ...data,
251
+ Attributes,
252
+ })),
253
+ { destroy: () => componentDataSignal.destroy() },
254
+ )
255
+
256
+ return renderComponent({
257
+ dataSignal: componentDataSignal,
258
+ component,
259
+ components: ctx.components,
260
+ path,
261
+ root: ctx.root,
262
+ isRootComponent: false,
263
+ children,
264
+ formulaCache,
265
+ providers,
266
+ apis,
267
+ abortSignal: abortController.signal,
268
+ package: node.package ?? ctx.package,
269
+ parentElement,
270
+ onEvent,
271
+ toddle: ctx.toddle,
272
+ env: ctx.env,
273
+ namespace,
274
+ // If the root node is another component, then append and forward previous instance
275
+ instance:
276
+ node.id === 'root'
277
+ ? { ...instance, [ctx.component.name]: 'root' }
278
+ : { [ctx.component.name]: node.id ?? '' },
279
+ })
280
+ }
@@ -0,0 +1,240 @@
1
+ import type {
2
+ ElementNodeModel,
3
+ NodeModel,
4
+ SupportedNamespaces,
5
+ } from '@nordcraft/core/dist/component/component.types'
6
+ import { applyFormula } from '@nordcraft/core/dist/formula/formula'
7
+ import {
8
+ getClassName,
9
+ toValidClassName,
10
+ } from '@nordcraft/core/dist/styling/className'
11
+ import { isDefined, toBoolean } from '@nordcraft/core/dist/utils/util'
12
+ import { handleAction } from '../events/handleAction'
13
+ import type { Signal } from '../signal/signal'
14
+ import { getDragData } from '../utils/getDragData'
15
+ import { getElementTagName } from '../utils/getElementTagName'
16
+ import { setAttribute } from '../utils/setAttribute'
17
+ import type { NodeRenderer } from './createNode'
18
+ import { createNode } from './createNode'
19
+
20
+ export function createElement({
21
+ node,
22
+ dataSignal,
23
+ id,
24
+ path,
25
+ ctx,
26
+ namespace,
27
+ instance,
28
+ }: NodeRenderer<ElementNodeModel>): Element {
29
+ const tag = getElementTagName(node, ctx, id)
30
+ switch (tag) {
31
+ case 'svg': {
32
+ namespace = 'http://www.w3.org/2000/svg'
33
+ break
34
+ }
35
+ case 'math': {
36
+ namespace = 'http://www.w3.org/1998/Math/MathML'
37
+ break
38
+ }
39
+ }
40
+
41
+ // Explicitly setting a namespace has precedence over inferring it from the tag
42
+ if (node.attrs['xmlns'] && node.attrs['xmlns'].type === 'value') {
43
+ namespace = String(node.attrs['xmlns'].value) as SupportedNamespaces
44
+ }
45
+
46
+ const elem = namespace
47
+ ? (document.createElementNS(namespace, tag) as SVGElement | MathMLElement)
48
+ : document.createElement(tag)
49
+
50
+ elem.setAttribute('data-node-id', id)
51
+ if (path) {
52
+ elem.setAttribute('data-id', path)
53
+ }
54
+ if (ctx.isRootComponent === false && id !== 'root') {
55
+ elem.setAttribute('data-component', ctx.component.name)
56
+ }
57
+ const classHash = getClassName([node.style, node.variants])
58
+ elem.classList.add(classHash)
59
+ if (instance && id === 'root') {
60
+ Object.entries(instance).forEach(([key, value]) => {
61
+ elem.classList.add(toValidClassName(`${key}:${value}`))
62
+ })
63
+ }
64
+ if (node.classes) {
65
+ Object.entries(node.classes)?.forEach(([className, { formula }]) => {
66
+ if (formula) {
67
+ const classSignal = dataSignal.map((data) =>
68
+ toBoolean(
69
+ applyFormula(formula, {
70
+ data,
71
+ component: ctx.component,
72
+ formulaCache: ctx.formulaCache,
73
+ root: ctx.root,
74
+ package: ctx.package,
75
+ toddle: ctx.toddle,
76
+ env: ctx.env,
77
+ }),
78
+ ),
79
+ )
80
+ classSignal.subscribe((show) =>
81
+ show
82
+ ? elem.classList.add(className)
83
+ : elem.classList.remove(className),
84
+ )
85
+ } else {
86
+ elem.classList.add(className)
87
+ }
88
+ })
89
+ }
90
+
91
+ Object.entries(node.attrs).forEach(([attr, value]) => {
92
+ if (!isDefined(value)) {
93
+ return
94
+ }
95
+ let o: Signal<any> | undefined
96
+ const setupAttribute = () => {
97
+ if (value.type === 'value') {
98
+ setAttribute(elem, attr, value?.value)
99
+ } else {
100
+ o = dataSignal.map((data) =>
101
+ applyFormula(value, {
102
+ data,
103
+ component: ctx.component,
104
+ formulaCache: ctx.formulaCache,
105
+ root: ctx.root,
106
+ package: ctx.package,
107
+ toddle: ctx.toddle,
108
+ env: ctx.env,
109
+ }),
110
+ )
111
+ o.subscribe((val) => {
112
+ setAttribute(elem, attr, val)
113
+ })
114
+ }
115
+ }
116
+ if (
117
+ attr === 'autofocus' &&
118
+ ctx.env.runtime === 'preview' &&
119
+ ctx.toddle._preview
120
+ ) {
121
+ ctx.toddle._preview.showSignal.subscribe(({ testMode }) => {
122
+ if (testMode) {
123
+ setupAttribute()
124
+ } else {
125
+ o?.destroy()
126
+ elem.removeAttribute(attr)
127
+ }
128
+ })
129
+ } else {
130
+ setupAttribute()
131
+ }
132
+ })
133
+ node['style-variables']?.forEach(({ formula, name, unit }) => {
134
+ const sig = dataSignal.map((data) => {
135
+ const value = applyFormula(formula, {
136
+ data,
137
+ component: ctx.component,
138
+ formulaCache: ctx.formulaCache,
139
+ root: ctx.root,
140
+ package: ctx.package,
141
+ toddle: ctx.toddle,
142
+ env: ctx.env,
143
+ })
144
+ return unit ? value + unit : value
145
+ })
146
+ sig.subscribe((value) => elem.style.setProperty(`--${name}`, value))
147
+ })
148
+ Object.values(node.events).forEach((event) => {
149
+ const handler = (e: Event) => {
150
+ event.actions.forEach((action) => {
151
+ if (e instanceof DragEvent) {
152
+ ;(e as any).data = getDragData(e)
153
+ }
154
+ if (e instanceof ClipboardEvent) {
155
+ try {
156
+ ;(e as any).data = Array.from(e.clipboardData?.items ?? []).reduce<
157
+ Record<string, any>
158
+ >((dragData, item) => {
159
+ try {
160
+ dragData[item.type] = JSON.parse(
161
+ e.clipboardData?.getData(item.type) as any,
162
+ )
163
+ } catch {
164
+ dragData[item.type] = e.clipboardData?.getData(item.type)
165
+ }
166
+ return dragData
167
+ }, {})
168
+ } catch (e) {
169
+ // eslint-disable-next-line no-console
170
+ console.error('Could not get paste data', e)
171
+ }
172
+ }
173
+ void handleAction(action, { ...dataSignal.get(), Event: e }, ctx, e)
174
+ })
175
+ return false
176
+ }
177
+ elem.addEventListener(event.trigger, handler)
178
+ })
179
+
180
+ // for script, style & SVG<text> tags we only render text child.
181
+ // this can be removed once we fix the editor to handle raw text nodes without wrapping <span>
182
+ const nodeTag = node.tag.toLocaleLowerCase()
183
+ if (nodeTag === 'script' || nodeTag === 'style') {
184
+ const textValues: Array<Signal<string> | string> = []
185
+ node.children
186
+ .map<NodeModel | undefined>((child) => ctx.component.nodes[child])
187
+ .filter((node) => node?.type === 'text')
188
+ .forEach((node) => {
189
+ if (node.value.type === 'value') {
190
+ textValues.push(String(node.value.value))
191
+ } else {
192
+ const textSignal = dataSignal.map((data) => {
193
+ return String(
194
+ applyFormula(node.value, {
195
+ data,
196
+ component: ctx.component,
197
+ formulaCache: ctx.formulaCache,
198
+ root: ctx.root,
199
+ package: ctx.package,
200
+ toddle: ctx.toddle,
201
+ env: ctx.env,
202
+ }),
203
+ )
204
+ })
205
+ textValues.push(textSignal)
206
+ }
207
+ })
208
+
209
+ // if all values are string, we can directly set textContent
210
+ if (textValues.every((value) => typeof value === 'string')) {
211
+ elem.textContent = textValues.join('')
212
+ }
213
+
214
+ // for each signal, we subscribe and rewrite the entire textContent from all text nodes
215
+ textValues
216
+ .filter((value) => typeof value !== 'string')
217
+ .forEach((valueSignal) => {
218
+ valueSignal.subscribe(() => {
219
+ elem.textContent = textValues
220
+ .map((value) => (typeof value === 'string' ? value : value.get()))
221
+ .join('')
222
+ })
223
+ })
224
+ } else {
225
+ node.children.forEach((child, i) => {
226
+ const childNodes = createNode({
227
+ parentElement: elem,
228
+ id: child,
229
+ path: path + '.' + i,
230
+ dataSignal,
231
+ ctx,
232
+ namespace,
233
+ instance,
234
+ })
235
+ childNodes.forEach((childNode) => elem.appendChild(childNode))
236
+ })
237
+ }
238
+
239
+ return elem
240
+ }