@nordcraft/runtime 1.0.60 → 1.0.62
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/custom-element.main.esm.js +13 -13
- package/dist/custom-element.main.esm.js.map +3 -3
- package/dist/editor-preview.main.js +3 -1
- package/dist/editor-preview.main.js.map +1 -1
- package/dist/events/handleAction.d.ts +1 -1
- package/dist/events/handleAction.js +2 -2
- package/dist/events/handleAction.js.map +1 -1
- package/dist/page.main.esm.js +3 -3
- package/dist/page.main.esm.js.map +3 -3
- package/dist/page.main.js +138 -133
- package/dist/page.main.js.map +1 -1
- package/package.json +3 -3
- package/src/editor-preview.main.ts +3 -1
- package/src/events/handleAction.ts +2 -2
- package/src/page.main.ts +162 -152
package/src/page.main.ts
CHANGED
|
@@ -26,6 +26,7 @@ import { createAPI } from './api/createAPIv2'
|
|
|
26
26
|
import { renderComponent } from './components/renderComponent'
|
|
27
27
|
import { isContextProvider } from './context/isContextProvider'
|
|
28
28
|
import { initLogState, registerComponentToLogState } from './debug/logState'
|
|
29
|
+
import type { Signal } from './signal/signal'
|
|
29
30
|
import { signal } from './signal/signal'
|
|
30
31
|
import type { ComponentContext, LocationSignal } from './types'
|
|
31
32
|
|
|
@@ -175,6 +176,167 @@ export const createRoot = (domNode: HTMLElement) => {
|
|
|
175
176
|
]),
|
|
176
177
|
})
|
|
177
178
|
|
|
179
|
+
registerComponentToLogState(component, dataSignal)
|
|
180
|
+
|
|
181
|
+
routeSignal.subscribe((route) =>
|
|
182
|
+
dataSignal.update((data) => ({
|
|
183
|
+
...data,
|
|
184
|
+
'URL parameters': route as Record<string, string>,
|
|
185
|
+
Attributes: route,
|
|
186
|
+
})),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
// Call the abort signal if the component's datasignal is destroyed (component unmounted) to cancel any pending requests
|
|
190
|
+
const abortController = new AbortController()
|
|
191
|
+
dataSignal.subscribe(() => {}, {
|
|
192
|
+
destroy: () =>
|
|
193
|
+
abortController.abort(`Component ${component.name} unmounted`),
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const ctx: ComponentContext = {
|
|
197
|
+
component,
|
|
198
|
+
components: window.toddle.components,
|
|
199
|
+
root: document,
|
|
200
|
+
isRootComponent: true,
|
|
201
|
+
dataSignal,
|
|
202
|
+
abortSignal: abortController.signal,
|
|
203
|
+
children: {},
|
|
204
|
+
formulaCache: {},
|
|
205
|
+
providers: {},
|
|
206
|
+
apis: {},
|
|
207
|
+
toddle: window.toddle,
|
|
208
|
+
triggerEvent: (event: string, data: unknown) =>
|
|
209
|
+
// eslint-disable-next-line no-console
|
|
210
|
+
console.info('EVENT FIRED', event, data),
|
|
211
|
+
package: undefined,
|
|
212
|
+
env,
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Note: this function must run procedurally to ensure apis (which are in correct order) can reference each other
|
|
216
|
+
sortApiObjects(Object.entries(component.apis)).forEach(([name, api]) => {
|
|
217
|
+
if (isLegacyApi(api)) {
|
|
218
|
+
ctx.apis[name] = createLegacyAPI(api, ctx)
|
|
219
|
+
} else {
|
|
220
|
+
ctx.apis[name] = createAPI({
|
|
221
|
+
apiRequest: api,
|
|
222
|
+
ctx,
|
|
223
|
+
componentData: dataSignal.get(),
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
// Trigger actions for all APIs after all of them are created.
|
|
228
|
+
Object.values(ctx.apis)
|
|
229
|
+
.filter(isContextApiV2)
|
|
230
|
+
.forEach((api) => {
|
|
231
|
+
api.triggerActions(dataSignal.get())
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
let providers = ctx.providers
|
|
235
|
+
if (isContextProvider(component)) {
|
|
236
|
+
// Subscribe to exposed formulas and update the component's data signal
|
|
237
|
+
const formulaDataSignals = Object.fromEntries(
|
|
238
|
+
Object.entries(component.formulas ?? {})
|
|
239
|
+
.filter(([, formula]) => formula.exposeInContext)
|
|
240
|
+
.map(([name, formula]) => [
|
|
241
|
+
name,
|
|
242
|
+
dataSignal.map((data) =>
|
|
243
|
+
applyFormula(formula.formula, {
|
|
244
|
+
data,
|
|
245
|
+
component,
|
|
246
|
+
formulaCache: ctx.formulaCache,
|
|
247
|
+
root: ctx.root,
|
|
248
|
+
package: ctx.package,
|
|
249
|
+
toddle: window.toddle,
|
|
250
|
+
env,
|
|
251
|
+
}),
|
|
252
|
+
),
|
|
253
|
+
]),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
providers = {
|
|
257
|
+
...providers,
|
|
258
|
+
[component.name]: {
|
|
259
|
+
component,
|
|
260
|
+
formulaDataSignals,
|
|
261
|
+
ctx,
|
|
262
|
+
},
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// We can only setup meta updates after the dataSignal has been initiated with API data etc.
|
|
267
|
+
setupMetaUpdates(component, dataSignal)
|
|
268
|
+
|
|
269
|
+
const elements = renderComponent({
|
|
270
|
+
...ctx,
|
|
271
|
+
providers,
|
|
272
|
+
path: '0',
|
|
273
|
+
package: undefined,
|
|
274
|
+
onEvent: ctx.triggerEvent,
|
|
275
|
+
parentElement: domNode,
|
|
276
|
+
instance: {},
|
|
277
|
+
})
|
|
278
|
+
domNode.innerText = ''
|
|
279
|
+
elements.forEach((elem) => {
|
|
280
|
+
domNode.appendChild(elem)
|
|
281
|
+
})
|
|
282
|
+
window.__toddle.isPageLoaded = true
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function parseUrl(component: Component) {
|
|
286
|
+
const path = window.location.pathname.split('/').slice(1)
|
|
287
|
+
let params: Record<string, string | null> = {}
|
|
288
|
+
if (component.route) {
|
|
289
|
+
component.route.path.forEach((segment, i) => {
|
|
290
|
+
if (segment.type === 'param') {
|
|
291
|
+
if (isDefined(path[i]) && path[i] !== '') {
|
|
292
|
+
params[segment.name] = decodeURIComponent(path[i])
|
|
293
|
+
} else {
|
|
294
|
+
params[segment.name] = null
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
params[segment.name] = segment.name
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
} else {
|
|
301
|
+
const urlPattern = match<Record<string, string>>(component.page ?? '', {
|
|
302
|
+
decode: decodeURIComponent,
|
|
303
|
+
})
|
|
304
|
+
const res = urlPattern(window.location.pathname) || {
|
|
305
|
+
params: {},
|
|
306
|
+
}
|
|
307
|
+
params = res.params
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const [hash] = window.location.hash.split('?')
|
|
311
|
+
const query = parseQuery(window.location.search) as Record<string, string>
|
|
312
|
+
// Explicitly set all query params to null by default
|
|
313
|
+
// to avoid undefined values in the runtime
|
|
314
|
+
const defaultQueryParams = Object.keys(component.route?.query ?? {}).reduce<
|
|
315
|
+
Record<string, null>
|
|
316
|
+
>((params, key) => ({ ...params, [key]: null }), {})
|
|
317
|
+
return {
|
|
318
|
+
params,
|
|
319
|
+
hash: hash?.slice(1),
|
|
320
|
+
query: { ...defaultQueryParams, ...query },
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const parseQuery = (queryString: string) =>
|
|
325
|
+
Object.fromEntries(
|
|
326
|
+
queryString
|
|
327
|
+
.replace('?', '')
|
|
328
|
+
.split('&')
|
|
329
|
+
.filter((pair) => pair !== '')
|
|
330
|
+
.map((pair: string) => {
|
|
331
|
+
return pair.split('=').map(decodeURIComponent)
|
|
332
|
+
}),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
const setupMetaUpdates = (
|
|
336
|
+
component: Component,
|
|
337
|
+
dataSignal: Signal<ComponentData>,
|
|
338
|
+
) => {
|
|
339
|
+
// Handle dynamic updates of the document language
|
|
178
340
|
const langFormula = component.route?.info?.language?.formula
|
|
179
341
|
const dynamicLang = langFormula && langFormula.type !== 'value'
|
|
180
342
|
if (dynamicLang) {
|
|
@@ -358,156 +520,4 @@ export const createRoot = (domNode: HTMLElement) => {
|
|
|
358
520
|
})
|
|
359
521
|
}
|
|
360
522
|
}
|
|
361
|
-
|
|
362
|
-
registerComponentToLogState(component, dataSignal)
|
|
363
|
-
|
|
364
|
-
routeSignal.subscribe((route) =>
|
|
365
|
-
dataSignal.update((data) => ({
|
|
366
|
-
...data,
|
|
367
|
-
'URL parameters': route as Record<string, string>,
|
|
368
|
-
Attributes: route,
|
|
369
|
-
})),
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
// Call the abort signal if the component's datasignal is destroyed (component unmounted) to cancel any pending requests
|
|
373
|
-
const abortController = new AbortController()
|
|
374
|
-
dataSignal.subscribe(() => {}, {
|
|
375
|
-
destroy: () =>
|
|
376
|
-
abortController.abort(`Component ${component.name} unmounted`),
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
const ctx: ComponentContext = {
|
|
380
|
-
component,
|
|
381
|
-
components: window.toddle.components,
|
|
382
|
-
root: document,
|
|
383
|
-
isRootComponent: true,
|
|
384
|
-
dataSignal,
|
|
385
|
-
abortSignal: abortController.signal,
|
|
386
|
-
children: {},
|
|
387
|
-
formulaCache: {},
|
|
388
|
-
providers: {},
|
|
389
|
-
apis: {},
|
|
390
|
-
toddle: window.toddle,
|
|
391
|
-
triggerEvent: (event: string, data: unknown) =>
|
|
392
|
-
// eslint-disable-next-line no-console
|
|
393
|
-
console.info('EVENT FIRED', event, data),
|
|
394
|
-
package: undefined,
|
|
395
|
-
env,
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Note: this function must run procedurally to ensure apis (which are in correct order) can reference each other
|
|
399
|
-
sortApiObjects(Object.entries(component.apis)).forEach(([name, api]) => {
|
|
400
|
-
if (isLegacyApi(api)) {
|
|
401
|
-
ctx.apis[name] = createLegacyAPI(api, ctx)
|
|
402
|
-
} else {
|
|
403
|
-
ctx.apis[name] = createAPI({
|
|
404
|
-
apiRequest: api,
|
|
405
|
-
ctx,
|
|
406
|
-
componentData: dataSignal.get(),
|
|
407
|
-
})
|
|
408
|
-
}
|
|
409
|
-
})
|
|
410
|
-
// Trigger actions for all APIs after all of them are created.
|
|
411
|
-
Object.values(ctx.apis)
|
|
412
|
-
.filter(isContextApiV2)
|
|
413
|
-
.forEach((api) => {
|
|
414
|
-
api.triggerActions(dataSignal.get())
|
|
415
|
-
})
|
|
416
|
-
|
|
417
|
-
let providers = ctx.providers
|
|
418
|
-
if (isContextProvider(component)) {
|
|
419
|
-
// Subscribe to exposed formulas and update the component's data signal
|
|
420
|
-
const formulaDataSignals = Object.fromEntries(
|
|
421
|
-
Object.entries(component.formulas ?? {})
|
|
422
|
-
.filter(([, formula]) => formula.exposeInContext)
|
|
423
|
-
.map(([name, formula]) => [
|
|
424
|
-
name,
|
|
425
|
-
dataSignal.map((data) =>
|
|
426
|
-
applyFormula(formula.formula, {
|
|
427
|
-
data,
|
|
428
|
-
component,
|
|
429
|
-
formulaCache: ctx.formulaCache,
|
|
430
|
-
root: ctx.root,
|
|
431
|
-
package: ctx.package,
|
|
432
|
-
toddle: window.toddle,
|
|
433
|
-
env,
|
|
434
|
-
}),
|
|
435
|
-
),
|
|
436
|
-
]),
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
providers = {
|
|
440
|
-
...providers,
|
|
441
|
-
[component.name]: {
|
|
442
|
-
component,
|
|
443
|
-
formulaDataSignals,
|
|
444
|
-
ctx,
|
|
445
|
-
},
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const elements = renderComponent({
|
|
450
|
-
...ctx,
|
|
451
|
-
providers,
|
|
452
|
-
path: '0',
|
|
453
|
-
package: undefined,
|
|
454
|
-
onEvent: ctx.triggerEvent,
|
|
455
|
-
parentElement: domNode,
|
|
456
|
-
instance: {},
|
|
457
|
-
})
|
|
458
|
-
domNode.innerText = ''
|
|
459
|
-
elements.forEach((elem) => {
|
|
460
|
-
domNode.appendChild(elem)
|
|
461
|
-
})
|
|
462
|
-
window.__toddle.isPageLoaded = true
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function parseUrl(component: Component) {
|
|
466
|
-
const path = window.location.pathname.split('/').slice(1)
|
|
467
|
-
let params: Record<string, string | null> = {}
|
|
468
|
-
if (component.route) {
|
|
469
|
-
component.route.path.forEach((segment, i) => {
|
|
470
|
-
if (segment.type === 'param') {
|
|
471
|
-
if (isDefined(path[i]) && path[i] !== '') {
|
|
472
|
-
params[segment.name] = decodeURIComponent(path[i])
|
|
473
|
-
} else {
|
|
474
|
-
params[segment.name] = null
|
|
475
|
-
}
|
|
476
|
-
} else {
|
|
477
|
-
params[segment.name] = segment.name
|
|
478
|
-
}
|
|
479
|
-
})
|
|
480
|
-
} else {
|
|
481
|
-
const urlPattern = match<Record<string, string>>(component.page ?? '', {
|
|
482
|
-
decode: decodeURIComponent,
|
|
483
|
-
})
|
|
484
|
-
const res = urlPattern(window.location.pathname) || {
|
|
485
|
-
params: {},
|
|
486
|
-
}
|
|
487
|
-
params = res.params
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const [hash] = window.location.hash.split('?')
|
|
491
|
-
const query = parseQuery(window.location.search) as Record<string, string>
|
|
492
|
-
// Explicitly set all query params to null by default
|
|
493
|
-
// to avoid undefined values in the runtime
|
|
494
|
-
const defaultQueryParams = Object.keys(component.route?.query ?? {}).reduce<
|
|
495
|
-
Record<string, null>
|
|
496
|
-
>((params, key) => ({ ...params, [key]: null }), {})
|
|
497
|
-
return {
|
|
498
|
-
params,
|
|
499
|
-
hash: hash?.slice(1),
|
|
500
|
-
query: { ...defaultQueryParams, ...query },
|
|
501
|
-
}
|
|
502
523
|
}
|
|
503
|
-
|
|
504
|
-
const parseQuery = (queryString: string) =>
|
|
505
|
-
Object.fromEntries(
|
|
506
|
-
queryString
|
|
507
|
-
.replace('?', '')
|
|
508
|
-
.split('&')
|
|
509
|
-
.filter((pair) => pair !== '')
|
|
510
|
-
.map((pair: string) => {
|
|
511
|
-
return pair.split('=').map(decodeURIComponent)
|
|
512
|
-
}),
|
|
513
|
-
)
|