@nordcraft/runtime 1.0.0

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 (168) hide show
  1. package/README.md +5 -0
  2. package/dist/api/createAPI.d.ts +20 -0
  3. package/dist/api/createAPI.js +319 -0
  4. package/dist/api/createAPI.js.map +1 -0
  5. package/dist/api/createAPIv2.d.ts +7 -0
  6. package/dist/api/createAPIv2.js +686 -0
  7. package/dist/api/createAPIv2.js.map +1 -0
  8. package/dist/components/createComponent.d.ts +13 -0
  9. package/dist/components/createComponent.js +216 -0
  10. package/dist/components/createComponent.js.map +1 -0
  11. package/dist/components/createElement.d.ts +3 -0
  12. package/dist/components/createElement.js +208 -0
  13. package/dist/components/createElement.js.map +1 -0
  14. package/dist/components/createNode.d.ts +22 -0
  15. package/dist/components/createNode.js +272 -0
  16. package/dist/components/createNode.js.map +1 -0
  17. package/dist/components/createSlot.d.ts +3 -0
  18. package/dist/components/createSlot.js +49 -0
  19. package/dist/components/createSlot.js.map +1 -0
  20. package/dist/components/createText.d.ts +23 -0
  21. package/dist/components/createText.js +68 -0
  22. package/dist/components/createText.js.map +1 -0
  23. package/dist/components/createText.test.d.ts +1 -0
  24. package/dist/components/createText.test.js +113 -0
  25. package/dist/components/createText.test.js.map +1 -0
  26. package/dist/components/renderComponent.d.ts +34 -0
  27. package/dist/components/renderComponent.js +66 -0
  28. package/dist/components/renderComponent.js.map +1 -0
  29. package/dist/context/isContextProvider.d.ts +2 -0
  30. package/dist/context/isContextProvider.js +5 -0
  31. package/dist/context/isContextProvider.js.map +1 -0
  32. package/dist/context/subscribeToContext.d.ts +4 -0
  33. package/dist/context/subscribeToContext.js +93 -0
  34. package/dist/context/subscribeToContext.js.map +1 -0
  35. package/dist/custom-components/components.d.ts +1 -0
  36. package/dist/custom-components/components.js +2 -0
  37. package/dist/custom-components/components.js.map +1 -0
  38. package/dist/custom-components/toddle-portal.d.ts +6 -0
  39. package/dist/custom-components/toddle-portal.js +20 -0
  40. package/dist/custom-components/toddle-portal.js.map +1 -0
  41. package/dist/custom-element/ToddleComponent.d.ts +37 -0
  42. package/dist/custom-element/ToddleComponent.js +244 -0
  43. package/dist/custom-element/ToddleComponent.js.map +1 -0
  44. package/dist/custom-element/defineComponents.d.ts +26 -0
  45. package/dist/custom-element/defineComponents.js +42 -0
  46. package/dist/custom-element/defineComponents.js.map +1 -0
  47. package/dist/custom-element.main.d.ts +3 -0
  48. package/dist/custom-element.main.esm.js +266 -0
  49. package/dist/custom-element.main.esm.js.map +7 -0
  50. package/dist/custom-element.main.js +14 -0
  51. package/dist/custom-element.main.js.map +1 -0
  52. package/dist/debug/logState.d.ts +4 -0
  53. package/dist/debug/logState.js +19 -0
  54. package/dist/debug/logState.js.map +1 -0
  55. package/dist/editor/drag-drop/dragEnded.d.ts +2 -0
  56. package/dist/editor/drag-drop/dragEnded.js +56 -0
  57. package/dist/editor/drag-drop/dragEnded.js.map +1 -0
  58. package/dist/editor/drag-drop/dragMove.d.ts +3 -0
  59. package/dist/editor/drag-drop/dragMove.js +74 -0
  60. package/dist/editor/drag-drop/dragMove.js.map +1 -0
  61. package/dist/editor/drag-drop/dragReorder.d.ts +3 -0
  62. package/dist/editor/drag-drop/dragReorder.js +92 -0
  63. package/dist/editor/drag-drop/dragReorder.js.map +1 -0
  64. package/dist/editor/drag-drop/dragStarted.d.ts +9 -0
  65. package/dist/editor/drag-drop/dragStarted.js +100 -0
  66. package/dist/editor/drag-drop/dragStarted.js.map +1 -0
  67. package/dist/editor/drag-drop/dropHighlight.d.ts +16 -0
  68. package/dist/editor/drag-drop/dropHighlight.js +50 -0
  69. package/dist/editor/drag-drop/dropHighlight.js.map +1 -0
  70. package/dist/editor/drag-drop/getInsertAreas.d.ts +20 -0
  71. package/dist/editor/drag-drop/getInsertAreas.js +220 -0
  72. package/dist/editor/drag-drop/getInsertAreas.js.map +1 -0
  73. package/dist/editor-preview.main.d.ts +19 -0
  74. package/dist/editor-preview.main.js +1303 -0
  75. package/dist/editor-preview.main.js.map +1 -0
  76. package/dist/events/handleAction.d.ts +3 -0
  77. package/dist/events/handleAction.js +307 -0
  78. package/dist/events/handleAction.js.map +1 -0
  79. package/dist/page.main.d.ts +7 -0
  80. package/dist/page.main.esm.js +8 -0
  81. package/dist/page.main.esm.js.map +7 -0
  82. package/dist/page.main.js +395 -0
  83. package/dist/page.main.js.map +1 -0
  84. package/dist/signal/signal.d.ts +19 -0
  85. package/dist/signal/signal.js +65 -0
  86. package/dist/signal/signal.js.map +1 -0
  87. package/dist/styles/style.d.ts +4 -0
  88. package/dist/styles/style.js +196 -0
  89. package/dist/styles/style.js.map +1 -0
  90. package/dist/utils/BatchQueue.d.ts +10 -0
  91. package/dist/utils/BatchQueue.js +25 -0
  92. package/dist/utils/BatchQueue.js.map +1 -0
  93. package/dist/utils/createFormulaCache.d.ts +3 -0
  94. package/dist/utils/createFormulaCache.js +81 -0
  95. package/dist/utils/createFormulaCache.js.map +1 -0
  96. package/dist/utils/findNearestLine.d.ts +13 -0
  97. package/dist/utils/findNearestLine.js +74 -0
  98. package/dist/utils/findNearestLine.js.map +1 -0
  99. package/dist/utils/findNearestLine.test.d.ts +1 -0
  100. package/dist/utils/findNearestLine.test.js +59 -0
  101. package/dist/utils/findNearestLine.test.js.map +1 -0
  102. package/dist/utils/getDragData.d.ts +1 -0
  103. package/dist/utils/getDragData.js +10 -0
  104. package/dist/utils/getDragData.js.map +1 -0
  105. package/dist/utils/getElementTagName.d.ts +3 -0
  106. package/dist/utils/getElementTagName.js +7 -0
  107. package/dist/utils/getElementTagName.js.map +1 -0
  108. package/dist/utils/nodes.d.ts +21 -0
  109. package/dist/utils/nodes.js +89 -0
  110. package/dist/utils/nodes.js.map +1 -0
  111. package/dist/utils/omitStyle.d.ts +2 -0
  112. package/dist/utils/omitStyle.js +13 -0
  113. package/dist/utils/omitStyle.js.map +1 -0
  114. package/dist/utils/rectHasPoint.d.ts +2 -0
  115. package/dist/utils/rectHasPoint.js +4 -0
  116. package/dist/utils/rectHasPoint.js.map +1 -0
  117. package/dist/utils/setAttribute.d.ts +4 -0
  118. package/dist/utils/setAttribute.js +57 -0
  119. package/dist/utils/setAttribute.js.map +1 -0
  120. package/dist/utils/tryStartViewTransition.d.ts +5 -0
  121. package/dist/utils/tryStartViewTransition.js +14 -0
  122. package/dist/utils/tryStartViewTransition.js.map +1 -0
  123. package/dist/utils/url.d.ts +2 -0
  124. package/dist/utils/url.js +36 -0
  125. package/dist/utils/url.js.map +1 -0
  126. package/package.json +25 -0
  127. package/src/api/createAPI.ts +375 -0
  128. package/src/api/createAPIv2.ts +931 -0
  129. package/src/components/createComponent.ts +280 -0
  130. package/src/components/createElement.ts +240 -0
  131. package/src/components/createNode.ts +381 -0
  132. package/src/components/createSlot.ts +61 -0
  133. package/src/components/createText.test.ts +117 -0
  134. package/src/components/createText.ts +104 -0
  135. package/src/components/renderComponent.ts +145 -0
  136. package/src/context/isContextProvider.ts +12 -0
  137. package/src/context/subscribeToContext.ts +135 -0
  138. package/src/custom-components/components.ts +1 -0
  139. package/src/custom-components/toddle-portal.ts +19 -0
  140. package/src/custom-element/ToddleComponent.ts +315 -0
  141. package/src/custom-element/defineComponents.ts +65 -0
  142. package/src/custom-element.main.ts +24 -0
  143. package/src/debug/logState.ts +30 -0
  144. package/src/editor/drag-drop/dragEnded.ts +75 -0
  145. package/src/editor/drag-drop/dragMove.ts +95 -0
  146. package/src/editor/drag-drop/dragReorder.ts +137 -0
  147. package/src/editor/drag-drop/dragStarted.ts +145 -0
  148. package/src/editor/drag-drop/dropHighlight.ts +82 -0
  149. package/src/editor/drag-drop/getInsertAreas.ts +235 -0
  150. package/src/editor/types.d.ts +36 -0
  151. package/src/editor-preview.main.ts +1782 -0
  152. package/src/events/handleAction.ts +387 -0
  153. package/src/page.main.ts +489 -0
  154. package/src/signal/signal.ts +74 -0
  155. package/src/styles/style.ts +254 -0
  156. package/src/types.d.ts +93 -0
  157. package/src/utils/BatchQueue.ts +24 -0
  158. package/src/utils/createFormulaCache.ts +96 -0
  159. package/src/utils/findNearestLine.test.ts +65 -0
  160. package/src/utils/findNearestLine.ts +92 -0
  161. package/src/utils/getDragData.ts +11 -0
  162. package/src/utils/getElementTagName.ts +14 -0
  163. package/src/utils/nodes.ts +125 -0
  164. package/src/utils/omitStyle.ts +19 -0
  165. package/src/utils/rectHasPoint.ts +5 -0
  166. package/src/utils/setAttribute.ts +56 -0
  167. package/src/utils/tryStartViewTransition.ts +32 -0
  168. package/src/utils/url.ts +45 -0
@@ -0,0 +1,24 @@
1
+ import * as libActions from '@nordcraft/std-lib/dist/actions'
2
+ import * as libFormulas from '@nordcraft/std-lib/dist/formulas'
3
+ import fastDeepEqual from 'fast-deep-equal'
4
+ import { defineComponents } from './custom-element/defineComponents'
5
+
6
+ const loadCorePlugins = (toddle = window.toddle) => {
7
+ toddle.isEqual = fastDeepEqual
8
+
9
+ // load default formulas and actions
10
+ Object.entries(libFormulas).forEach(([name, module]) =>
11
+ toddle.registerFormula(
12
+ '@toddle/' + name,
13
+ module.default as any,
14
+ 'getArgumentInputData' in module
15
+ ? module.getArgumentInputData
16
+ : undefined,
17
+ ),
18
+ )
19
+ Object.entries(libActions).forEach(([name, module]) =>
20
+ toddle.registerAction('@toddle/' + name, module.default),
21
+ )
22
+ }
23
+
24
+ export { defineComponents, loadCorePlugins }
@@ -0,0 +1,30 @@
1
+ import type {
2
+ Component,
3
+ ComponentData,
4
+ } from '@nordcraft/core/dist/component/component.types'
5
+ import type { Signal } from '../signal/signal'
6
+
7
+ export function initLogState() {
8
+ ;(window as any).logState = () => {
9
+ // eslint-disable-next-line no-console
10
+ console.table(
11
+ Object.entries(window.__components ?? {}).map(([name, sig]) => {
12
+ return {
13
+ name,
14
+ ...(sig as any).get(),
15
+ }
16
+ }),
17
+ )
18
+ }
19
+ }
20
+
21
+ export function registerComponentToLogState(
22
+ component: Component,
23
+ dataSignal: Signal<ComponentData>,
24
+ ) {
25
+ if (!window.__components) {
26
+ window.__components = {}
27
+ }
28
+
29
+ window.__components[component.name] = dataSignal
30
+ }
@@ -0,0 +1,75 @@
1
+ import { getRectData } from '../../editor-preview.main'
2
+ import { tryStartViewTransition } from '../../utils/tryStartViewTransition'
3
+ import type { DragState } from '../types'
4
+ import { DRAG_MOVE_CLASSNAME } from './dragMove'
5
+ import { DRAG_REORDER_CLASSNAME } from './dragReorder'
6
+ import { removeDropHighlight } from './dropHighlight'
7
+
8
+ export async function dragEnded(dragState: DragState, canceled: boolean) {
9
+ dragState.destroying = true
10
+ const selectedInsertArea =
11
+ dragState.insertAreas?.[dragState.selectedInsertAreaIndex ?? -1]
12
+ const siblings =
13
+ (dragState.mode === 'insert' && !canceled
14
+ ? selectedInsertArea?.parent
15
+ : dragState.initialContainer
16
+ )?.querySelectorAll('[data-id]') ?? []
17
+ dragState.element.style.setProperty(
18
+ 'view-transition-name',
19
+ 'dropped-item-self',
20
+ )
21
+ siblings.forEach((node, i) => {
22
+ if (node instanceof HTMLElement) {
23
+ node.style.setProperty(
24
+ 'view-transition-name',
25
+ 'dropped-item-sibling-' + i,
26
+ )
27
+ }
28
+ })
29
+ dragState.repeatedNodes.forEach((node, i) => {
30
+ node.style.setProperty('view-transition-name', 'dropped-item-repeated-' + i)
31
+ })
32
+ await tryStartViewTransition(() => {
33
+ if (canceled) {
34
+ dragState.copy?.remove()
35
+ dragState.initialContainer.insertBefore(
36
+ dragState.element,
37
+ dragState.initialNextSibling,
38
+ )
39
+ } else if (dragState.mode === 'insert') {
40
+ selectedInsertArea?.parent.insertBefore(
41
+ dragState.element,
42
+ selectedInsertArea.parent.childNodes[selectedInsertArea.index],
43
+ )
44
+ }
45
+
46
+ dragState.element.classList.remove(DRAG_REORDER_CLASSNAME)
47
+ dragState.element.classList.remove(DRAG_MOVE_CLASSNAME)
48
+ dragState.element.style.removeProperty('translate')
49
+ dragState.repeatedNodes.toReversed().forEach((node) => {
50
+ dragState.element.insertAdjacentElement('afterend', node)
51
+ node.classList.remove('drag-repeat-node')
52
+ node.style.removeProperty('rotate')
53
+ node.style.removeProperty('--drag-repeat-node-width')
54
+ node.style.removeProperty('--drag-repeat-node-height')
55
+ node.style.removeProperty('--drag-repeat-node-translate')
56
+ node.style.removeProperty('--drag-repeat-node-rotate')
57
+ node.style.removeProperty('--drag-repeat-node-opacity')
58
+ })
59
+ removeDropHighlight()
60
+ window.parent.postMessage(
61
+ {
62
+ type: 'selectionRect',
63
+ rect: getRectData(dragState.element),
64
+ },
65
+ '*',
66
+ )
67
+ }).finished.then(() => {
68
+ dragState.element.style.removeProperty('view-transition-name')
69
+ siblings.forEach((node) => {
70
+ if (node instanceof HTMLElement) {
71
+ node.style.removeProperty('view-transition-name')
72
+ }
73
+ })
74
+ })
75
+ }
@@ -0,0 +1,95 @@
1
+ import { findNearestLine } from '../../utils/findNearestLine'
2
+ import type { DragState } from '../types'
3
+ import { DRAG_REORDER_CLASSNAME } from './dragReorder'
4
+ import { removeDropHighlight, setExternalDropHighlight } from './dropHighlight'
5
+ import { getInsertAreas } from './getInsertAreas'
6
+
7
+ export const DRAG_MOVE_CLASSNAME = '__drag-mode--move'
8
+
9
+ export function dragMove(dragState: DragState | null, exclude: HTMLElement[]) {
10
+ if (!dragState) {
11
+ return
12
+ }
13
+
14
+ // If the drag operation was a reorder operation, we need to switch to insert mode and perform some one-time preparation
15
+ if (dragState.mode === 'reorder') {
16
+ dragState.mode = 'insert'
17
+ dragState.element.style.setProperty('display', 'none')
18
+ // We only calculate insert locations when dragging outside the container to avoid unnecessary calculations
19
+ dragState.insertAreas ??= getInsertAreas().filter(
20
+ (x) =>
21
+ exclude.every((e) => !e?.contains(x.parent) && e !== x.parent) &&
22
+ x.parent !== document.body,
23
+ )
24
+ dragState.element.style.removeProperty('display')
25
+ const translate = dragState.element.style.getPropertyValue('translate')
26
+ dragState.element.style.setProperty('translate', '0')
27
+ const rect = dragState.element.getBoundingClientRect()
28
+ document.body.appendChild(dragState.element)
29
+ dragState.element.classList.add(DRAG_MOVE_CLASSNAME)
30
+ dragState.element.classList.remove(DRAG_REORDER_CLASSNAME)
31
+ dragState.element.style.setProperty(
32
+ '--drag-mode--move-left',
33
+ `${rect.left}px`,
34
+ )
35
+ dragState.element.style.setProperty(
36
+ '--drag-mode--move-top',
37
+ `${rect.top}px`,
38
+ )
39
+ dragState.element.style.setProperty(
40
+ '--drag-mode--move-width',
41
+ `${dragState.initialRect.width}px`,
42
+ )
43
+ dragState.element.style.setProperty('translate', translate)
44
+ }
45
+ dragState.repeatedNodes.forEach((node, i) => {
46
+ node.style.setProperty('--drag-repeat-node-opacity', i < 3 ? '0.2' : '0')
47
+ })
48
+
49
+ const lines = dragState.insertAreas?.map((line) => {
50
+ if (line.layout === 'block') {
51
+ return {
52
+ x1: line.center.x - line.size / 2,
53
+ y1: line.center.y,
54
+ x2: line.center.x + line.size / 2,
55
+ y2: line.center.y,
56
+ }
57
+ } else {
58
+ return {
59
+ x1: line.center.x,
60
+ y1: line.center.y - line.size / 2,
61
+ x2: line.center.x,
62
+ y2: line.center.y + line.size / 2,
63
+ }
64
+ }
65
+ })
66
+ const { nearestLine, projectionPoint } = findNearestLine(
67
+ lines ?? [],
68
+ dragState.lastCursorPosition,
69
+ )
70
+ if (!nearestLine || !dragState.insertAreas || !lines) {
71
+ return
72
+ }
73
+
74
+ const insertArea = dragState.insertAreas.at(lines.indexOf(nearestLine))
75
+ if (insertArea) {
76
+ dragState.selectedInsertAreaIndex =
77
+ dragState.insertAreas?.indexOf(insertArea)
78
+ window.parent?.postMessage(
79
+ {
80
+ type: 'highlight',
81
+ highlightedNodeId: insertArea.parent.getAttribute('data-id'),
82
+ },
83
+ '*',
84
+ )
85
+ setExternalDropHighlight({
86
+ layout: insertArea.layout,
87
+ center: insertArea.center,
88
+ length: insertArea.size,
89
+ color: dragState.elementType === 'component' ? 'D946EF' : '2563EB',
90
+ projectionPoint,
91
+ })
92
+ } else {
93
+ removeDropHighlight()
94
+ }
95
+ }
@@ -0,0 +1,137 @@
1
+ import { tryStartViewTransition } from '../../utils/tryStartViewTransition'
2
+ import type { DragState } from '../types'
3
+ import { DRAG_MOVE_CLASSNAME } from './dragMove'
4
+ import { setDropHighlight } from './dropHighlight'
5
+
6
+ export const DRAG_REORDER_CLASSNAME = '__drag-mode--reorder'
7
+ const OVERLAP_OFFSET_PX = 100
8
+
9
+ export function dragReorder(dragState: DragState | null) {
10
+ if (!dragState || dragState.isTransitioning) {
11
+ return
12
+ }
13
+
14
+ // If the drag operation was an insert operation, we need to switch to reorder mode and perform some one-time preparation
15
+ if (dragState.mode === 'insert') {
16
+ dragState.mode = 'reorder'
17
+
18
+ // Move back to the original container
19
+ const prevRect = dragState.element.getBoundingClientRect()
20
+ dragState.element.classList.add(DRAG_REORDER_CLASSNAME)
21
+ dragState.element.classList.remove(DRAG_MOVE_CLASSNAME)
22
+ dragState.initialContainer.insertBefore(
23
+ dragState.element,
24
+ dragState.initialNextSibling,
25
+ )
26
+ dragState.repeatedNodes.forEach((node, i) => {
27
+ node.style.setProperty('--drag-repeat-node-opacity', i < 3 ? '1' : '0')
28
+ })
29
+ const nextRect = dragState.element.getBoundingClientRect()
30
+ dragState.offset.x += nextRect.left - prevRect.left
31
+ dragState.offset.y += nextRect.top - prevRect.top
32
+ window.parent?.postMessage(
33
+ {
34
+ type: 'highlight',
35
+ highlightedNodeId: dragState.initialContainer.getAttribute('data-id'),
36
+ },
37
+ '*',
38
+ )
39
+ setDropHighlight(
40
+ dragState.element,
41
+ dragState.initialContainer,
42
+ dragState.elementType === 'component' ? 'D946EF' : '2563EB',
43
+ )
44
+ }
45
+
46
+ const bestPermutation = getBestPermutation(dragState)
47
+ if (
48
+ bestPermutation &&
49
+ dragState.element.nextElementSibling !== bestPermutation.nextSibling
50
+ ) {
51
+ dragState.isTransitioning = true
52
+ const siblings = Array.from(dragState.initialContainer.childNodes)
53
+ siblings.forEach((sibling, i) => {
54
+ if (sibling instanceof HTMLElement) {
55
+ sibling.style.setProperty('view-transition-name', 'item-' + i)
56
+ }
57
+ })
58
+ dragState.element.style.setProperty('view-transition-name', '__drag-item')
59
+
60
+ const prevLeft = dragState.element.offsetLeft
61
+ const prevTop = dragState.element.offsetTop
62
+ const transition = tryStartViewTransition(() => {
63
+ if (!dragState) {
64
+ return
65
+ }
66
+ dragState.initialContainer.insertBefore(
67
+ dragState.element,
68
+ bestPermutation.nextSibling,
69
+ )
70
+ dragState.offset.x += dragState.element.offsetLeft - prevLeft
71
+ dragState.offset.y += dragState.element.offsetTop - prevTop
72
+ dragState?.element.style.setProperty(
73
+ 'translate',
74
+ `${dragState.lastCursorPosition.x - dragState.offset.x}px ${
75
+ dragState.lastCursorPosition.y - dragState.offset.y
76
+ }px`,
77
+ )
78
+ setDropHighlight(
79
+ dragState.element,
80
+ dragState.initialContainer,
81
+ dragState.elementType === 'component' ? 'D946EF' : '2563EB',
82
+ )
83
+ })
84
+
85
+ transition.finished
86
+ .then(() => {
87
+ siblings.forEach((sibling) => {
88
+ if (sibling instanceof HTMLElement) {
89
+ sibling.style.removeProperty('view-transition-name')
90
+ }
91
+ })
92
+ if (dragState) {
93
+ dragState.isTransitioning = false
94
+ }
95
+ })
96
+ .catch(() => {})
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Return the most likely permutation to move the dragged element to based on the current drag position.
102
+ * The calculation is based on distance from the center of the dragged element to the center of the potential target element,
103
+ * but only if the dragged element is overlapping with the target element.
104
+ */
105
+ function getBestPermutation(dragState: DragState) {
106
+ const { left, top, width, height } = dragState.element.getBoundingClientRect()
107
+ const dragElementCenterX = left + width / 2
108
+ const dragElementCenterY = top + height / 2
109
+ return dragState.reorderPermutations.reduce<null | {
110
+ rect: DOMRect
111
+ nextSibling: Node | null
112
+ }>((prev, curr) => {
113
+ const isOverlapping =
114
+ Math.abs(curr.rect.left + curr.rect.width / 2 - dragElementCenterX) <
115
+ curr.rect.width / 2 + OVERLAP_OFFSET_PX &&
116
+ Math.abs(curr.rect.top + curr.rect.height / 2 - dragElementCenterY) <
117
+ curr.rect.height / 2 + OVERLAP_OFFSET_PX
118
+ if (isOverlapping) {
119
+ if (!prev) {
120
+ return curr
121
+ }
122
+
123
+ const prevDist = Math.hypot(
124
+ prev.rect.left + prev.rect.width / 2 - dragElementCenterX,
125
+ prev.rect.top + prev.rect.height / 2 - dragElementCenterY,
126
+ )
127
+ const nextDist = Math.hypot(
128
+ curr.rect.left + curr.rect.width / 2 - dragElementCenterX,
129
+ curr.rect.top + curr.rect.height / 2 - dragElementCenterY,
130
+ )
131
+
132
+ return prevDist < nextDist ? prev : curr
133
+ }
134
+
135
+ return prev
136
+ }, null)
137
+ }
@@ -0,0 +1,145 @@
1
+ import type { DragState, Point } from '../types'
2
+ import { DRAG_MOVE_CLASSNAME } from './dragMove'
3
+ import { DRAG_REORDER_CLASSNAME } from './dragReorder'
4
+ import { setDropHighlight } from './dropHighlight'
5
+
6
+ export function dragStarted({
7
+ element,
8
+ lastCursorPosition,
9
+ repeatedNodes,
10
+ asCopy,
11
+ initialContainer = element.parentElement as HTMLElement,
12
+ initialNextSibling,
13
+ }: {
14
+ element: HTMLElement
15
+ lastCursorPosition: Point
16
+ repeatedNodes: HTMLElement[]
17
+ asCopy: boolean
18
+ initialContainer?: HTMLElement
19
+ initialNextSibling?: Element | null
20
+ }) {
21
+ // Move repeat nodes as a stack below the dragged element
22
+ repeatedNodes
23
+ .map<[HTMLElement, DOMRect]>((node) => [node, node.getBoundingClientRect()])
24
+ .forEach(([node, rect], i) => {
25
+ node.classList.add('drag-repeat-node')
26
+ node.style.setProperty('--drag-repeat-node-width', `${rect.width}px`)
27
+ node.style.setProperty('--drag-repeat-node-height', `${rect.height}px`)
28
+ node.style.setProperty(
29
+ '--drag-repeat-node-translate',
30
+ `${rect.left}px ${rect.top}px`,
31
+ )
32
+ node.style.setProperty(
33
+ '--drag-repeat-node-rotate',
34
+ `${Math.random() * 9 - 4.5}deg`,
35
+ )
36
+ node.style.setProperty('--drag-repeat-node-opacity', i < 3 ? '1' : '0')
37
+ })
38
+
39
+ initialNextSibling ??= element.nextElementSibling
40
+
41
+ const dragState: DragState = {
42
+ destroying: false,
43
+ elementType: elementIsComponent(element) ? 'component' : 'element',
44
+ element,
45
+ offset: lastCursorPosition,
46
+ lastCursorPosition,
47
+ initialContainer,
48
+ initialNextSibling,
49
+ initialRect: element.getBoundingClientRect(),
50
+ reorderPermutations: [],
51
+ isTransitioning: false,
52
+ repeatedNodes,
53
+ mode: 'reorder',
54
+ }
55
+
56
+ if (asCopy) {
57
+ dragState.copy = element.cloneNode(true) as HTMLElement
58
+ dragState.copy.style.setProperty('opacity', '0.5')
59
+ dragState.copy.classList.remove(DRAG_REORDER_CLASSNAME)
60
+ dragState.copy.classList.remove(DRAG_MOVE_CLASSNAME)
61
+ dragState.initialContainer.insertBefore(
62
+ dragState.copy,
63
+ dragState.initialNextSibling,
64
+ )
65
+ }
66
+
67
+ // Calculate all possible permutations, by iterating over all siblings of the targetContainer
68
+ // and moving the draggedElement to before each sibling to calculate the rect and then
69
+ // store it in the dragState.permutations array
70
+ dragState.initialContainer.childNodes.forEach((sibling) => {
71
+ if (
72
+ sibling instanceof Element &&
73
+ sibling.getAttribute('data-id') &&
74
+ // Only first item of repeated nodes should be considered
75
+ !sibling.getAttribute('data-id')?.endsWith(')') &&
76
+ !sibling.hasAttribute('data-component') &&
77
+ repeatedNodes.every((node) => node !== sibling)
78
+ ) {
79
+ dragState?.initialContainer.insertBefore(element, sibling)
80
+ dragState?.reorderPermutations.push({
81
+ nextSibling: sibling,
82
+ rect: element.getBoundingClientRect(),
83
+ })
84
+ }
85
+ })
86
+ // Test the last position
87
+ if (!dragState.initialContainer.hasAttribute('data-component')) {
88
+ dragState.initialContainer.appendChild(element)
89
+ dragState?.reorderPermutations.push({
90
+ nextSibling: null,
91
+ rect: element.getBoundingClientRect(),
92
+ })
93
+ }
94
+ // Restore the initial position of the draggedElement
95
+ dragState.initialContainer.insertBefore(element, dragState.initialNextSibling)
96
+ ;(function followRepeatedNodes() {
97
+ if (dragState.destroying || !dragState.element.isConnected) {
98
+ return
99
+ }
100
+
101
+ const followRect = dragState.element.getBoundingClientRect()
102
+ dragState.repeatedNodes.forEach((node, i) => {
103
+ // Calculate rect without rotation as it expands the rect and makes it difficult to calculate the correct position
104
+ node.style.setProperty('rotate', '0deg')
105
+ const fromRect = node.getBoundingClientRect()
106
+ node.style.removeProperty('rotate')
107
+ const toX = followRect.left + followRect.width / 2 - fromRect.width / 2
108
+ const toY = followRect.top + followRect.height / 2 - fromRect.height / 2
109
+ const interpolation = 0.4 / (i + 1)
110
+ const x = fromRect.left + (toX - fromRect.left) * interpolation
111
+ const y = fromRect.top + (toY - fromRect.top) * interpolation
112
+ node.style.setProperty('--drag-repeat-node-translate', `${x}px ${y}px`)
113
+ })
114
+
115
+ requestAnimationFrame(followRepeatedNodes)
116
+ })()
117
+
118
+ // Highlight container
119
+ element.classList.add(DRAG_REORDER_CLASSNAME)
120
+ window.parent?.postMessage(
121
+ {
122
+ type: 'highlight',
123
+ highlightedNodeId: dragState.initialContainer.getAttribute('data-id'),
124
+ },
125
+ '*',
126
+ )
127
+
128
+ setDropHighlight(
129
+ dragState.element,
130
+ dragState.initialContainer,
131
+ dragState.elementType === 'component' ? 'D946EF' : '2563EB',
132
+ )
133
+
134
+ return dragState
135
+ }
136
+
137
+ /**
138
+ * Semi-hacky way to determine if an element is a Toddle component by checking if it is a root node, but not the top-level root node (page).
139
+ */
140
+ function elementIsComponent(element: Element) {
141
+ return (
142
+ element.getAttribute('data-node-id') === 'root' &&
143
+ element.getAttribute('data-id') !== '0'
144
+ )
145
+ }
@@ -0,0 +1,82 @@
1
+ import type { Point } from '../types'
2
+
3
+ let highlight: HTMLElement | null = null
4
+
5
+ /**
6
+ * Visual representation of where a dragged node will be dropped.
7
+ */
8
+ export function setDropHighlight(
9
+ element: HTMLElement,
10
+ targetContainer: HTMLElement,
11
+ color: string,
12
+ ) {
13
+ highlight?.remove()
14
+ if (!element || !targetContainer) {
15
+ return
16
+ }
17
+
18
+ highlight = document.createElement('div')
19
+ highlight.classList.add('__drop-area')
20
+ const { top, left } = targetContainer.getBoundingClientRect()
21
+ highlight.style.setProperty('position', 'fixed')
22
+ highlight.style.setProperty(
23
+ '--drop-area-left',
24
+ `${element.offsetLeft + left}px`,
25
+ )
26
+ highlight.style.setProperty('--drop-area-top', `${element.offsetTop + top}px`)
27
+ highlight.style.setProperty('--drop-area-width', `${element.offsetWidth}px`)
28
+ highlight.style.setProperty('--drop-area-height', `${element.offsetHeight}px`)
29
+ highlight.style.setProperty('--drop-area-outline-color', `#${color}`)
30
+ highlight.style.setProperty(
31
+ '--drop-area-border-radius',
32
+ window.getComputedStyle(element).borderRadius,
33
+ )
34
+ highlight.style.setProperty(
35
+ '--dashed-line-color',
36
+ `color-mix(in srgb, #${color} 33%, transparent)`,
37
+ )
38
+ document.body.append(highlight)
39
+ }
40
+
41
+ /**
42
+ * Visual representation of where a dragged node will be dropped outside of its own container.
43
+ */
44
+ export function setExternalDropHighlight({
45
+ layout,
46
+ center,
47
+ length,
48
+ color,
49
+ projectionPoint,
50
+ }: {
51
+ layout: 'block' | 'inline'
52
+ center: Point
53
+ length: number
54
+ color: string
55
+ projectionPoint: number
56
+ }) {
57
+ highlight?.remove()
58
+ highlight = document.createElement('div')
59
+ highlight.classList.add('__drop-area-line')
60
+ highlight.style.setProperty('--drop-area-left', `${center.x}px`)
61
+ highlight.style.setProperty('--drop-area-top', `${center.y}px`)
62
+ if (layout === 'block') {
63
+ highlight.style.setProperty('--drop-area-width', `${length}px`)
64
+ highlight.style.setProperty('--drop-area-height', `4px`)
65
+ highlight.style.setProperty('--drop-area-translate', '-50% -2px')
66
+ } else {
67
+ highlight.style.setProperty('--drop-area-width', `4px`)
68
+ highlight.style.setProperty('--drop-area-height', `${length}px`)
69
+ highlight.style.setProperty('--drop-area-translate', '-2px -50%')
70
+ }
71
+
72
+ const gradient = `radial-gradient(circle at ${projectionPoint * 100}% ${
73
+ projectionPoint * 100
74
+ }%, #${color} 0%, #${color}55 max(100%, 75px))`
75
+ highlight.style.setProperty('--drop-area-background', gradient)
76
+ document.body.appendChild(highlight)
77
+ }
78
+
79
+ export function removeDropHighlight() {
80
+ highlight?.remove()
81
+ highlight = null
82
+ }