@nordcraft/runtime 1.0.1 → 1.0.2

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/package.json CHANGED
@@ -4,8 +4,8 @@
4
4
  "type": "module",
5
5
  "homepage": "https://github.com/nordcraftengine/nordcraft",
6
6
  "dependencies": {
7
- "@nordcraft/core": "1.0.1",
8
- "@nordcraft/std-lib": "1.0.1",
7
+ "@nordcraft/core": "1.0.2",
8
+ "@nordcraft/std-lib": "1.0.2",
9
9
  "fast-deep-equal": "3.1.3",
10
10
  "path-to-regexp": "6.3.0"
11
11
  },
@@ -21,5 +21,5 @@
21
21
  "files": ["dist", "src"],
22
22
  "main": "dist/page.main.js",
23
23
  "types": "dist/page.main.d.ts",
24
- "version": "1.0.1"
24
+ "version": "1.0.2"
25
25
  }
@@ -1,4 +1,3 @@
1
- import { getRectData } from '../../editor-preview.main'
2
1
  import { tryStartViewTransition } from '../../utils/tryStartViewTransition'
3
2
  import type { DragState } from '../types'
4
3
  import { DRAG_MOVE_CLASSNAME } from './dragMove'
@@ -57,13 +56,6 @@ export async function dragEnded(dragState: DragState, canceled: boolean) {
57
56
  node.style.removeProperty('--drag-repeat-node-opacity')
58
57
  })
59
58
  removeDropHighlight()
60
- window.parent.postMessage(
61
- {
62
- type: 'selectionRect',
63
- rect: getRectData(dragState.element),
64
- },
65
- '*',
66
- )
67
59
  }).finished.then(() => {
68
60
  dragState.element.style.removeProperty('view-transition-name')
69
61
  siblings.forEach((node) => {
@@ -99,14 +99,14 @@ export function dragStarted({
99
99
  }
100
100
 
101
101
  const followRect = dragState.element.getBoundingClientRect()
102
- dragState.repeatedNodes.forEach((node, i) => {
102
+ dragState.repeatedNodes.forEach((node) => {
103
103
  // Calculate rect without rotation as it expands the rect and makes it difficult to calculate the correct position
104
104
  node.style.setProperty('rotate', '0deg')
105
105
  const fromRect = node.getBoundingClientRect()
106
106
  node.style.removeProperty('rotate')
107
107
  const toX = followRect.left + followRect.width / 2 - fromRect.width / 2
108
108
  const toY = followRect.top + followRect.height / 2 - fromRect.height / 2
109
- const interpolation = 0.4 / (i + 1)
109
+ const interpolation = 0.4
110
110
  const x = fromRect.left + (toX - fromRect.left) * interpolation
111
111
  const y = fromRect.top + (toY - fromRect.top) * interpolation
112
112
  node.style.setProperty('--drag-repeat-node-translate', `${x}px ${y}px`)
@@ -61,9 +61,6 @@ type ToddlePreviewEvent =
61
61
  type: 'style_variant_changed'
62
62
  variantIndex: number | null
63
63
  }
64
- | {
65
- type: 'update'
66
- }
67
64
  | {
68
65
  type: 'component'
69
66
  component: Component
@@ -319,6 +316,7 @@ export const createRoot = (
319
316
  let altKey = false
320
317
  let metaKey = false
321
318
  let previewStyleAnimationFrame = -1
319
+ let timelineTimeAnimationFrame = -1
322
320
 
323
321
  /**
324
322
  * Modifies all link nodes on a component
@@ -341,34 +339,6 @@ export const createRoot = (
341
339
  console.error('UNTRUSTED MESSAGE')
342
340
  }
343
341
  switch (message.data?.type) {
344
- case 'update':
345
- {
346
- if (highlightedNodeId) {
347
- const highlightedNode = getDOMNodeFromNodeId(highlightedNodeId)
348
- if (highlightedNode) {
349
- window.parent?.postMessage(
350
- {
351
- type: 'highlightRect',
352
- rect: getRectData(highlightedNode),
353
- },
354
- '*',
355
- )
356
- }
357
- }
358
- if (selectedNodeId) {
359
- const selectedNode = getDOMNodeFromNodeId(selectedNodeId)
360
- if (selectedNode) {
361
- window.parent?.postMessage(
362
- {
363
- type: 'selectionRect',
364
- rect: getRectData(selectedNode),
365
- },
366
- '*',
367
- )
368
- }
369
- }
370
- }
371
- break
372
342
  case 'component': {
373
343
  if (!message.data.component) {
374
344
  return
@@ -413,34 +383,12 @@ export const createRoot = (
413
383
 
414
384
  update()
415
385
 
416
- if (highlightedNodeId) {
417
- const highlightedNode = getDOMNodeFromNodeId(highlightedNodeId)
418
- if (highlightedNode) {
419
- window.parent?.postMessage(
420
- {
421
- type: 'highlightRect',
422
- rect: getRectData(highlightedNode),
423
- },
424
- '*',
425
- )
426
- }
427
- }
428
386
  if (selectedNodeId) {
429
387
  if (styleVariantSelection) {
430
388
  updateSelectedStyleVariant(
431
389
  styleVariantSelection.styleVariantIndex,
432
390
  )
433
391
  }
434
- const selectedNode = getDOMNodeFromNodeId(selectedNodeId)
435
- if (selectedNode) {
436
- window.parent?.postMessage(
437
- {
438
- type: 'selectionRect',
439
- rect: getRectData(selectedNode),
440
- },
441
- '*',
442
- )
443
- }
444
392
  }
445
393
 
446
394
  break
@@ -527,15 +475,6 @@ export const createRoot = (
527
475
 
528
476
  updateConditionalElements()
529
477
 
530
- const selectedNode = getDOMNodeFromNodeId(selectedNodeId)
531
- window.parent?.postMessage(
532
- {
533
- type: 'selectionRect',
534
- rect: getRectData(selectedNode),
535
- },
536
- '*',
537
- )
538
-
539
478
  const node = getDOMNodeFromNodeId(selectedNodeId)
540
479
  const element =
541
480
  component?.nodes[node?.getAttribute('data-node-id') ?? '']
@@ -579,28 +518,11 @@ export const createRoot = (
579
518
  selectedNode.getAttribute('data-node-type') === 'text'
580
519
  ) {
581
520
  ;(selectedNode as HTMLElement).innerText = innerText
582
- window.parent?.postMessage(
583
- {
584
- type: 'selectionRect',
585
- rect: getRectData(selectedNode),
586
- },
587
- '*',
588
- )
589
521
  }
590
522
  return
591
523
  }
592
524
  case 'highlight': {
593
- if (highlightedNodeId !== message.data.highlightedNodeId) {
594
- highlightedNodeId = message.data.highlightedNodeId ?? null
595
- const highlightedNode = getDOMNodeFromNodeId(highlightedNodeId)
596
- window.parent?.postMessage(
597
- {
598
- type: 'highlightRect',
599
- rect: getRectData(highlightedNode),
600
- },
601
- '*',
602
- )
603
- }
525
+ highlightedNodeId = message.data.highlightedNodeId ?? null
604
526
  return
605
527
  }
606
528
  case 'mousemove':
@@ -967,41 +889,35 @@ export const createRoot = (
967
889
  )
968
890
  styleElem.setAttribute('data-timeline-keyframes', '')
969
891
  document.head.appendChild(styleElem)
970
- window.parent?.postMessage(
971
- {
972
- type: 'selectionRect',
973
- rect: getRectData(
974
- getDOMNodeFromNodeId(selectedNodeId) ?? document.body,
975
- ),
976
- },
977
- '*',
978
- )
979
892
  break
980
893
 
981
894
  case 'set_timeline_time':
982
895
  const { time, timingFunction, fillMode } = message.data
983
- const prevAnimatedElement = getDOMNodeFromNodeId(
984
- animationState?.animatedElementId ?? '',
985
- )
986
- animationState = {
987
- animatedElementId: time !== null ? selectedNodeId : null,
988
- time,
989
- timingFunction,
990
- fillMode,
991
- }
896
+ cancelAnimationFrame(timelineTimeAnimationFrame)
897
+ timelineTimeAnimationFrame = requestAnimationFrame(() => {
898
+ const animatedElementChanged =
899
+ animationState?.animatedElementId !== selectedNodeId
900
+ animationState = {
901
+ animatedElementId: time !== null ? selectedNodeId : null,
902
+ time,
903
+ timingFunction,
904
+ fillMode,
905
+ }
992
906
 
993
- const animatedElement = getDOMNodeFromNodeId(
994
- animationState.animatedElementId,
995
- )
996
- if (
997
- prevAnimatedElement === null ||
998
- prevAnimatedElement !== animatedElement
999
- ) {
1000
- prevAnimatedElement?.classList.remove('editor-preview-timeline')
1001
- }
907
+ // Cleanup on null
908
+ if (time === null) {
909
+ document.head
910
+ .querySelector('[data-id="preview-animation-styles"]')
911
+ ?.remove()
912
+ document.body.style.removeProperty('--editor-timeline-position')
913
+ document.body.style.removeProperty(
914
+ '--editor-timeline-timing-function',
915
+ )
916
+ document.body.style.removeProperty('--editor-timeline-fill-mode')
917
+ update()
918
+ return
919
+ }
1002
920
 
1003
- if (animatedElement && time !== null) {
1004
- animatedElement.classList.add('editor-preview-timeline')
1005
921
  document.body.style.setProperty(
1006
922
  '--editor-timeline-position',
1007
923
  `${time}s`,
@@ -1014,22 +930,31 @@ export const createRoot = (
1014
930
  '--editor-timeline-fill-mode',
1015
931
  fillMode ?? 'none',
1016
932
  )
1017
- } else {
1018
- document.body.style.removeProperty('--editor-timeline-position')
1019
- document.body.style.removeProperty(
1020
- '--editor-timeline-timing-function',
1021
- )
1022
- document.body.style.removeProperty('--editor-timeline-fill-mode')
1023
- update()
1024
- }
1025
933
 
1026
- window.parent?.postMessage(
1027
- {
1028
- type: 'selectionRect',
1029
- rect: getRectData(animatedElement),
1030
- },
1031
- '*',
1032
- )
934
+ if (animatedElementChanged) {
935
+ let styleTag = document.head.querySelector(
936
+ '[data-id="preview-animation-styles"]',
937
+ )
938
+ if (!styleTag) {
939
+ styleTag = document.createElement('style')
940
+ styleTag.setAttribute('data-id', 'preview-animation-styles')
941
+ document.head.appendChild(styleTag)
942
+ }
943
+
944
+ // Set the animation styles for self and repeated nodes, but pause for all others
945
+ // TODO: Consider if we should set all other animations to follow the current timeline time, by setting animation-delay with paused
946
+ styleTag.innerHTML = `
947
+ [data-id] {
948
+ animation-play-state: paused !important;
949
+ }
950
+ [data-id="${animationState.animatedElementId}"], [data-id="${animationState.animatedElementId}"] ~ [data-id^="${animationState.animatedElementId}("] {
951
+ animation: preview_timeline 1s paused normal !important;
952
+ animation-fill-mode: var(--editor-timeline-fill-mode) !important;
953
+ animation-timing-function: var(--editor-timeline-timing-function) !important;
954
+ animation-delay: calc(0s - var(--editor-timeline-position)) !important;
955
+ }`
956
+ }
957
+ })
1033
958
  break
1034
959
  case 'preview_style':
1035
960
  const { styles: previewStyleStyles } = message.data
@@ -1059,14 +984,6 @@ export const createRoot = (
1059
984
  ${previewStyles}
1060
985
  transition: none !important;
1061
986
  }`
1062
-
1063
- window.parent?.postMessage(
1064
- {
1065
- type: 'selectionRect',
1066
- rect: getRectData(getDOMNodeFromNodeId(selectedNodeId)),
1067
- },
1068
- '*',
1069
- )
1070
987
  })
1071
988
  break
1072
989
  }
@@ -1156,14 +1073,6 @@ export const createRoot = (
1156
1073
  }
1157
1074
  }
1158
1075
  }
1159
- const selectedNode = getDOMNodeFromNodeId(selectedNodeId)
1160
- window.parent?.postMessage(
1161
- {
1162
- type: 'selectionRect',
1163
- rect: getRectData(selectedNode),
1164
- },
1165
- '*',
1166
- )
1167
1076
  }
1168
1077
 
1169
1078
  const update = () => {
@@ -1481,10 +1390,6 @@ export const createRoot = (
1481
1390
  )
1482
1391
  }
1483
1392
  }
1484
- // Rerendering may clear editor-preview-only styles, so we need to reapply them
1485
- getDOMNodeFromNodeId(animationState?.animatedElementId)?.classList.add(
1486
- 'editor-preview-timeline',
1487
- )
1488
1393
 
1489
1394
  ctx = newCtx
1490
1395
  }
@@ -1661,6 +1566,58 @@ export const createRoot = (
1661
1566
  testMode: mode === 'test',
1662
1567
  })
1663
1568
  }
1569
+
1570
+ // Animations are first class citizens in Nordcraft, so we sync their overlay positions on each frame
1571
+ ;(function syncOverlayRects(
1572
+ prevSelectionRect?: ReturnType<typeof getRectData>,
1573
+ prevHighlightedRect?: ReturnType<typeof getRectData>,
1574
+ ) {
1575
+ const selectionRect = getRectData(getDOMNodeFromNodeId(selectedNodeId))
1576
+ if (!fastDeepEqual(prevSelectionRect, selectionRect)) {
1577
+ window.parent?.postMessage(
1578
+ {
1579
+ type: 'selectionRect',
1580
+ rect: selectionRect,
1581
+ },
1582
+ '*',
1583
+ )
1584
+ }
1585
+
1586
+ const highlightRect = getRectData(getDOMNodeFromNodeId(highlightedNodeId))
1587
+ if (!fastDeepEqual(prevHighlightedRect, highlightRect)) {
1588
+ window.parent?.postMessage(
1589
+ {
1590
+ type: 'highlightRect',
1591
+ rect: highlightRect,
1592
+ },
1593
+ '*',
1594
+ )
1595
+ }
1596
+
1597
+ requestAnimationFrame(() => syncOverlayRects(selectionRect, highlightRect))
1598
+ })()
1599
+ }
1600
+
1601
+ function getRectData(selectedNode: Element | null | undefined) {
1602
+ if (!selectedNode) {
1603
+ return null
1604
+ }
1605
+
1606
+ const rect = selectedNode.getBoundingClientRect()
1607
+ return {
1608
+ left: rect.left,
1609
+ right: rect.right,
1610
+ top: rect.top,
1611
+ bottom: rect.bottom,
1612
+ width: rect.width,
1613
+ height: rect.height,
1614
+ x: rect.x,
1615
+ y: rect.y,
1616
+ borderRadius: window
1617
+ .getComputedStyle(selectedNode)
1618
+ .borderRadius.split(' ')
1619
+ .map(parseFloat),
1620
+ }
1664
1621
  }
1665
1622
 
1666
1623
  const insertOrReplaceHeadNode = (id: string, node: Node) => {
@@ -1724,28 +1681,6 @@ export function getDOMNodeFromNodeId(
1724
1681
  )
1725
1682
  }
1726
1683
 
1727
- export function getRectData(selectedNode: Element | null | undefined) {
1728
- if (!selectedNode) {
1729
- return null
1730
- }
1731
-
1732
- const rect = selectedNode.getBoundingClientRect()
1733
- return {
1734
- left: rect.left,
1735
- right: rect.right,
1736
- top: rect.top,
1737
- bottom: rect.bottom,
1738
- width: rect.width,
1739
- height: rect.height,
1740
- x: rect.x,
1741
- y: rect.y,
1742
- borderRadius: window
1743
- .getComputedStyle(selectedNode)
1744
- .borderRadius.split(' ')
1745
- .map(parseFloat),
1746
- }
1747
- }
1748
-
1749
1684
  function getNodeId(component: Component, path: string[]) {
1750
1685
  function getId(
1751
1686
  [nextChild, ...path]: string[],