@nordcraft/runtime 1.0.33 → 1.0.35

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 (40) hide show
  1. package/dist/components/createComponent.js +4 -0
  2. package/dist/components/createComponent.js.map +1 -1
  3. package/dist/components/createElement.js +9 -1
  4. package/dist/components/createElement.js.map +1 -1
  5. package/dist/components/createNode.js +3 -3
  6. package/dist/components/createNode.js.map +1 -1
  7. package/dist/custom-element.main.esm.js +17 -17
  8. package/dist/custom-element.main.esm.js.map +4 -4
  9. package/dist/debug/panicScreen.d.ts +6 -0
  10. package/dist/debug/panicScreen.js +25 -0
  11. package/dist/debug/panicScreen.js.map +1 -0
  12. package/dist/debug/sendEditorToast.d.ts +3 -0
  13. package/dist/debug/sendEditorToast.js +9 -0
  14. package/dist/debug/sendEditorToast.js.map +1 -0
  15. package/dist/editor/graphql.d.ts +12 -0
  16. package/dist/editor/graphql.js +150 -0
  17. package/dist/editor/graphql.js.map +1 -0
  18. package/dist/editor-preview.main.js +142 -71
  19. package/dist/editor-preview.main.js.map +1 -1
  20. package/dist/page.main.esm.js +3 -3
  21. package/dist/page.main.esm.js.map +4 -4
  22. package/dist/styles/CustomPropertyStyleSheet.d.ts +2 -1
  23. package/dist/styles/CustomPropertyStyleSheet.js +14 -5
  24. package/dist/styles/CustomPropertyStyleSheet.js.map +1 -1
  25. package/dist/styles/CustomPropertyStyleSheet.test.js +7 -7
  26. package/dist/styles/CustomPropertyStyleSheet.test.js.map +1 -1
  27. package/dist/utils/subscribeCustomProperty.d.ts +4 -1
  28. package/dist/utils/subscribeCustomProperty.js +11 -4
  29. package/dist/utils/subscribeCustomProperty.js.map +1 -1
  30. package/package.json +3 -3
  31. package/src/components/createComponent.ts +4 -0
  32. package/src/components/createElement.ts +10 -1
  33. package/src/components/createNode.ts +3 -3
  34. package/src/debug/panicScreen.ts +37 -0
  35. package/src/debug/sendEditorToast.ts +19 -0
  36. package/src/editor/graphql.ts +167 -0
  37. package/src/editor-preview.main.ts +319 -215
  38. package/src/styles/CustomPropertyStyleSheet.test.ts +7 -7
  39. package/src/styles/CustomPropertyStyleSheet.ts +22 -5
  40. package/src/utils/subscribeCustomProperty.ts +25 -12
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable no-case-declarations */
4
4
  /* eslint-disable no-fallthrough */
5
5
  import { isLegacyApi } from '@nordcraft/core/dist/api/api';
6
+ import {} from '@nordcraft/core/dist/api/apiTypes';
6
7
  import { isPageComponent } from '@nordcraft/core/dist/component/isPageComponent';
7
8
  import { applyFormula } from '@nordcraft/core/dist/formula/formula';
8
9
  import { valueFormula } from '@nordcraft/core/dist/formula/formulaUtils';
@@ -17,10 +18,13 @@ import { createLegacyAPI } from './api/createAPI';
17
18
  import { createAPI } from './api/createAPIv2';
18
19
  import { createNode } from './components/createNode';
19
20
  import { isContextProvider } from './context/isContextProvider';
21
+ import { createPanicScreen } from './debug/panicScreen';
22
+ import { sendEditorToast } from './debug/sendEditorToast';
20
23
  import { dragEnded } from './editor/drag-drop/dragEnded';
21
24
  import { dragMove } from './editor/drag-drop/dragMove';
22
25
  import { dragReorder } from './editor/drag-drop/dragReorder';
23
26
  import { dragStarted } from './editor/drag-drop/dragStarted';
27
+ import { introspectApiRequest } from './editor/graphql';
24
28
  import { handleAction } from './events/handleAction';
25
29
  import { signal } from './signal/signal';
26
30
  import { insertStyles, styleToCss } from './styles/style';
@@ -198,7 +202,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
198
202
  });
199
203
  return component;
200
204
  };
201
- window.addEventListener('message', (message) => {
205
+ window.addEventListener('message', async (message) => {
202
206
  if (!message.isTrusted) {
203
207
  console.error('UNTRUSTED MESSAGE');
204
208
  }
@@ -321,20 +325,20 @@ export const createRoot = (domNode = document.getElementById('App')) => {
321
325
  element.type === 'text' &&
322
326
  element.value.type === 'value') {
323
327
  const computedStyle = window.getComputedStyle(node);
324
- window.parent?.postMessage({
328
+ postMessageToEditor({
325
329
  type: 'textComputedStyle',
326
330
  computedStyle: Object.fromEntries(Object.values(TextNodeComputedStyles).map((style) => [
327
331
  style,
328
332
  computedStyle.getPropertyValue(style),
329
333
  ])),
330
- }, '*');
334
+ });
331
335
  }
332
336
  else if (node && node.getAttribute('data-node-type') !== 'text') {
333
337
  // Reset computed style on blur
334
- window.parent?.postMessage({
338
+ postMessageToEditor({
335
339
  type: 'textComputedStyle',
336
340
  computedStyle: {},
337
- }, '*');
341
+ });
338
342
  }
339
343
  }
340
344
  return;
@@ -382,7 +386,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
382
386
  }
383
387
  const { x, y, type } = message.data;
384
388
  const elementsAtPoint = document.elementsFromPoint(x, y);
385
- let element = elementsAtPoint.find((elem) => {
389
+ const element = elementsAtPoint.find((elem) => {
386
390
  const id = elem.getAttribute('data-id');
387
391
  if (typeof id !== 'string' ||
388
392
  component === null ||
@@ -397,21 +401,10 @@ export const createRoot = (domNode = document.getElementById('App')) => {
397
401
  if (elem.getAttribute('data-node-type') === 'text') {
398
402
  return (
399
403
  // Select text nodes if the meta key is pressed or the text node is double-clicked
400
- metaKey ||
401
- type === 'dblclick' ||
402
- // Select text nodes if the selected node is a text node. This is useful as the user is likely in a text editing mode
403
- getDOMNodeFromNodeId(selectedNodeId)?.getAttribute('data-node-type') === 'text');
404
+ metaKey || type === 'dblclick');
404
405
  }
405
406
  return true;
406
407
  });
407
- // Bubble selection to the topmost parent that has the exact same size as the element.
408
- // This is important for drag and drop as you are often left with childless parents after dragging.
409
- while (element?.parentElement &&
410
- element.getAttribute('data-node-id') !== 'root' &&
411
- fastDeepEqual(element.getBoundingClientRect().toJSON(), element.parentElement.getBoundingClientRect().toJSON()) &&
412
- element.getAttribute('data-node-type') !== 'text') {
413
- element = element.parentElement;
414
- }
415
408
  const id = element?.getAttribute('data-id') ?? null;
416
409
  if (type === 'click' && id !== selectedNodeId) {
417
410
  if (message.data.metaKey) {
@@ -421,36 +414,36 @@ export const createRoot = (domNode = document.getElementById('App')) => {
421
414
  if (root && id) {
422
415
  const nodeLookup = getNodeAndAncestors(component, root, id);
423
416
  if (nodeLookup?.node.type === 'text') {
424
- window.parent?.postMessage({
417
+ postMessageToEditor({
425
418
  type: 'selection',
426
419
  selectedNodeId: id,
427
- }, '*');
420
+ });
428
421
  }
429
422
  else {
430
423
  const firstTextChild = nodeLookup?.node.type === 'element'
431
424
  ? nodeLookup.node.children.find((c) => component?.nodes[c]?.type === 'text')
432
425
  : undefined;
433
426
  if (firstTextChild) {
434
- window.parent?.postMessage({
427
+ postMessageToEditor({
435
428
  type: 'selection',
436
429
  selectedNodeId: `${id}.0`,
437
- }, '*');
430
+ });
438
431
  }
439
432
  }
440
433
  }
441
434
  }
442
435
  else {
443
- window.parent?.postMessage({
436
+ postMessageToEditor({
444
437
  type: 'selection',
445
438
  selectedNodeId: id,
446
- }, '*');
439
+ });
447
440
  }
448
441
  }
449
442
  else if (type === 'mousemove' && id !== highlightedNodeId) {
450
- window.parent?.postMessage({
443
+ postMessageToEditor({
451
444
  type: 'highlight',
452
445
  highlightedNodeId: id,
453
- }, '*');
446
+ });
454
447
  }
455
448
  else if (type === 'dblclick' &&
456
449
  id &&
@@ -462,17 +455,17 @@ export const createRoot = (domNode = document.getElementById('App')) => {
462
455
  const nodeLookup = getNodeAndAncestors(component, root, id);
463
456
  if (nodeLookup?.node.type === 'component' &&
464
457
  nodeLookup.node.name) {
465
- window.parent?.postMessage({
458
+ postMessageToEditor({
466
459
  type: 'navigate',
467
460
  name: nodeLookup.node.name,
468
- }, '*');
461
+ });
469
462
  }
470
463
  // Double click on text node should select the text node for editing
471
464
  else if (nodeLookup?.node.type === 'text') {
472
- window.parent?.postMessage({
465
+ postMessageToEditor({
473
466
  type: 'selection',
474
467
  selectedNodeId: id,
475
- }, '*');
468
+ });
476
469
  }
477
470
  }
478
471
  }
@@ -484,16 +477,16 @@ export const createRoot = (domNode = document.getElementById('App')) => {
484
477
  // We request manually instead of automatic to avoid mutation observer spam.
485
478
  // Also, reporting automatically proved unreliable when elements' height was in %
486
479
  case 'report_document_scroll_size':
487
- window.parent?.postMessage({
480
+ postMessageToEditor({
488
481
  type: 'documentScrollSize',
489
482
  scrollHeight: domNode.scrollHeight,
490
483
  scrollWidth: domNode.scrollWidth,
491
- }, '*');
484
+ });
492
485
  break;
493
486
  case 'reload':
494
487
  window.location.reload();
495
488
  break;
496
- case 'fetch_api':
489
+ case 'fetch_api': {
497
490
  const { apiKey } = message.data;
498
491
  dataSignal.update((data) => ({
499
492
  ...data,
@@ -508,6 +501,33 @@ export const createRoot = (domNode = document.getElementById('App')) => {
508
501
  }));
509
502
  void ctx?.apis[apiKey]?.fetch({});
510
503
  break;
504
+ }
505
+ case 'introspect_qraphql_api': {
506
+ const { apiKey } = message.data;
507
+ const api = component?.apis[apiKey];
508
+ if (api && !isLegacyApi(api) && component) {
509
+ const Attributes = mapObject(component.attributes, ([name, { testValue }]) => [name, testValue]);
510
+ const formulaContext = {
511
+ component,
512
+ data: { Attributes },
513
+ root: document,
514
+ package: ctx?.package,
515
+ toddle: window.toddle,
516
+ env,
517
+ };
518
+ const introspectionResult = await introspectApiRequest({
519
+ api: api,
520
+ componentName: component.name,
521
+ formulaContext,
522
+ });
523
+ postMessageToEditor({
524
+ type: 'introspectionResult',
525
+ data: introspectionResult,
526
+ apiKey,
527
+ });
528
+ }
529
+ break;
530
+ }
511
531
  case 'drag-started':
512
532
  const draggedElement = getDOMNodeFromNodeId(selectedNodeId);
513
533
  if (!draggedElement || !draggedElement.parentElement) {
@@ -543,14 +563,14 @@ export const createRoot = (domNode = document.getElementById('App')) => {
543
563
  (nextSibling !== dragState?.initialNextSibling ||
544
564
  dragState?.copy)) {
545
565
  void dragEnded(dragState, false).then(() => {
546
- window.parent?.postMessage({
566
+ postMessageToEditor({
547
567
  type: 'nodeMoved',
548
568
  copy: Boolean(dragState?.copy),
549
569
  parent: parentDataId,
550
570
  index: !isNaN(nextSiblingId)
551
571
  ? nextSiblingId
552
572
  : component?.nodes[parentNodeId]?.children?.length,
553
- }, '*');
573
+ });
554
574
  dragState = null;
555
575
  });
556
576
  }
@@ -564,12 +584,12 @@ export const createRoot = (domNode = document.getElementById('App')) => {
564
584
  const selectedPermutation = dragState?.insertAreas?.[dragState?.selectedInsertAreaIndex ?? -1];
565
585
  if (selectedPermutation && !message.data.canceled) {
566
586
  void dragEnded(dragState, false).then(() => {
567
- window.parent?.postMessage({
587
+ postMessageToEditor({
568
588
  type: 'nodeMoved',
569
589
  copy: Boolean(dragState?.copy),
570
590
  parent: selectedPermutation?.parent.getAttribute('data-id'),
571
591
  index: selectedPermutation?.index,
572
- }, '*');
592
+ });
573
593
  dragState = null;
574
594
  });
575
595
  }
@@ -615,13 +635,13 @@ export const createRoot = (domNode = document.getElementById('App')) => {
615
635
  }
616
636
  const { styles } = message.data;
617
637
  const computedStyle = window.getComputedStyle(selectedNode);
618
- window.parent?.postMessage({
638
+ postMessageToEditor({
619
639
  type: 'computedStyle',
620
640
  computedStyle: Object.fromEntries((styles ?? []).map((style) => [
621
641
  style,
622
642
  computedStyle.getPropertyValue(style),
623
643
  ])),
624
- }, '*');
644
+ });
625
645
  break;
626
646
  case 'set_timeline_keyframes':
627
647
  const { keyframes } = message.data;
@@ -705,10 +725,20 @@ export const createRoot = (domNode = document.getElementById('App')) => {
705
725
  styleTag.setAttribute('data-id', 'selected-node-styles');
706
726
  document.head.appendChild(styleTag);
707
727
  }
728
+ // If style variant targets a pseudo-element, apply styles to it instead
729
+ let pseudoElement = '';
730
+ if (component && styleVariantSelection) {
731
+ const nodeLookup = getNodeAndAncestors(component, component.nodes.root, styleVariantSelection.nodeId);
732
+ if (nodeLookup?.node.type === 'element' ||
733
+ (nodeLookup?.node.type === 'component' &&
734
+ nodeLookup.node.variants?.[styleVariantSelection.styleVariantIndex].pseudoElement)) {
735
+ pseudoElement = `::${nodeLookup.node.variants?.[styleVariantSelection.styleVariantIndex].pseudoElement}`;
736
+ }
737
+ }
708
738
  const previewStyles = Object.entries(previewStyleStyles)
709
739
  .map(([key, value]) => `${key}: ${value} !important;`)
710
740
  .join('\n');
711
- styleTag.innerHTML = `[data-id="${selectedNodeId}"], [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("] {
741
+ styleTag.innerHTML = `[data-id="${selectedNodeId}"]${pseudoElement}, [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("]${pseudoElement} {
712
742
  ${previewStyles}
713
743
  transition: none !important;
714
744
  }`;
@@ -785,11 +815,14 @@ export const createRoot = (domNode = document.getElementById('App')) => {
785
815
  }))
786
816
  .filter(({ value }) => value !== undefined);
787
817
  const styleElem = document.createElement('style');
818
+ const pseudoElement = selectedStyleVariant.pseudoElement
819
+ ? `::${selectedStyleVariant.pseudoElement}`
820
+ : '';
788
821
  styleElem.setAttribute('data-hash', selectedNodeId);
789
822
  styleElem.appendChild(document.createTextNode(`
790
- body[data-mode="design"] [data-id="${selectedNodeId}"] {
823
+ body[data-mode="design"] [data-id="${selectedNodeId}"]${pseudoElement} {
791
824
  ${styleToCss({
792
- ...nodeLookup.node.style,
825
+ ...(!pseudoElement && nodeLookup.node.style),
793
826
  ...selectedStyleVariant.style,
794
827
  ...Object.fromEntries(styleVariantCustomProperties.map(({ name, value }) => [name, value])),
795
828
  })}
@@ -1021,25 +1054,57 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1021
1054
  // Clear old root signal and create a new one to not keep old signals with previous root around
1022
1055
  ctxDataSignal?.destroy();
1023
1056
  ctxDataSignal = dataSignal.map((data) => data);
1024
- const rootElem = createNode({
1025
- id: 'root',
1026
- path: '0',
1027
- dataSignal: ctxDataSignal,
1028
- ctx: newCtx,
1029
- parentElement: domNode,
1030
- instance: { [newCtx.component.name]: 'root' },
1031
- });
1032
- newCtx.component.onLoad?.actions.forEach((action) => {
1033
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1034
- handleAction(action, dataSignal.get(), newCtx);
1035
- });
1036
- rootElem.forEach((elem) => domNode.appendChild(elem));
1037
- window.parent?.postMessage({
1057
+ try {
1058
+ const rootElem = createNode({
1059
+ id: 'root',
1060
+ path: '0',
1061
+ dataSignal: ctxDataSignal,
1062
+ ctx: newCtx,
1063
+ parentElement: domNode,
1064
+ instance: { [newCtx.component.name]: 'root' },
1065
+ });
1066
+ newCtx.component.onLoad?.actions.forEach((action) => {
1067
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1068
+ handleAction(action, dataSignal.get(), newCtx);
1069
+ });
1070
+ rootElem.forEach((elem) => domNode.appendChild(elem));
1071
+ }
1072
+ catch (error) {
1073
+ const isPage = isPageComponent(newCtx.component);
1074
+ let name = `Unexpected error while rendering ${isPage ? 'page' : 'component'}`;
1075
+ let message = error instanceof Error ? error.message : String(error);
1076
+ let panic = false;
1077
+ if (error instanceof RangeError) {
1078
+ // RangeError is unrecoverable
1079
+ panic = true;
1080
+ name = 'Infinite loop detected';
1081
+ message =
1082
+ 'RangeError (Maximum call stack size exceeded): Remove any circular dependencies or recursive calls. This is most likely caused by components, formulas or actions using themselves without an exit case.';
1083
+ }
1084
+ // Send a toast to the editor with the error
1085
+ sendEditorToast(name, message, {
1086
+ type: 'critical',
1087
+ });
1088
+ if (panic) {
1089
+ // Show error overlay in the editor until next update
1090
+ const panicScreen = createPanicScreen({
1091
+ name: name,
1092
+ message,
1093
+ isPage,
1094
+ cause: error,
1095
+ });
1096
+ // Replace the inner HTML of the editor preview with the panic screen
1097
+ domNode.innerHTML = '';
1098
+ domNode.appendChild(panicScreen);
1099
+ }
1100
+ console.error(name, message, error);
1101
+ }
1102
+ postMessageToEditor({
1038
1103
  type: 'style',
1039
1104
  time: new Intl.DateTimeFormat('en-GB', {
1040
1105
  timeStyle: 'long',
1041
1106
  }).format(new Date()),
1042
- }, '*');
1107
+ });
1043
1108
  }
1044
1109
  }
1045
1110
  ctx = newCtx;
@@ -1049,14 +1114,14 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1049
1114
  component,
1050
1115
  components,
1051
1116
  triggerEvent: (event, data) => {
1052
- window.parent?.postMessage({
1117
+ postMessageToEditor({
1053
1118
  type: 'component event',
1054
1119
  event,
1055
1120
  time: new Intl.DateTimeFormat('en-GB', {
1056
1121
  timeStyle: 'long',
1057
1122
  }).format(new Date()),
1058
1123
  data,
1059
- }, '*');
1124
+ });
1060
1125
  },
1061
1126
  dataSignal,
1062
1127
  root: document,
@@ -1107,7 +1172,7 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1107
1172
  event.preventDefault();
1108
1173
  }
1109
1174
  }
1110
- window.parent?.postMessage({
1175
+ postMessageToEditor({
1111
1176
  type: 'keydown',
1112
1177
  event: {
1113
1178
  key: event.key,
@@ -1115,13 +1180,13 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1115
1180
  shiftKey: event.shiftKey,
1116
1181
  altKey: event.altKey,
1117
1182
  },
1118
- }, '*');
1183
+ });
1119
1184
  });
1120
1185
  document.addEventListener('keyup', (event) => {
1121
1186
  if (isInputTarget(event)) {
1122
1187
  return;
1123
1188
  }
1124
- window.parent?.postMessage({
1189
+ postMessageToEditor({
1125
1190
  type: 'keyup',
1126
1191
  event: {
1127
1192
  key: event.key,
@@ -1129,13 +1194,13 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1129
1194
  shiftKey: event.shiftKey,
1130
1195
  altKey: event.altKey,
1131
1196
  },
1132
- }, '*');
1197
+ });
1133
1198
  });
1134
1199
  document.addEventListener('keypress', (event) => {
1135
1200
  if (isInputTarget(event)) {
1136
1201
  return;
1137
1202
  }
1138
- window.parent?.postMessage({
1203
+ postMessageToEditor({
1139
1204
  type: 'keypress',
1140
1205
  event: {
1141
1206
  key: event.key,
@@ -1143,16 +1208,19 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1143
1208
  shiftKey: event.shiftKey,
1144
1209
  altKey: event.altKey,
1145
1210
  },
1146
- }, '*');
1211
+ });
1147
1212
  });
1148
1213
  dataSignal.subscribe((data) => {
1149
1214
  if (component && components && packageComponents && data) {
1150
1215
  try {
1151
- window.parent?.postMessage({ type: 'data', data }, '*');
1216
+ postMessageToEditor({ type: 'data', data });
1152
1217
  }
1153
1218
  catch {
1154
1219
  // If we're unable to send the data, let's try to JSON serialize it
1155
- window.parent?.postMessage({ type: 'data', data: JSON.parse(JSON.stringify(data)) }, '*');
1220
+ postMessageToEditor({
1221
+ type: 'data',
1222
+ data: JSON.parse(JSON.stringify(data)),
1223
+ });
1156
1224
  }
1157
1225
  }
1158
1226
  });
@@ -1187,17 +1255,17 @@ export const createRoot = (domNode = document.getElementById('App')) => {
1187
1255
  (function syncOverlayRects(prevSelectionRect, prevHighlightedRect) {
1188
1256
  const selectionRect = getRectData(getDOMNodeFromNodeId(selectedNodeId));
1189
1257
  if (!fastDeepEqual(prevSelectionRect, selectionRect)) {
1190
- window.parent?.postMessage({
1258
+ postMessageToEditor({
1191
1259
  type: 'selectionRect',
1192
1260
  rect: selectionRect,
1193
- }, '*');
1261
+ });
1194
1262
  }
1195
1263
  const highlightRect = getRectData(getDOMNodeFromNodeId(highlightedNodeId));
1196
1264
  if (!fastDeepEqual(prevHighlightedRect, highlightRect)) {
1197
- window.parent?.postMessage({
1265
+ postMessageToEditor({
1198
1266
  type: 'highlightRect',
1199
1267
  rect: highlightRect,
1200
- }, '*');
1268
+ });
1201
1269
  }
1202
1270
  requestAnimationFrame(() => syncOverlayRects(selectionRect, highlightRect));
1203
1271
  })();
@@ -1294,4 +1362,7 @@ const insertTheme = (parent, theme) => {
1294
1362
  });
1295
1363
  parent.appendChild(styleElem);
1296
1364
  };
1365
+ const postMessageToEditor = (message) => {
1366
+ window.parent?.postMessage(message, '*');
1367
+ };
1297
1368
  //# sourceMappingURL=editor-preview.main.js.map