@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 { type ApiRequest } from '@nordcraft/core/dist/api/apiTypes'
6
7
  import type {
7
8
  AnimationKeyframe,
8
9
  Component,
@@ -38,10 +39,13 @@ import { createLegacyAPI } from './api/createAPI'
38
39
  import { createAPI } from './api/createAPIv2'
39
40
  import { createNode } from './components/createNode'
40
41
  import { isContextProvider } from './context/isContextProvider'
42
+ import { createPanicScreen } from './debug/panicScreen'
43
+ import { sendEditorToast } from './debug/sendEditorToast'
41
44
  import { dragEnded } from './editor/drag-drop/dragEnded'
42
45
  import { dragMove } from './editor/drag-drop/dragMove'
43
46
  import { dragReorder } from './editor/drag-drop/dragReorder'
44
47
  import { dragStarted } from './editor/drag-drop/dragStarted'
48
+ import { introspectApiRequest } from './editor/graphql'
45
49
  import type { DragState } from './editor/types'
46
50
  import { handleAction } from './events/handleAction'
47
51
  import type { Signal } from './signal/signal'
@@ -97,6 +101,7 @@ type ToddlePreviewEvent =
97
101
  | { type: 'update_inner_text'; innerText: string }
98
102
  | { type: 'reload' }
99
103
  | { type: 'fetch_api'; apiKey: string }
104
+ | { type: 'introspect_qraphql_api'; apiKey: string }
100
105
  | { type: 'drag-started'; x: number; y: number }
101
106
  | { type: 'drag-ended'; canceled?: true }
102
107
  | { type: 'keydown'; key: string; altKey: boolean; metaKey: boolean }
@@ -336,7 +341,7 @@ export const createRoot = (
336
341
 
337
342
  window.addEventListener(
338
343
  'message',
339
- (message: MessageEvent<ToddlePreviewEvent>) => {
344
+ async (message: MessageEvent<ToddlePreviewEvent>) => {
340
345
  if (!message.isTrusted) {
341
346
  console.error('UNTRUSTED MESSAGE')
342
347
  }
@@ -487,27 +492,21 @@ export const createRoot = (
487
492
  element.value.type === 'value'
488
493
  ) {
489
494
  const computedStyle = window.getComputedStyle(node)
490
- window.parent?.postMessage(
491
- {
492
- type: 'textComputedStyle',
493
- computedStyle: Object.fromEntries(
494
- Object.values(TextNodeComputedStyles).map((style) => [
495
- style,
496
- computedStyle.getPropertyValue(style),
497
- ]),
498
- ),
499
- },
500
- '*',
501
- )
495
+ postMessageToEditor({
496
+ type: 'textComputedStyle',
497
+ computedStyle: Object.fromEntries(
498
+ Object.values(TextNodeComputedStyles).map((style) => [
499
+ style,
500
+ computedStyle.getPropertyValue(style),
501
+ ]),
502
+ ),
503
+ })
502
504
  } else if (node && node.getAttribute('data-node-type') !== 'text') {
503
505
  // Reset computed style on blur
504
- window.parent?.postMessage(
505
- {
506
- type: 'textComputedStyle',
507
- computedStyle: {},
508
- },
509
- '*',
510
- )
506
+ postMessageToEditor({
507
+ type: 'textComputedStyle',
508
+ computedStyle: {},
509
+ })
511
510
  }
512
511
  }
513
512
  return
@@ -566,7 +565,7 @@ export const createRoot = (
566
565
  }
567
566
  const { x, y, type } = message.data
568
567
  const elementsAtPoint = document.elementsFromPoint(x, y)
569
- let element = elementsAtPoint.find((elem) => {
568
+ const element = elementsAtPoint.find((elem) => {
570
569
  const id = elem.getAttribute('data-id')
571
570
  if (
572
571
  typeof id !== 'string' ||
@@ -583,31 +582,12 @@ export const createRoot = (
583
582
  if (elem.getAttribute('data-node-type') === 'text') {
584
583
  return (
585
584
  // Select text nodes if the meta key is pressed or the text node is double-clicked
586
- metaKey ||
587
- type === 'dblclick' ||
588
- // Select text nodes if the selected node is a text node. This is useful as the user is likely in a text editing mode
589
- getDOMNodeFromNodeId(selectedNodeId)?.getAttribute(
590
- 'data-node-type',
591
- ) === 'text'
585
+ metaKey || type === 'dblclick'
592
586
  )
593
587
  }
594
588
  return true
595
589
  })
596
590
 
597
- // Bubble selection to the topmost parent that has the exact same size as the element.
598
- // This is important for drag and drop as you are often left with childless parents after dragging.
599
- while (
600
- element?.parentElement &&
601
- element.getAttribute('data-node-id') !== 'root' &&
602
- fastDeepEqual(
603
- element.getBoundingClientRect().toJSON(),
604
- element.parentElement.getBoundingClientRect().toJSON(),
605
- ) &&
606
- element.getAttribute('data-node-type') !== 'text'
607
- ) {
608
- element = element.parentElement
609
- }
610
-
611
591
  const id = element?.getAttribute('data-id') ?? null
612
592
  if (type === 'click' && id !== selectedNodeId) {
613
593
  if (message.data.metaKey) {
@@ -617,13 +597,10 @@ export const createRoot = (
617
597
  if (root && id) {
618
598
  const nodeLookup = getNodeAndAncestors(component, root, id)
619
599
  if (nodeLookup?.node.type === 'text') {
620
- window.parent?.postMessage(
621
- {
622
- type: 'selection',
623
- selectedNodeId: id,
624
- },
625
- '*',
626
- )
600
+ postMessageToEditor({
601
+ type: 'selection',
602
+ selectedNodeId: id,
603
+ })
627
604
  } else {
628
605
  const firstTextChild =
629
606
  nodeLookup?.node.type === 'element'
@@ -632,33 +609,24 @@ export const createRoot = (
632
609
  )
633
610
  : undefined
634
611
  if (firstTextChild) {
635
- window.parent?.postMessage(
636
- {
637
- type: 'selection',
638
- selectedNodeId: `${id}.0`,
639
- },
640
- '*',
641
- )
612
+ postMessageToEditor({
613
+ type: 'selection',
614
+ selectedNodeId: `${id}.0`,
615
+ })
642
616
  }
643
617
  }
644
618
  }
645
619
  } else {
646
- window.parent?.postMessage(
647
- {
648
- type: 'selection',
649
- selectedNodeId: id,
650
- },
651
- '*',
652
- )
620
+ postMessageToEditor({
621
+ type: 'selection',
622
+ selectedNodeId: id,
623
+ })
653
624
  }
654
625
  } else if (type === 'mousemove' && id !== highlightedNodeId) {
655
- window.parent?.postMessage(
656
- {
657
- type: 'highlight',
658
- highlightedNodeId: id,
659
- },
660
- '*',
661
- )
626
+ postMessageToEditor({
627
+ type: 'highlight',
628
+ highlightedNodeId: id,
629
+ })
662
630
  } else if (
663
631
  type === 'dblclick' &&
664
632
  id &&
@@ -673,23 +641,17 @@ export const createRoot = (
673
641
  nodeLookup?.node.type === 'component' &&
674
642
  nodeLookup.node.name
675
643
  ) {
676
- window.parent?.postMessage(
677
- {
678
- type: 'navigate',
679
- name: nodeLookup.node.name,
680
- },
681
- '*',
682
- )
644
+ postMessageToEditor({
645
+ type: 'navigate',
646
+ name: nodeLookup.node.name,
647
+ })
683
648
  }
684
649
  // Double click on text node should select the text node for editing
685
650
  else if (nodeLookup?.node.type === 'text') {
686
- window.parent?.postMessage(
687
- {
688
- type: 'selection',
689
- selectedNodeId: id,
690
- },
691
- '*',
692
- )
651
+ postMessageToEditor({
652
+ type: 'selection',
653
+ selectedNodeId: id,
654
+ })
693
655
  }
694
656
  }
695
657
  }
@@ -701,19 +663,16 @@ export const createRoot = (
701
663
  // We request manually instead of automatic to avoid mutation observer spam.
702
664
  // Also, reporting automatically proved unreliable when elements' height was in %
703
665
  case 'report_document_scroll_size':
704
- window.parent?.postMessage(
705
- {
706
- type: 'documentScrollSize',
707
- scrollHeight: domNode.scrollHeight,
708
- scrollWidth: domNode.scrollWidth,
709
- },
710
- '*',
711
- )
666
+ postMessageToEditor({
667
+ type: 'documentScrollSize',
668
+ scrollHeight: domNode.scrollHeight,
669
+ scrollWidth: domNode.scrollWidth,
670
+ })
712
671
  break
713
672
  case 'reload':
714
673
  window.location.reload()
715
674
  break
716
- case 'fetch_api':
675
+ case 'fetch_api': {
717
676
  const { apiKey } = message.data
718
677
  dataSignal.update((data) => ({
719
678
  ...data,
@@ -728,6 +687,36 @@ export const createRoot = (
728
687
  }))
729
688
  void ctx?.apis[apiKey]?.fetch({} as any)
730
689
  break
690
+ }
691
+ case 'introspect_qraphql_api': {
692
+ const { apiKey } = message.data
693
+ const api = component?.apis[apiKey]
694
+ if (api && !isLegacyApi(api) && component) {
695
+ const Attributes = mapObject(
696
+ component.attributes,
697
+ ([name, { testValue }]) => [name, testValue],
698
+ )
699
+ const formulaContext: FormulaContext = {
700
+ component,
701
+ data: { Attributes },
702
+ root: document,
703
+ package: ctx?.package,
704
+ toddle: window.toddle,
705
+ env,
706
+ }
707
+ const introspectionResult = await introspectApiRequest({
708
+ api: api as ApiRequest,
709
+ componentName: component.name,
710
+ formulaContext,
711
+ })
712
+ postMessageToEditor({
713
+ type: 'introspectionResult',
714
+ data: introspectionResult,
715
+ apiKey,
716
+ })
717
+ }
718
+ break
719
+ }
731
720
  case 'drag-started':
732
721
  const draggedElement = getDOMNodeFromNodeId(selectedNodeId)
733
722
  if (!draggedElement || !draggedElement.parentElement) {
@@ -777,17 +766,14 @@ export const createRoot = (
777
766
  dragState?.copy)
778
767
  ) {
779
768
  void dragEnded(dragState, false).then(() => {
780
- window.parent?.postMessage(
781
- {
782
- type: 'nodeMoved',
783
- copy: Boolean(dragState?.copy),
784
- parent: parentDataId,
785
- index: !isNaN(nextSiblingId)
786
- ? nextSiblingId
787
- : component?.nodes[parentNodeId]?.children?.length,
788
- },
789
- '*',
790
- )
769
+ postMessageToEditor({
770
+ type: 'nodeMoved',
771
+ copy: Boolean(dragState?.copy),
772
+ parent: parentDataId,
773
+ index: !isNaN(nextSiblingId)
774
+ ? nextSiblingId
775
+ : component?.nodes[parentNodeId]?.children?.length,
776
+ })
791
777
  dragState = null
792
778
  })
793
779
  } else {
@@ -803,16 +789,12 @@ export const createRoot = (
803
789
  ]
804
790
  if (selectedPermutation && !message.data.canceled) {
805
791
  void dragEnded(dragState, false).then(() => {
806
- window.parent?.postMessage(
807
- {
808
- type: 'nodeMoved',
809
- copy: Boolean(dragState?.copy),
810
- parent:
811
- selectedPermutation?.parent.getAttribute('data-id'),
812
- index: selectedPermutation?.index,
813
- },
814
- '*',
815
- )
792
+ postMessageToEditor({
793
+ type: 'nodeMoved',
794
+ copy: Boolean(dragState?.copy),
795
+ parent: selectedPermutation?.parent.getAttribute('data-id'),
796
+ index: selectedPermutation?.index,
797
+ })
816
798
  dragState = null
817
799
  })
818
800
  } else {
@@ -860,18 +842,15 @@ export const createRoot = (
860
842
 
861
843
  const { styles } = message.data
862
844
  const computedStyle = window.getComputedStyle(selectedNode)
863
- window.parent?.postMessage(
864
- {
865
- type: 'computedStyle',
866
- computedStyle: Object.fromEntries(
867
- (styles ?? []).map((style) => [
868
- style,
869
- computedStyle.getPropertyValue(style),
870
- ]),
871
- ),
872
- },
873
- '*',
874
- )
845
+ postMessageToEditor({
846
+ type: 'computedStyle',
847
+ computedStyle: Object.fromEntries(
848
+ (styles ?? []).map((style) => [
849
+ style,
850
+ computedStyle.getPropertyValue(style),
851
+ ]),
852
+ ),
853
+ })
875
854
  break
876
855
 
877
856
  case 'set_timeline_keyframes':
@@ -986,10 +965,30 @@ export const createRoot = (
986
965
  document.head.appendChild(styleTag)
987
966
  }
988
967
 
968
+ // If style variant targets a pseudo-element, apply styles to it instead
969
+ let pseudoElement = ''
970
+ if (component && styleVariantSelection) {
971
+ const nodeLookup = getNodeAndAncestors(
972
+ component,
973
+ component.nodes.root,
974
+ styleVariantSelection.nodeId,
975
+ )
976
+
977
+ if (
978
+ nodeLookup?.node.type === 'element' ||
979
+ (nodeLookup?.node.type === 'component' &&
980
+ nodeLookup.node.variants?.[
981
+ styleVariantSelection.styleVariantIndex
982
+ ].pseudoElement)
983
+ ) {
984
+ pseudoElement = `::${nodeLookup.node.variants?.[styleVariantSelection.styleVariantIndex].pseudoElement}`
985
+ }
986
+ }
987
+
989
988
  const previewStyles = Object.entries(previewStyleStyles)
990
989
  .map(([key, value]) => `${key}: ${value} !important;`)
991
990
  .join('\n')
992
- styleTag.innerHTML = `[data-id="${selectedNodeId}"], [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("] {
991
+ styleTag.innerHTML = `[data-id="${selectedNodeId}"]${pseudoElement}, [data-id="${selectedNodeId}"] ~ [data-id^="${selectedNodeId}("]${pseudoElement} {
993
992
  ${previewStyles}
994
993
  transition: none !important;
995
994
  }`
@@ -1054,9 +1053,10 @@ export const createRoot = (
1054
1053
  (nodeLookup.node.type === 'element' ||
1055
1054
  nodeLookup.node.type === 'component')
1056
1055
  ) {
1057
- const selectedStyleVariant = nodeLookup.node.variants?.[
1058
- styleVariantSelection.styleVariantIndex
1059
- ] ?? { style: {} }
1056
+ const selectedStyleVariant =
1057
+ nodeLookup.node.variants?.[
1058
+ styleVariantSelection.styleVariantIndex
1059
+ ] ?? ({ style: {} } as StyleVariant)
1060
1060
  // Add a style element specific to the selected element which
1061
1061
  // is only applied when the preview is in design mode
1062
1062
  const styleVariantCustomProperties = Object.entries(
@@ -1081,12 +1081,15 @@ export const createRoot = (
1081
1081
  .filter(({ value }) => value !== undefined)
1082
1082
 
1083
1083
  const styleElem = document.createElement('style')
1084
+ const pseudoElement = selectedStyleVariant.pseudoElement
1085
+ ? `::${selectedStyleVariant.pseudoElement}`
1086
+ : ''
1084
1087
  styleElem.setAttribute('data-hash', selectedNodeId)
1085
1088
  styleElem.appendChild(
1086
1089
  document.createTextNode(`
1087
- body[data-mode="design"] [data-id="${selectedNodeId}"] {
1090
+ body[data-mode="design"] [data-id="${selectedNodeId}"]${pseudoElement} {
1088
1091
  ${styleToCss({
1089
- ...nodeLookup.node.style,
1092
+ ...(!pseudoElement && nodeLookup.node.style),
1090
1093
  ...selectedStyleVariant.style,
1091
1094
  ...Object.fromEntries(
1092
1095
  styleVariantCustomProperties.map(
@@ -1406,28 +1409,59 @@ export const createRoot = (
1406
1409
  // Clear old root signal and create a new one to not keep old signals with previous root around
1407
1410
  ctxDataSignal?.destroy()
1408
1411
  ctxDataSignal = dataSignal.map((data) => data)
1409
- const rootElem = createNode({
1410
- id: 'root',
1411
- path: '0',
1412
- dataSignal: ctxDataSignal,
1413
- ctx: newCtx,
1414
- parentElement: domNode,
1415
- instance: { [newCtx.component.name]: 'root' },
1416
- })
1417
- newCtx.component.onLoad?.actions.forEach((action) => {
1418
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
1419
- handleAction(action, dataSignal.get(), newCtx)
1412
+ try {
1413
+ const rootElem = createNode({
1414
+ id: 'root',
1415
+ path: '0',
1416
+ dataSignal: ctxDataSignal,
1417
+ ctx: newCtx,
1418
+ parentElement: domNode,
1419
+ instance: { [newCtx.component.name]: 'root' },
1420
+ })
1421
+ newCtx.component.onLoad?.actions.forEach((action) => {
1422
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1423
+ handleAction(action, dataSignal.get(), newCtx)
1424
+ })
1425
+ rootElem.forEach((elem) => domNode.appendChild(elem))
1426
+ } catch (error: unknown) {
1427
+ const isPage = isPageComponent(newCtx.component)
1428
+ let name = `Unexpected error while rendering ${isPage ? 'page' : 'component'}`
1429
+ let message = error instanceof Error ? error.message : String(error)
1430
+ let panic = false
1431
+ if (error instanceof RangeError) {
1432
+ // RangeError is unrecoverable
1433
+ panic = true
1434
+ name = 'Infinite loop detected'
1435
+ message =
1436
+ '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.'
1437
+ }
1438
+
1439
+ // Send a toast to the editor with the error
1440
+ sendEditorToast(name, message, {
1441
+ type: 'critical',
1442
+ })
1443
+
1444
+ if (panic) {
1445
+ // Show error overlay in the editor until next update
1446
+ const panicScreen = createPanicScreen({
1447
+ name: name,
1448
+ message,
1449
+ isPage,
1450
+ cause: error,
1451
+ })
1452
+
1453
+ // Replace the inner HTML of the editor preview with the panic screen
1454
+ domNode.innerHTML = ''
1455
+ domNode.appendChild(panicScreen)
1456
+ }
1457
+ console.error(name, message, error)
1458
+ }
1459
+ postMessageToEditor({
1460
+ type: 'style',
1461
+ time: new Intl.DateTimeFormat('en-GB', {
1462
+ timeStyle: 'long',
1463
+ }).format(new Date()),
1420
1464
  })
1421
- rootElem.forEach((elem) => domNode.appendChild(elem))
1422
- window.parent?.postMessage(
1423
- {
1424
- type: 'style',
1425
- time: new Intl.DateTimeFormat('en-GB', {
1426
- timeStyle: 'long',
1427
- }).format(new Date()),
1428
- },
1429
- '*',
1430
- )
1431
1465
  }
1432
1466
  }
1433
1467
 
@@ -1442,17 +1476,14 @@ export const createRoot = (
1442
1476
  component,
1443
1477
  components,
1444
1478
  triggerEvent: (event, data) => {
1445
- window.parent?.postMessage(
1446
- {
1447
- type: 'component event',
1448
- event,
1449
- time: new Intl.DateTimeFormat('en-GB', {
1450
- timeStyle: 'long',
1451
- }).format(new Date()),
1452
- data,
1453
- },
1454
- '*',
1455
- )
1479
+ postMessageToEditor({
1480
+ type: 'component event',
1481
+ event,
1482
+ time: new Intl.DateTimeFormat('en-GB', {
1483
+ timeStyle: 'long',
1484
+ }).format(new Date()),
1485
+ data,
1486
+ })
1456
1487
  },
1457
1488
  dataSignal,
1458
1489
  root: document,
@@ -1511,64 +1542,55 @@ export const createRoot = (
1511
1542
  event.preventDefault()
1512
1543
  }
1513
1544
  }
1514
- window.parent?.postMessage(
1515
- {
1516
- type: 'keydown',
1517
- event: {
1518
- key: event.key,
1519
- metaKey: event.metaKey,
1520
- shiftKey: event.shiftKey,
1521
- altKey: event.altKey,
1522
- },
1545
+ postMessageToEditor({
1546
+ type: 'keydown',
1547
+ event: {
1548
+ key: event.key,
1549
+ metaKey: event.metaKey,
1550
+ shiftKey: event.shiftKey,
1551
+ altKey: event.altKey,
1523
1552
  },
1524
- '*',
1525
- )
1553
+ })
1526
1554
  })
1527
1555
  document.addEventListener('keyup', (event) => {
1528
1556
  if (isInputTarget(event)) {
1529
1557
  return
1530
1558
  }
1531
- window.parent?.postMessage(
1532
- {
1533
- type: 'keyup',
1534
- event: {
1535
- key: event.key,
1536
- metaKey: event.metaKey,
1537
- shiftKey: event.shiftKey,
1538
- altKey: event.altKey,
1539
- },
1559
+ postMessageToEditor({
1560
+ type: 'keyup',
1561
+ event: {
1562
+ key: event.key,
1563
+ metaKey: event.metaKey,
1564
+ shiftKey: event.shiftKey,
1565
+ altKey: event.altKey,
1540
1566
  },
1541
- '*',
1542
- )
1567
+ })
1543
1568
  })
1544
1569
  document.addEventListener('keypress', (event) => {
1545
1570
  if (isInputTarget(event)) {
1546
1571
  return
1547
1572
  }
1548
- window.parent?.postMessage(
1549
- {
1550
- type: 'keypress',
1551
- event: {
1552
- key: event.key,
1553
- metaKey: event.metaKey,
1554
- shiftKey: event.shiftKey,
1555
- altKey: event.altKey,
1556
- },
1573
+ postMessageToEditor({
1574
+ type: 'keypress',
1575
+ event: {
1576
+ key: event.key,
1577
+ metaKey: event.metaKey,
1578
+ shiftKey: event.shiftKey,
1579
+ altKey: event.altKey,
1557
1580
  },
1558
- '*',
1559
- )
1581
+ })
1560
1582
  })
1561
1583
 
1562
1584
  dataSignal.subscribe((data) => {
1563
1585
  if (component && components && packageComponents && data) {
1564
1586
  try {
1565
- window.parent?.postMessage({ type: 'data', data }, '*')
1587
+ postMessageToEditor({ type: 'data', data })
1566
1588
  } catch {
1567
1589
  // If we're unable to send the data, let's try to JSON serialize it
1568
- window.parent?.postMessage(
1569
- { type: 'data', data: JSON.parse(JSON.stringify(data)) },
1570
- '*',
1571
- )
1590
+ postMessageToEditor({
1591
+ type: 'data',
1592
+ data: JSON.parse(JSON.stringify(data)),
1593
+ })
1572
1594
  }
1573
1595
  }
1574
1596
  })
@@ -1614,24 +1636,18 @@ export const createRoot = (
1614
1636
  ) {
1615
1637
  const selectionRect = getRectData(getDOMNodeFromNodeId(selectedNodeId))
1616
1638
  if (!fastDeepEqual(prevSelectionRect, selectionRect)) {
1617
- window.parent?.postMessage(
1618
- {
1619
- type: 'selectionRect',
1620
- rect: selectionRect,
1621
- },
1622
- '*',
1623
- )
1639
+ postMessageToEditor({
1640
+ type: 'selectionRect',
1641
+ rect: selectionRect,
1642
+ })
1624
1643
  }
1625
1644
 
1626
1645
  const highlightRect = getRectData(getDOMNodeFromNodeId(highlightedNodeId))
1627
1646
  if (!fastDeepEqual(prevHighlightedRect, highlightRect)) {
1628
- window.parent?.postMessage(
1629
- {
1630
- type: 'highlightRect',
1631
- rect: highlightRect,
1632
- },
1633
- '*',
1634
- )
1647
+ postMessageToEditor({
1648
+ type: 'highlightRect',
1649
+ rect: highlightRect,
1650
+ })
1635
1651
  }
1636
1652
 
1637
1653
  requestAnimationFrame(() => syncOverlayRects(selectionRect, highlightRect))
@@ -1755,3 +1771,91 @@ const insertTheme = (parent: HTMLElement, theme: Theme | OldTheme) => {
1755
1771
  })
1756
1772
  parent.appendChild(styleElem)
1757
1773
  }
1774
+
1775
+ type PostMessageType =
1776
+ | {
1777
+ type: 'textComputedStyle'
1778
+ computedStyle: Record<string, string>
1779
+ }
1780
+ | {
1781
+ type: 'selection'
1782
+ selectedNodeId: string | null
1783
+ }
1784
+ | {
1785
+ type: 'highlight'
1786
+ highlightedNodeId: string | null
1787
+ }
1788
+ | {
1789
+ type: 'navigate'
1790
+ name: string
1791
+ }
1792
+ | {
1793
+ type: 'documentScrollSize'
1794
+ scrollHeight: number
1795
+ scrollWidth: number
1796
+ }
1797
+ | {
1798
+ type: 'nodeMoved'
1799
+ copy: boolean
1800
+ parent?: string | null
1801
+ index?: number
1802
+ }
1803
+ | {
1804
+ type: 'computedStyle'
1805
+ computedStyle: Record<string, string>
1806
+ }
1807
+ | {
1808
+ type: 'style'
1809
+ time: string
1810
+ }
1811
+ | {
1812
+ type: 'component event'
1813
+ event: any
1814
+ time: string
1815
+ data: any
1816
+ }
1817
+ | {
1818
+ type: 'keydown'
1819
+ event: {
1820
+ key: string
1821
+ metaKey: boolean
1822
+ shiftKey: boolean
1823
+ altKey: boolean
1824
+ }
1825
+ }
1826
+ | {
1827
+ type: 'keyup'
1828
+ event: {
1829
+ key: string
1830
+ metaKey: boolean
1831
+ shiftKey: boolean
1832
+ altKey: boolean
1833
+ }
1834
+ }
1835
+ | {
1836
+ type: 'keypress'
1837
+ event: {
1838
+ key: string
1839
+ metaKey: boolean
1840
+ shiftKey: boolean
1841
+ altKey: boolean
1842
+ }
1843
+ }
1844
+ | { type: 'data'; data: ComponentData }
1845
+ | {
1846
+ type: 'selectionRect'
1847
+ rect: ReturnType<typeof getRectData>
1848
+ }
1849
+ | {
1850
+ type: 'highlightRect'
1851
+ rect: ReturnType<typeof getRectData>
1852
+ }
1853
+ | {
1854
+ type: 'introspectionResult'
1855
+ data: any
1856
+ apiKey: string
1857
+ }
1858
+
1859
+ const postMessageToEditor = (message: PostMessageType) => {
1860
+ window.parent?.postMessage(message, '*')
1861
+ }