@nordcraft/runtime 1.0.74 → 1.0.76

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 (43) hide show
  1. package/README.md +1 -1
  2. package/dist/api/createAPI.d.ts +3 -2
  3. package/dist/api/createAPI.js.map +1 -1
  4. package/dist/components/createComponent.js +8 -8
  5. package/dist/components/createComponent.js.map +1 -1
  6. package/dist/components/createElement.js +2 -2
  7. package/dist/components/createElement.js.map +1 -1
  8. package/dist/components/createNode.js +4 -4
  9. package/dist/components/createNode.js.map +1 -1
  10. package/dist/components/renderComponent.js +4 -4
  11. package/dist/components/renderComponent.js.map +1 -1
  12. package/dist/context/subscribeToContext.js +2 -5
  13. package/dist/context/subscribeToContext.js.map +1 -1
  14. package/dist/custom-element/ToddleComponent.js +4 -4
  15. package/dist/custom-element/ToddleComponent.js.map +1 -1
  16. package/dist/custom-element.main.esm.js +15 -15
  17. package/dist/custom-element.main.esm.js.map +3 -3
  18. package/dist/editor/types.d.ts +4 -0
  19. package/dist/editor/types.js.map +1 -1
  20. package/dist/editor-preview.main.js +55 -39
  21. package/dist/editor-preview.main.js.map +1 -1
  22. package/dist/page.main.esm.js +3 -3
  23. package/dist/page.main.esm.js.map +3 -3
  24. package/dist/page.main.js +2 -2
  25. package/dist/page.main.js.map +1 -1
  26. package/dist/styles/CustomPropertyStyleSheet.d.ts +11 -10
  27. package/dist/styles/CustomPropertyStyleSheet.js.map +1 -1
  28. package/dist/utils/nodes.js +1 -1
  29. package/dist/utils/nodes.js.map +1 -1
  30. package/package.json +4 -4
  31. package/src/api/createAPI.ts +2 -1
  32. package/src/api/createAPIv2.ts +7 -7
  33. package/src/components/createComponent.ts +43 -37
  34. package/src/components/createElement.ts +2 -2
  35. package/src/components/createNode.ts +5 -5
  36. package/src/components/renderComponent.ts +4 -4
  37. package/src/context/subscribeToContext.ts +5 -5
  38. package/src/custom-element/ToddleComponent.ts +24 -21
  39. package/src/editor/types.ts +4 -0
  40. package/src/editor-preview.main.ts +79 -47
  41. package/src/page.main.ts +14 -12
  42. package/src/styles/CustomPropertyStyleSheet.ts +18 -14
  43. package/src/utils/nodes.ts +1 -1
@@ -453,7 +453,7 @@ export const createRoot = (
453
453
 
454
454
  const node = getDOMNodeFromNodeId(selectedNodeId)
455
455
  const element =
456
- component?.nodes[node?.getAttribute('data-node-id') ?? '']
456
+ component?.nodes?.[node?.getAttribute('data-node-id') ?? '']
457
457
  if (
458
458
  node &&
459
459
  element &&
@@ -544,7 +544,7 @@ export const createRoot = (
544
544
  return false
545
545
  }
546
546
  const nodeId = getNodeId(component, id.split('.').slice(1))
547
- const node = nodeId ? component?.nodes[nodeId] : undefined
547
+ const node = nodeId ? component?.nodes?.[nodeId] : undefined
548
548
  if (!node) {
549
549
  return false
550
550
  }
@@ -562,7 +562,7 @@ export const createRoot = (
562
562
  if (message.data.metaKey) {
563
563
  // Figure out if the clicked element is a text element
564
564
  // or if one of its descendants is a text element
565
- const root = component.nodes.root
565
+ const root = component.nodes?.root
566
566
  if (root && id) {
567
567
  const nodeLookup = getNodeAndAncestors(component, root, id)
568
568
  if (nodeLookup?.node.type === 'text') {
@@ -574,7 +574,7 @@ export const createRoot = (
574
574
  const firstTextChild =
575
575
  nodeLookup?.node.type === 'element'
576
576
  ? nodeLookup.node.children.find(
577
- (c) => component?.nodes[c]?.type === 'text',
577
+ (c) => component?.nodes?.[c]?.type === 'text',
578
578
  )
579
579
  : undefined
580
580
  if (firstTextChild) {
@@ -603,7 +603,7 @@ export const createRoot = (
603
603
  mode === 'design'
604
604
  ) {
605
605
  // Figure out if the clicked element is a component
606
- const root = component.nodes.root
606
+ const root = component.nodes?.root
607
607
  if (root) {
608
608
  const nodeLookup = getNodeAndAncestors(component, root, id)
609
609
  if (
@@ -659,7 +659,7 @@ export const createRoot = (
659
659
  }
660
660
  case 'introspect_qraphql_api': {
661
661
  const { apiKey } = message.data
662
- const api = component?.apis[apiKey]
662
+ const api = component?.apis?.[apiKey]
663
663
  if (api && !isLegacyApi(api) && component) {
664
664
  const formulaContext: FormulaContext = {
665
665
  component,
@@ -737,7 +737,7 @@ export const createRoot = (
737
737
  parent: parentDataId,
738
738
  index: !isNaN(nextSiblingId)
739
739
  ? nextSiblingId
740
- : component?.nodes[parentNodeId]?.children?.length,
740
+ : component?.nodes?.[parentNodeId]?.children?.length,
741
741
  })
742
742
  dragState = null
743
743
  })
@@ -913,43 +913,75 @@ export const createRoot = (
913
913
  })
914
914
  break
915
915
  case 'preview_style':
916
- const { styles: previewStyleStyles, theme } = message.data
916
+ const { styles: previewStyleStyles, theme, resources } = message.data
917
917
  cancelAnimationFrame(previewStyleAnimationFrame)
918
918
  previewStyleAnimationFrame = requestAnimationFrame(() => {
919
+ // Allow for temporarily adding preview resources (e.g. fonts).
920
+ const resourceElements = Array.from(
921
+ document.head.querySelectorAll('[data-id="preview-resource"]'),
922
+ )
923
+ // Remove any resources that are no longer needed
924
+ resourceElements.forEach((el) => {
925
+ if (
926
+ !resources ||
927
+ resources.length === 0 ||
928
+ !resources.some((res) => res.href === el.getAttribute('href'))
929
+ ) {
930
+ el.remove()
931
+ }
932
+ })
933
+ resources
934
+ ?.filter(
935
+ (resource) =>
936
+ !resourceElements.some(
937
+ (el) => el.getAttribute('href') === resource.href,
938
+ ),
939
+ )
940
+ .forEach((resource) => {
941
+ const resourceElement = document.createElement('link')
942
+ resourceElement.setAttribute('data-id', 'preview-resource')
943
+ resourceElement.rel = 'stylesheet'
944
+ resourceElement.href = resource.href
945
+ document.head.appendChild(resourceElement)
946
+ })
947
+
919
948
  // Update or create a new style tag and set the given styles with important priority
920
- let styleTag = document.head.querySelector(
949
+ let styleElement = document.head.querySelector(
921
950
  '[data-id="selected-node-styles"]',
922
951
  )
923
952
 
924
953
  // Cleanup when null styles are sent
925
954
  if (!previewStyleStyles) {
926
- styleTag?.remove()
955
+ styleElement?.remove()
927
956
  return
928
957
  }
929
958
 
930
- if (!styleTag) {
931
- styleTag = document.createElement('style')
932
- styleTag.setAttribute('data-id', 'selected-node-styles')
933
- document.head.appendChild(styleTag)
959
+ if (!styleElement) {
960
+ styleElement = document.createElement('style')
961
+ styleElement.setAttribute('data-id', 'selected-node-styles')
962
+ document.head.appendChild(styleElement)
934
963
  }
935
964
 
936
965
  // If style variant targets a pseudo-element, apply styles to it instead
937
966
  let pseudoElement = ''
938
967
  if (component && styleVariantSelection) {
939
- const nodeLookup = getNodeAndAncestors(
940
- component,
941
- component.nodes.root,
942
- styleVariantSelection.nodeId,
943
- )
968
+ const rootNode = component.nodes?.root
969
+ if (rootNode) {
970
+ const nodeLookup = getNodeAndAncestors(
971
+ component,
972
+ rootNode,
973
+ styleVariantSelection.nodeId,
974
+ )
944
975
 
945
- if (
946
- (nodeLookup?.node.type === 'element' ||
947
- nodeLookup?.node.type === 'component') &&
948
- nodeLookup.node.variants?.[
949
- styleVariantSelection.styleVariantIndex
950
- ].pseudoElement
951
- ) {
952
- pseudoElement = `::${nodeLookup.node.variants[styleVariantSelection.styleVariantIndex].pseudoElement}`
976
+ if (
977
+ (nodeLookup?.node.type === 'element' ||
978
+ nodeLookup?.node.type === 'component') &&
979
+ nodeLookup.node.variants?.[
980
+ styleVariantSelection.styleVariantIndex
981
+ ].pseudoElement
982
+ ) {
983
+ pseudoElement = `::${nodeLookup.node.variants[styleVariantSelection.styleVariantIndex].pseudoElement}`
984
+ }
953
985
  }
954
986
  }
955
987
 
@@ -1003,12 +1035,12 @@ export const createRoot = (
1003
1035
  getThemeEntries(theme.value, theme.key),
1004
1036
  ),
1005
1037
  )
1006
- styleTag.innerHTML = cssBlocks.join('\n')
1038
+ styleElement.innerHTML = cssBlocks.join('\n')
1007
1039
  } else {
1008
1040
  const previewStyles = Object.entries(previewStyleStyles)
1009
1041
  .map(([key, value]) => `${key}: ${value} !important;`)
1010
1042
  .join('\n')
1011
- styleTag.innerHTML = `[data-id="${selectedNodeId}"]${pseudoElement}, [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("]${pseudoElement} {
1043
+ styleElement.innerHTML = `[data-id="${selectedNodeId}"]${pseudoElement}, [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("]${pseudoElement} {
1012
1044
  ${previewStyles}
1013
1045
  transition: none !important;
1014
1046
  }`
@@ -1049,7 +1081,7 @@ export const createRoot = (
1049
1081
  }
1050
1082
  if (mode === 'design') {
1051
1083
  if (selectedNodeId !== null) {
1052
- const root = _component?.nodes.root
1084
+ const root = _component?.nodes?.root
1053
1085
  if (root) {
1054
1086
  const nodeLookup = getNodeAndAncestors(
1055
1087
  _component,
@@ -1077,7 +1109,7 @@ export const createRoot = (
1077
1109
  nodeId: selectedNodeId,
1078
1110
  styleVariantIndex: variantIndex,
1079
1111
  }
1080
- const root = component?.nodes.root
1112
+ const root = component?.nodes?.root
1081
1113
  if (root && component) {
1082
1114
  const nodeLookup = getNodeAndAncestors(component, root, selectedNodeId)
1083
1115
  if (nodeLookup) {
@@ -1158,10 +1190,10 @@ export const createRoot = (
1158
1190
  if (
1159
1191
  fastDeepEqual(ctx?.component.attributes, _component.attributes) === false
1160
1192
  ) {
1161
- Attributes = mapObject(_component.attributes, ([name, { testValue }]) => [
1162
- name,
1163
- testValue,
1164
- ])
1193
+ Attributes = mapObject(
1194
+ _component.attributes ?? {},
1195
+ ([name, { testValue }]) => [name, testValue],
1196
+ )
1165
1197
  }
1166
1198
  if (
1167
1199
  _component.route &&
@@ -1205,10 +1237,10 @@ export const createRoot = (
1205
1237
  )
1206
1238
  }
1207
1239
 
1208
- Attributes = mapObject(_component.attributes, ([name, { testValue }]) => [
1209
- name,
1210
- testValue,
1211
- ])
1240
+ Attributes = mapObject(
1241
+ _component.attributes ?? {},
1242
+ ([name, { testValue }]) => [name, testValue],
1243
+ )
1212
1244
  }
1213
1245
  if (
1214
1246
  fastDeepEqual(
@@ -1253,7 +1285,7 @@ export const createRoot = (
1253
1285
  const formulaContext: FormulaContext = {
1254
1286
  data: {
1255
1287
  Attributes: mapObject(
1256
- providerComponent.attributes,
1288
+ providerComponent.attributes ?? {},
1257
1289
  ([name, attr]) => [name, attr.testValue],
1258
1290
  ),
1259
1291
  // Recursively resolve contexts providers before their children to build up the fake context tree in preview mode
@@ -1287,7 +1319,7 @@ export const createRoot = (
1287
1319
  }
1288
1320
  }
1289
1321
  formulaContext.data.Variables = mapObject(
1290
- providerComponent.variables,
1322
+ providerComponent.variables ?? {},
1291
1323
  ([name, variable]) => [
1292
1324
  name,
1293
1325
  applyFormula(variable.initialValue, formulaContext),
@@ -1321,7 +1353,7 @@ export const createRoot = (
1321
1353
  fastDeepEqual(_component.variables, ctx?.component.variables) === false
1322
1354
  ) {
1323
1355
  Variables = mapObject(
1324
- _component.variables,
1356
+ _component.variables ?? {},
1325
1357
  ([name, { initialValue }]) => [
1326
1358
  name,
1327
1359
  applyFormula(initialValue, {
@@ -1359,7 +1391,7 @@ export const createRoot = (
1359
1391
  for (const api in newCtx.component.apis) {
1360
1392
  // check if the api has changed (ignoring onCompleted and onFailed).
1361
1393
  const apiInstance = newCtx.component.apis[api]
1362
- const previousApiInstance = ctx?.component.apis[api]
1394
+ const previousApiInstance = ctx?.component.apis?.[api]
1363
1395
  if (isLegacyApi(apiInstance)) {
1364
1396
  if (
1365
1397
  fastDeepEqual(
@@ -1376,7 +1408,7 @@ export const createRoot = (
1376
1408
  Apis: omitKeys(data.Apis ?? {}, [
1377
1409
  ...Object.keys(data.Apis ?? {}).filter(
1378
1410
  // remove any data from an api that is not part of the component
1379
- (key) => !newCtx.component.apis[key],
1411
+ (key) => !newCtx.component.apis?.[key],
1380
1412
  ),
1381
1413
  api,
1382
1414
  ]),
@@ -1427,7 +1459,7 @@ export const createRoot = (
1427
1459
  parentElement: domNode,
1428
1460
  instance: { [newCtx.component.name]: 'root' },
1429
1461
  })
1430
- newCtx.component.onLoad?.actions.forEach((action) => {
1462
+ newCtx.component.onLoad?.actions?.forEach((action) => {
1431
1463
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1432
1464
  handleAction(action, dataSignal.get(), newCtx)
1433
1465
  })
@@ -1576,7 +1608,7 @@ export const createRoot = (
1576
1608
  const updateConditionalElements = () => {
1577
1609
  const displayedNodes: string[] = []
1578
1610
  if (selectedNodeId && component) {
1579
- const root = component.nodes.root
1611
+ const root = component.nodes?.root
1580
1612
  if (root) {
1581
1613
  const nodeLookup = getNodeAndAncestors(component, root, selectedNodeId)
1582
1614
  if (isNodeOrAncestorConditional(nodeLookup)) {
@@ -1692,7 +1724,7 @@ function getNodeId(component: Component, path: string[]) {
1692
1724
  if (nextChild === undefined || currentId === undefined) {
1693
1725
  return currentId ?? null
1694
1726
  }
1695
- const currentNode = component.nodes[currentId]
1727
+ const currentNode = component.nodes?.[currentId]
1696
1728
  if (!currentNode?.children) {
1697
1729
  return null
1698
1730
  }
package/src/page.main.ts CHANGED
@@ -162,7 +162,7 @@ export const createRoot = (domNode: HTMLElement) => {
162
162
  ...window.toddle.pageState,
163
163
  // Re-initialize variables since some of them might rely on client-side
164
164
  // state (e.g. localStorage, sensors etc.)
165
- Variables: mapObject(component.variables, ([name, variable]) => [
165
+ Variables: mapObject(component.variables ?? {}, ([name, variable]) => [
166
166
  name,
167
167
  applyFormula(variable.initialValue, {
168
168
  data: window.toddle.pageState,
@@ -213,17 +213,19 @@ export const createRoot = (domNode: HTMLElement) => {
213
213
  }
214
214
 
215
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
- })
216
+ sortApiObjects(Object.entries(component.apis ?? {})).forEach(
217
+ ([name, api]) => {
218
+ if (isLegacyApi(api)) {
219
+ ctx.apis[name] = createLegacyAPI(api, ctx)
220
+ } else {
221
+ ctx.apis[name] = createAPI({
222
+ apiRequest: api,
223
+ ctx,
224
+ componentData: dataSignal.get(),
225
+ })
226
+ }
227
+ },
228
+ )
227
229
  // Trigger actions for all APIs after all of them are created.
228
230
  Object.values(ctx.apis)
229
231
  .filter(isContextApiV2)
@@ -1,4 +1,5 @@
1
1
  import type { MediaQuery } from '@nordcraft/core/dist/component/component.types'
2
+ import type { Nullable } from '@nordcraft/core/dist/types'
2
3
 
3
4
  /**
4
5
  * CustomPropertyStyleSheet is a utility class that manages CSS custom properties
@@ -15,7 +16,10 @@ export class CustomPropertyStyleSheet {
15
16
  // Selector to rule index mapping
16
17
  private ruleMap: Map<string, CSSStyleRule | CSSNestedDeclarations> | undefined
17
18
 
18
- constructor(root: Document | ShadowRoot, styleSheet?: CSSStyleSheet | null) {
19
+ constructor(
20
+ root: Document | ShadowRoot,
21
+ styleSheet?: Nullable<CSSStyleSheet>,
22
+ ) {
19
23
  if (styleSheet) {
20
24
  this.styleSheet = styleSheet
21
25
  } else {
@@ -30,10 +34,10 @@ export class CustomPropertyStyleSheet {
30
34
  public registerProperty(
31
35
  selector: string,
32
36
  name: string,
33
- options?: {
34
- mediaQuery?: MediaQuery
35
- startingStyle?: boolean
36
- },
37
+ options?: Nullable<{
38
+ mediaQuery?: Nullable<MediaQuery>
39
+ startingStyle?: Nullable<boolean>
40
+ }>,
37
41
  ): (newValue: string) => void {
38
42
  this.ruleMap ??= this.hydrateFromBase()
39
43
  const fullSelector = CustomPropertyStyleSheet.getFullSelector(
@@ -69,11 +73,11 @@ export class CustomPropertyStyleSheet {
69
73
  public unregisterProperty(
70
74
  selector: string,
71
75
  name: string,
72
- options?: {
73
- mediaQuery?: MediaQuery
74
- startingStyle?: boolean
75
- deepClean?: boolean
76
- },
76
+ options?: Nullable<{
77
+ mediaQuery?: Nullable<MediaQuery>
78
+ startingStyle?: Nullable<boolean>
79
+ deepClean?: Nullable<boolean>
80
+ }>,
77
81
  ): void {
78
82
  if (!this.ruleMap) {
79
83
  return
@@ -161,10 +165,10 @@ export class CustomPropertyStyleSheet {
161
165
 
162
166
  private static getFullSelector(
163
167
  selector: string,
164
- options?: {
165
- mediaQuery?: MediaQuery
166
- startingStyle?: boolean
167
- },
168
+ options?: Nullable<{
169
+ mediaQuery?: Nullable<MediaQuery>
170
+ startingStyle?: Nullable<boolean>
171
+ }>,
168
172
  ) {
169
173
  let result =
170
174
  selector + (options?.startingStyle ? ' { @starting-style { }}' : ' { }')
@@ -38,7 +38,7 @@ export const getNodeAndAncestors = (
38
38
  nodeId: path.slice(0, i + 1).join('.'),
39
39
  })
40
40
  }
41
- return component.nodes[node.children[childIndex]]
41
+ return component.nodes?.[node.children[childIndex]]
42
42
  default:
43
43
  return undefined
44
44
  }