@witchcraft/layout 0.1.3 → 0.2.1

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 (189) hide show
  1. package/README.md +27 -24
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/FrameDragHandle.d.vue.ts +15 -0
  4. package/dist/runtime/components/FrameDragHandle.vue +28 -0
  5. package/dist/runtime/components/FrameDragHandle.vue.d.ts +15 -0
  6. package/dist/runtime/components/LayoutDecos.d.vue.ts +2 -4
  7. package/dist/runtime/components/LayoutDecos.vue +10 -29
  8. package/dist/runtime/components/LayoutDecos.vue.d.ts +2 -4
  9. package/dist/runtime/components/LayoutEdges.d.vue.ts +3 -3
  10. package/dist/runtime/components/LayoutEdges.vue +8 -8
  11. package/dist/runtime/components/LayoutEdges.vue.d.ts +3 -3
  12. package/dist/runtime/components/LayoutFrame.d.vue.ts +1 -1
  13. package/dist/runtime/components/LayoutFrame.vue +0 -1
  14. package/dist/runtime/components/LayoutFrame.vue.d.ts +1 -1
  15. package/dist/runtime/components/LayoutShapeSquare.d.vue.ts +3 -1
  16. package/dist/runtime/components/LayoutShapeSquare.vue.d.ts +3 -1
  17. package/dist/runtime/components/LayoutWindow.d.vue.ts +26 -12
  18. package/dist/runtime/components/LayoutWindow.vue +95 -84
  19. package/dist/runtime/components/LayoutWindow.vue.d.ts +26 -12
  20. package/dist/runtime/composables/useFrames.d.ts +15 -13
  21. package/dist/runtime/composables/useFrames.js +59 -39
  22. package/dist/runtime/demo/App.vue +116 -30
  23. package/dist/runtime/demo/DemoControls.d.vue.ts +4 -1
  24. package/dist/runtime/demo/DemoControls.vue +98 -4
  25. package/dist/runtime/demo/DemoControls.vue.d.ts +4 -1
  26. package/dist/runtime/drag/CloseAction.d.ts +26 -5
  27. package/dist/runtime/drag/CloseAction.js +87 -40
  28. package/dist/runtime/drag/DragActionHandler.d.ts +20 -8
  29. package/dist/runtime/drag/DragActionHandler.js +47 -12
  30. package/dist/runtime/drag/FrameDragAction.d.ts +45 -0
  31. package/dist/runtime/drag/FrameDragAction.js +143 -0
  32. package/dist/runtime/drag/SplitAction.d.ts +32 -11
  33. package/dist/runtime/drag/SplitAction.js +82 -24
  34. package/dist/runtime/drag/createDefaultHandlers.d.ts +9 -0
  35. package/dist/runtime/drag/createDefaultHandlers.js +10 -0
  36. package/dist/runtime/drag/defaultDragActions.d.ts +9 -0
  37. package/dist/runtime/drag/defaultDragActions.js +10 -0
  38. package/dist/runtime/drag/types.d.ts +82 -13
  39. package/dist/runtime/drag/types.js +1 -0
  40. package/dist/runtime/helpers/createZoneSideClipPath.d.ts +12 -0
  41. package/dist/runtime/helpers/createZoneSideClipPath.js +17 -0
  42. package/dist/runtime/helpers/doEdgesOverlap.d.ts +3 -1
  43. package/dist/runtime/helpers/doEdgesOverlap.js +5 -5
  44. package/dist/runtime/helpers/getDockBoundaries.d.ts +19 -0
  45. package/dist/runtime/helpers/getDockBoundaries.js +14 -0
  46. package/dist/runtime/helpers/getEdgeLength.d.ts +2 -0
  47. package/dist/runtime/helpers/getEdgeLength.js +5 -0
  48. package/dist/runtime/helpers/getIntersections.js +2 -2
  49. package/dist/runtime/helpers/getIntersectionsCss.js +2 -2
  50. package/dist/runtime/helpers/getMoveEdgeInfo.js +2 -2
  51. package/dist/runtime/helpers/getResizeLimit.js +2 -2
  52. package/dist/runtime/helpers/getShapeSquareCss.js +2 -2
  53. package/dist/runtime/helpers/getVisualEdgeCss.js +2 -2
  54. package/dist/runtime/helpers/getVisualEdges.d.ts +1 -1
  55. package/dist/runtime/helpers/getVisualEdges.js +4 -3
  56. package/dist/runtime/helpers/index.d.ts +4 -0
  57. package/dist/runtime/helpers/index.js +4 -0
  58. package/dist/runtime/helpers/isEdgeEqual.js +2 -4
  59. package/dist/runtime/helpers/isWindowEdge.js +2 -2
  60. package/dist/runtime/helpers/isWindowEdgePoint.js +2 -2
  61. package/dist/runtime/helpers/moveEdge.js +2 -2
  62. package/dist/runtime/helpers/numberToScaledPercent.d.ts +1 -1
  63. package/dist/runtime/helpers/numberToScaledPercent.js +2 -2
  64. package/dist/runtime/helpers/numberToScaledSize.js +2 -2
  65. package/dist/runtime/helpers/rotateFrames.d.ts +7 -0
  66. package/dist/runtime/helpers/rotateFrames.js +36 -0
  67. package/dist/runtime/helpers/scaledPointToPx.d.ts +13 -0
  68. package/dist/runtime/helpers/scaledPointToPx.js +7 -0
  69. package/dist/runtime/helpers/toWindowCoord.js +2 -2
  70. package/dist/runtime/layout/applyFrameChanges.d.ts +10 -0
  71. package/dist/runtime/layout/applyFrameChanges.js +29 -0
  72. package/dist/runtime/layout/createSplitDecoFromDrag.d.ts +6 -1
  73. package/dist/runtime/layout/createSplitDecoFromDrag.js +4 -4
  74. package/dist/runtime/layout/createSplitDecoShapes.d.ts +7 -0
  75. package/dist/runtime/layout/{createSplitDecoEdge.js → createSplitDecoShapes.js} +6 -3
  76. package/dist/runtime/layout/debugFrame.js +2 -1
  77. package/dist/runtime/layout/findSafeSplitEdge.js +2 -2
  78. package/dist/runtime/layout/frameCreate.js +2 -2
  79. package/dist/runtime/layout/getCloseFrameInfo.d.ts +7 -6
  80. package/dist/runtime/layout/getCloseFrameInfo.js +10 -3
  81. package/dist/runtime/layout/getDragZones.d.ts +8 -0
  82. package/dist/runtime/layout/getDragZones.js +32 -0
  83. package/dist/runtime/layout/getFillEmptySpaceInfo.d.ts +65 -0
  84. package/dist/runtime/layout/getFillEmptySpaceInfo.js +69 -0
  85. package/dist/runtime/layout/getFrameCollapseInfo.d.ts +13 -0
  86. package/dist/runtime/layout/getFrameCollapseInfo.js +93 -0
  87. package/dist/runtime/layout/getFrameDockInfo.d.ts +9 -0
  88. package/dist/runtime/layout/getFrameDockInfo.js +82 -0
  89. package/dist/runtime/layout/getFrameDragZones.d.ts +16 -0
  90. package/dist/runtime/layout/getFrameDragZones.js +74 -0
  91. package/dist/runtime/layout/getFrameRearrangeInfo.d.ts +139 -0
  92. package/dist/runtime/layout/getFrameRearrangeInfo.js +87 -0
  93. package/dist/runtime/layout/getFrameSplitInfo.d.ts +7 -5
  94. package/dist/runtime/layout/getFrameSplitInfo.js +10 -3
  95. package/dist/runtime/layout/getFrameSwapInfo.d.ts +9 -0
  96. package/dist/runtime/layout/getFrameSwapInfo.js +27 -0
  97. package/dist/runtime/layout/getFrameTo.js +2 -2
  98. package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +12 -0
  99. package/dist/runtime/layout/getFrameUncollapseInfo.js +88 -0
  100. package/dist/runtime/layout/getFrameUndockInfo.d.ts +13 -0
  101. package/dist/runtime/layout/getFrameUndockInfo.js +51 -0
  102. package/dist/runtime/layout/getFramesRedistributeInfo.d.ts +29 -0
  103. package/dist/runtime/layout/getFramesRedistributeInfo.js +53 -0
  104. package/dist/runtime/layout/getWindowDragZones.d.ts +6 -0
  105. package/dist/runtime/layout/getWindowDragZones.js +49 -0
  106. package/dist/runtime/layout/index.d.ts +14 -5
  107. package/dist/runtime/layout/index.js +14 -5
  108. package/dist/runtime/layout/isPointInRect.d.ts +7 -0
  109. package/dist/runtime/layout/{isPointInFrame.js → isPointInRect.js} +1 -1
  110. package/dist/runtime/layout/resizeFrame.js +2 -2
  111. package/dist/runtime/settings.d.ts +41 -16
  112. package/dist/runtime/settings.js +95 -53
  113. package/dist/runtime/types/index.d.ts +324 -54
  114. package/dist/runtime/types/index.js +54 -20
  115. package/package.json +28 -29
  116. package/src/runtime/components/FrameDragHandle.vue +30 -0
  117. package/src/runtime/components/LayoutDecos.vue +12 -36
  118. package/src/runtime/components/LayoutEdges.vue +27 -23
  119. package/src/runtime/components/LayoutFrame.vue +6 -5
  120. package/src/runtime/components/LayoutShapeSquare.vue +9 -3
  121. package/src/runtime/components/LayoutWindow.vue +110 -101
  122. package/src/runtime/composables/useFrames.ts +80 -50
  123. package/src/runtime/demo/App.vue +126 -36
  124. package/src/runtime/demo/DemoControls.vue +115 -6
  125. package/src/runtime/drag/CloseAction.ts +106 -44
  126. package/src/runtime/drag/DragActionHandler.ts +71 -20
  127. package/src/runtime/drag/FrameDragAction.ts +202 -0
  128. package/src/runtime/drag/SplitAction.ts +106 -34
  129. package/src/runtime/drag/createDefaultHandlers.ts +19 -0
  130. package/src/runtime/drag/defaultDragActions.ts +19 -0
  131. package/src/runtime/drag/types.ts +90 -20
  132. package/src/runtime/helpers/createZoneSideClipPath.ts +41 -0
  133. package/src/runtime/helpers/doEdgesOverlap.ts +11 -5
  134. package/src/runtime/helpers/getDockBoundaries.ts +36 -0
  135. package/src/runtime/helpers/getEdgeLength.ts +10 -0
  136. package/src/runtime/helpers/getIntersections.ts +2 -2
  137. package/src/runtime/helpers/getIntersectionsCss.ts +2 -2
  138. package/src/runtime/helpers/getMoveEdgeInfo.ts +2 -2
  139. package/src/runtime/helpers/getResizeLimit.ts +2 -2
  140. package/src/runtime/helpers/getShapeSquareCss.ts +2 -2
  141. package/src/runtime/helpers/getVisualEdgeCss.ts +2 -2
  142. package/src/runtime/helpers/getVisualEdges.ts +5 -4
  143. package/src/runtime/helpers/index.ts +4 -0
  144. package/src/runtime/helpers/isEdgeEqual.ts +2 -4
  145. package/src/runtime/helpers/isWindowEdge.ts +2 -2
  146. package/src/runtime/helpers/isWindowEdgePoint.ts +2 -2
  147. package/src/runtime/helpers/moveEdge.ts +2 -2
  148. package/src/runtime/helpers/numberToScaledPercent.ts +3 -3
  149. package/src/runtime/helpers/numberToScaledSize.ts +2 -2
  150. package/src/runtime/helpers/rotateFrames.ts +45 -0
  151. package/src/runtime/helpers/scaledPointToPx.ts +13 -0
  152. package/src/runtime/helpers/toWindowCoord.ts +2 -2
  153. package/src/runtime/layout/applyFrameChanges.ts +39 -0
  154. package/src/runtime/layout/createSplitDecoFromDrag.ts +12 -6
  155. package/src/runtime/layout/{createSplitDecoEdge.ts → createSplitDecoShapes.ts} +17 -7
  156. package/src/runtime/layout/debugFrame.ts +1 -1
  157. package/src/runtime/layout/findSafeSplitEdge.ts +3 -3
  158. package/src/runtime/layout/frameCreate.ts +2 -2
  159. package/src/runtime/layout/getCloseFrameInfo.ts +21 -8
  160. package/src/runtime/layout/getDragZones.ts +48 -0
  161. package/src/runtime/layout/getFillEmptySpaceInfo.ts +177 -0
  162. package/src/runtime/layout/getFrameCollapseInfo.ts +164 -0
  163. package/src/runtime/layout/getFrameDockInfo.ts +126 -0
  164. package/src/runtime/layout/getFrameDragZones.ts +100 -0
  165. package/src/runtime/layout/getFrameRearrangeInfo.ts +261 -0
  166. package/src/runtime/layout/getFrameSplitInfo.ts +21 -8
  167. package/src/runtime/layout/getFrameSwapInfo.ts +45 -0
  168. package/src/runtime/layout/getFrameTo.ts +2 -2
  169. package/src/runtime/layout/getFrameUncollapseInfo.ts +160 -0
  170. package/src/runtime/layout/getFrameUndockInfo.ts +97 -0
  171. package/src/runtime/layout/getFramesRedistributeInfo.ts +98 -0
  172. package/src/runtime/layout/getWindowDragZones.ts +59 -0
  173. package/src/runtime/layout/index.ts +14 -5
  174. package/src/runtime/layout/isPointInRect.ts +7 -0
  175. package/src/runtime/layout/resizeFrame.ts +2 -2
  176. package/src/runtime/settings.ts +69 -49
  177. package/src/runtime/types/index.ts +143 -28
  178. package/dist/runtime/layout/closeFrame.d.ts +0 -5
  179. package/dist/runtime/layout/closeFrame.js +0 -13
  180. package/dist/runtime/layout/closeFrames.d.ts +0 -2
  181. package/dist/runtime/layout/closeFrames.js +0 -8
  182. package/dist/runtime/layout/createSplitDecoEdge.d.ts +0 -2
  183. package/dist/runtime/layout/frameSplit.d.ts +0 -16
  184. package/dist/runtime/layout/frameSplit.js +0 -9
  185. package/dist/runtime/layout/isPointInFrame.d.ts +0 -2
  186. package/src/runtime/layout/closeFrame.ts +0 -33
  187. package/src/runtime/layout/closeFrames.ts +0 -14
  188. package/src/runtime/layout/frameSplit.ts +0 -31
  189. package/src/runtime/layout/isPointInFrame.ts +0 -7
@@ -1,6 +1,8 @@
1
- import type { RecordFromArray } from "@alanscodelog/utils"
1
+ import { get, type RecordFromArray } from "@alanscodelog/utils"
2
2
 
3
- import type { DragChangeHandler, DragState, IDragAction } from "./types.js"
3
+ import type { DragChangeHandler, DragChangeResult, DragState, IDragAction } from "./types.js"
4
+
5
+ import type { LayoutShape } from "../types/index.js"
4
6
 
5
7
  /**
6
8
  * Handles the lifecycle of a drag actions {@link IDragAction} and provides additional hooks.
@@ -15,11 +17,15 @@ export class DragActionHandler<
15
17
 
16
18
  actions: TDragActions
17
19
 
18
- eventCanceller: (() => void) | undefined = undefined
20
+ eventCanceller: ((e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void) | undefined = undefined
21
+
22
+ boundCancel: (e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void
19
23
 
20
- boundCancel: () => void
24
+ defaultOnDragChange: DragChangeHandler = (type, _e, state, _forceRecalculateEdges, _cancel) => ({
25
+ updateEdges: type === "move" ? !state.isDraggingFromWindowEdge : true,
26
+ shapes: []
27
+ })
21
28
 
22
- defaultOnDragChange: DragChangeHandler
23
29
  hooks: {
24
30
  /** Called while dragging during dragChange events. You can use this to update the dragging edges. */
25
31
  onRecalculate?: () => void
@@ -35,17 +41,23 @@ export class DragActionHandler<
35
41
  onEnd?: (context: { cancelled: boolean, applied: boolean }) => void
36
42
  }
37
43
 
44
+ /** All action shapes merged into a single array. If using vue you can set this to a reactive array for reactivity. */
45
+ shapes: LayoutShape[] = []
46
+
47
+ /** All hint/error text from all actions, updated on every onDragChange. If using vue you can set this to a reactive object for reactivity. */
48
+ textHints: { actions: string[], errors: string[] } = { actions: [], errors: [] }
49
+
38
50
  constructor(
51
+ actions: TRawDragActions,
52
+ hooks: DragActionHandler<TRawDragActions, TDragActions>["hooks"] = {},
39
53
  /**
40
54
  * Default onDragChange handler for when no action can handle the request.
41
55
  *
42
56
  * Should return true to allow the edges to be moved, or false to prevent it.
43
57
  */
44
- defaultOnDragChange: DragChangeHandler,
45
- actions: TRawDragActions,
46
- hooks: DragActionHandler<TRawDragActions, TDragActions>["hooks"] = {}
58
+ defaultOnDragChange?: DragChangeHandler
47
59
  ) {
48
- this.defaultOnDragChange = defaultOnDragChange
60
+ if (defaultOnDragChange) this.defaultOnDragChange = defaultOnDragChange
49
61
  this.hooks = hooks
50
62
  this.actions = {} as any
51
63
  for (const action of actions) {
@@ -65,7 +77,7 @@ export class DragActionHandler<
65
77
  let cancelled = false
66
78
  this.hooks.onEvent?.(e, () => {
67
79
  cancelled = true
68
- this.cancel()
80
+ this.cancel(e, state)
69
81
  })
70
82
  if (cancelled) return
71
83
 
@@ -87,7 +99,7 @@ export class DragActionHandler<
87
99
 
88
100
  if (oldActiveAction !== this.activeAction) {
89
101
  if (oldActiveAction) {
90
- this.actions[oldActiveAction]!.cancel()
102
+ this.actions[oldActiveAction]!.cancel(e, state)
91
103
  }
92
104
  this.hooks.onRequestChange?.(this.activeAction)
93
105
  }
@@ -104,22 +116,38 @@ export class DragActionHandler<
104
116
  e: T extends "end" ? PointerEvent | undefined : PointerEvent,
105
117
  state: DragState,
106
118
  forceRecalculateEdges: () => void,
107
- cancel?: () => void
108
- ): boolean | undefined {
119
+ cancel: (e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void
120
+ ): DragChangeResult {
109
121
  if (type === "start") {
110
122
  this.eventCanceller = cancel
111
- state.isDragging = true
112
123
  this.eventHandler(e!, state, forceRecalculateEdges)
113
124
  }
114
125
  if (type === "end") {
115
- state.isDragging = false
116
126
  this.activeAction = undefined
117
127
  }
118
128
 
119
129
  if (this.activeAction) {
120
- return this.actions[this.activeAction]!.onDragChange(type, e, state, forceRecalculateEdges, this.boundCancel)
130
+ const res = this.actions[this.activeAction]!.onDragChange(type, e, state, forceRecalculateEdges, this.boundCancel)
131
+ // in case it's a vue reactive array
132
+ this.shapes.splice(0, this.shapes.length, ...res.shapes)
133
+ this.setTextHints(type)
134
+ return res
135
+ }
136
+ this.setTextHints(type)
137
+ const res = this.defaultOnDragChange(type, e, state, forceRecalculateEdges, this.boundCancel)
138
+ this.shapes.splice(0, this.shapes.length, ...res.shapes)
139
+ return res
140
+ }
141
+
142
+ setTextHints(type: "start" | "move" | "end") {
143
+ // again in case it's a vue reactive object
144
+ this.textHints.actions = []
145
+ this.textHints.errors = []
146
+ for (const action of Object.values<IDragAction>(this.actions)) {
147
+ const res = action.getTextHints?.(type)
148
+ this.textHints.actions.push(...(res?.actions ?? []))
149
+ this.textHints.errors.push(...(res?.errors ?? []))
121
150
  }
122
- return this.defaultOnDragChange(type, e, state, forceRecalculateEdges, this.boundCancel)
123
151
  }
124
152
 
125
153
  onDragApply(
@@ -135,12 +163,35 @@ export class DragActionHandler<
135
163
  return true
136
164
  }
137
165
 
138
- cancel(): void {
166
+ cancel(e: PointerEvent | KeyboardEvent | undefined, state: DragState): void {
139
167
  if (this.activeAction) {
140
- this.actions[this.activeAction].cancel()
168
+ this.actions[this.activeAction].cancel(e, state)
141
169
  }
142
170
  this.activeAction = undefined
143
- this.eventCanceller?.()
171
+ this.eventCanceller?.(e, state)
144
172
  this.hooks.onEnd?.({ cancelled: true, applied: false })
145
173
  }
174
+
175
+ static debugState(
176
+ pluginName: string,
177
+ type: "before" | "after" | string,
178
+ state: DragState,
179
+ pluginState: Record<string, any> = {},
180
+ /** Object key to filter the state by, e.g. state.win.frames. If boolean is ignored. The idea is you pass this.debug and users can set this.debug to a string to filter. */
181
+ key?: string | boolean
182
+ ): void {
183
+ let res = { state, pluginState }
184
+ let pickedRes: any = {}
185
+ if (typeof key === "string" && key !== "") {
186
+ pickedRes = {} as any
187
+ const paths = key.split(",").map(_ => _.trim()).filter(_ => _)
188
+ for (const path of paths) {
189
+ pickedRes[path] = get(res, path.split("."))
190
+ }
191
+ res = pickedRes as any
192
+ }
193
+ const resString = JSON.stringify(res, null, 2)
194
+ // eslint-disable-next-line no-console
195
+ console.log(`[Drag Action Debug - ${pluginName}]:`, type, resString)
196
+ }
146
197
  }
@@ -0,0 +1,202 @@
1
+ import { DragActionHandler } from "./DragActionHandler.js"
2
+
3
+ import { createZoneSideClipPath } from "../helpers/createZoneSideClipPath.js"
4
+ import { applyFrameChanges } from "../layout/applyFrameChanges.js"
5
+ import { getDragZones } from "../layout/getDragZones.js"
6
+ import { getFrameDockInfo } from "../layout/getFrameDockInfo.js"
7
+ import { getFrameRearrangeInfo } from "../layout/getFrameRearrangeInfo.js"
8
+ import { getFrameSwapInfo } from "../layout/getFrameSwapInfo.js"
9
+ import { settings } from "../settings.js"
10
+ import type { ActionDragChangeResult, DragState, DragZone, FrameDragDeco, IDragAction, LayoutChange } from "../types/index.js"
11
+ import type { KnownError } from "../utils/KnownError.js"
12
+
13
+
14
+ export class FrameDragAction implements IDragAction {
15
+ name = "frameDrag" as const
16
+ state: {
17
+ lastReturn?: LayoutChange<"split" | "swap" | "rearrange" | "dock"> | KnownError
18
+ } = {
19
+ lastReturn: undefined
20
+ }
21
+
22
+ debug: boolean | string = false
23
+ textHints: { actions: string[], errors: string[] } = { actions: [], errors: [] }
24
+
25
+ handleEvent: (e: PointerEvent | KeyboardEvent, state: DragState) => boolean
26
+ = (_e: PointerEvent | KeyboardEvent, state: DragState) => state.isDragging === "frame"
27
+
28
+ dragHints: {
29
+ actions: { split: string, swap: string, rearrange: string, dock: string }
30
+ transformError: (e: KnownError) => string
31
+ } = {
32
+ actions: {
33
+ split: "Drop to Split the current frame.",
34
+ swap: "Drop to Swap the frames.",
35
+ rearrange: "Drop to Move the frame.",
36
+ dock: "Drop to Dock the frame."
37
+ },
38
+ transformError: e => e.message
39
+ }
40
+
41
+ modifyDecos: (shapes: FrameDragDeco[]) => void = () => { }
42
+ hooks: {
43
+ onStart?: () => void
44
+ onCancel?: () => void
45
+ onError?: (e: KnownError) => void
46
+ } = {}
47
+
48
+ constructor(
49
+ handleEvent?: FrameDragAction["handleEvent"],
50
+ /** Modify the created decos before they are rendered. */
51
+ modifyDecos?: FrameDragAction["modifyDecos"],
52
+ hooks: FrameDragAction["hooks"] = {},
53
+ config?: {
54
+ debug?: boolean | string
55
+ }
56
+ ) {
57
+ if (handleEvent !== undefined) this.handleEvent = handleEvent
58
+ if (modifyDecos !== undefined) this.modifyDecos = modifyDecos
59
+ this.hooks = hooks
60
+ if (config?.debug) this.debug = true
61
+
62
+ this.reset()
63
+ }
64
+
65
+ reset(): void {
66
+ this.state = {
67
+ lastReturn: undefined
68
+ }
69
+ }
70
+
71
+ setTextHints(result: LayoutChange<"split" | "swap" | "rearrange" | "dock"> | KnownError | undefined): void {
72
+ if (result === undefined || !result.info) {
73
+ this.textHints.actions = []
74
+ this.textHints.errors = []
75
+ } else if (result instanceof Error) {
76
+ this.textHints.actions = []
77
+ this.textHints.errors = [this.dragHints.transformError(result)]
78
+ } else {
79
+ const text = this.dragHints.actions[result.info]
80
+ this.textHints.actions = [text]
81
+ this.textHints.errors = []
82
+ }
83
+ }
84
+
85
+ getTextHints(type: "start" | "move" | "end"): {
86
+ actions: string[]
87
+ errors: string[]
88
+ } {
89
+ if (type === "end") { this.setTextHints(undefined) }
90
+ return this.textHints
91
+ }
92
+
93
+
94
+ getDecos(
95
+ matchedZone: DragZone | undefined,
96
+ state: DragState,
97
+ result: LayoutChange<"split" | "swap" | "rearrange" | "dock"> | KnownError | undefined
98
+ ): FrameDragDeco[] {
99
+ let decos: FrameDragDeco[] = []
100
+ const isError = result instanceof Error
101
+ const frame = state.dragHoveredFrame
102
+
103
+ if (!matchedZone || !frame) {
104
+ decos = []
105
+ this.modifyDecos(decos)
106
+ return decos
107
+ }
108
+
109
+ const clipPath = createZoneSideClipPath(matchedZone, settings.zoneSizes)
110
+
111
+ const classes = isError
112
+ ? `deco-frame-drag deco-frame-drag-error deco-frame-drag-${matchedZone.type}-${matchedZone.side} bg-red-500/50`
113
+ : `deco-frame-drag deco-frame-drag-${matchedZone.type}-${matchedZone.side} bg-blue-500/50`
114
+
115
+ decos = [{
116
+ id: frame.id,
117
+ type: "drop",
118
+ position: matchedZone.side ?? "center",
119
+ shapes: [
120
+ {
121
+ type: "square",
122
+ data: matchedZone,
123
+ attrs: clipPath
124
+ ? { class: classes, style: `clip-path:${clipPath}` }
125
+ : { class: classes }
126
+ }
127
+ ]
128
+ }]
129
+
130
+ this.modifyDecos(decos)
131
+ return decos
132
+ }
133
+
134
+ canHandleRequest(e: PointerEvent | KeyboardEvent, state: DragState): boolean {
135
+ const res = this.handleEvent(e, state)
136
+ this.setTextHints(undefined)
137
+ if (res) {
138
+ this.hooks.onStart?.()
139
+ return true
140
+ }
141
+ this.reset()
142
+ return false
143
+ }
144
+
145
+ onDragChange<T extends "start" | "move" | "end">(
146
+ _type: T,
147
+ _e: PointerEvent | undefined,
148
+ state: DragState
149
+ ): ActionDragChangeResult {
150
+ const { win, draggingFrameId, dragHoveredFrame } = state
151
+ const matchedZone = getDragZones(state, settings.zoneSizes)
152
+
153
+ if (!matchedZone) {
154
+ this.state.lastReturn = undefined
155
+ } else if (dragHoveredFrame && matchedZone.type === "frame" && matchedZone.side) {
156
+ if (matchedZone.side !== "center") {
157
+ this.state.lastReturn = getFrameRearrangeInfo(win, draggingFrameId!, dragHoveredFrame.id!, matchedZone.side)
158
+ } else {
159
+ this.state.lastReturn = getFrameSwapInfo(win, draggingFrameId!, dragHoveredFrame.id!)
160
+ // for the hints
161
+ if (!(this.state.lastReturn instanceof Error)) this.state.lastReturn.info = "swap"
162
+ }
163
+ } else if (matchedZone.type === "window") {
164
+ this.state.lastReturn = getFrameDockInfo(win, draggingFrameId!, matchedZone.side)
165
+ // for the hints
166
+ if (!(this.state.lastReturn instanceof Error)) this.state.lastReturn.info = "dock"
167
+ }
168
+
169
+ this.setTextHints(this.state.lastReturn)
170
+ const decos = this.getDecos(matchedZone, state, this.state.lastReturn)
171
+
172
+ return {
173
+ shapes: decos.flatMap(_ => _.shapes),
174
+ updateEdges: false,
175
+ showDragging: false
176
+ }
177
+ }
178
+
179
+ onDragApply(state: DragState): boolean {
180
+ const result = this.state.lastReturn
181
+ if (!result || !state.dragHoveredFrame || !state.draggingFrameId) {
182
+ this.reset()
183
+ return true
184
+ }
185
+
186
+
187
+ if (result instanceof Error) {
188
+ this.hooks.onError?.(result)
189
+ } else {
190
+ if (this.debug) { DragActionHandler.debugState(this.name, "before", state, this.state, this.debug) }
191
+ applyFrameChanges(state.win, result)
192
+ if (this.debug) { DragActionHandler.debugState(this.name, "after", state, this.state, this.debug) }
193
+ }
194
+
195
+ this.reset()
196
+ return true
197
+ }
198
+
199
+ cancel(): void {
200
+ this.reset()
201
+ }
202
+ }
@@ -1,29 +1,45 @@
1
- import type { DragState, IDragAction } from "./types.js"
1
+ import { DragActionHandler } from "./DragActionHandler.js"
2
+ import type { ActionDragChangeResult, DragState, IDragAction } from "./types.js"
2
3
 
3
4
  import { getEdgeOrientation } from "../helpers/getEdgeOrientation.js"
4
5
  import { oppositeSide } from "../helpers/oppositeSide.js"
6
+ import { applyFrameChanges } from "../layout/applyFrameChanges.js"
5
7
  import { createSplitDecoFromDrag } from "../layout/createSplitDecoFromDrag.js"
6
- import { frameSplit } from "../layout/frameSplit.js"
7
8
  import { getFrameSplitInfo } from "../layout/getFrameSplitInfo.js"
8
- import type { SplitDeco } from "../types/index.js"
9
+ import type { LayoutShape, Point, SplitDeco } from "../types/index.js"
9
10
  import type { KnownError } from "../utils/KnownError.js"
10
11
 
11
12
  export type SplitInfo = Exclude<ReturnType<typeof getFrameSplitInfo>, KnownError>
13
+
12
14
  export class SplitAction implements IDragAction {
13
15
  name = "split" as const
14
16
 
15
17
  state: {
16
- allowed: false
17
- res: SplitInfo | undefined
18
- cacheKey: string | undefined
19
- } | {
20
18
  allowed: true
21
19
  res: SplitInfo
22
- cacheKey: string | undefined
20
+ lastPoint: Point | undefined
21
+ lastReturn: undefined
22
+ } | {
23
+ allowed: false
24
+ res: SplitInfo | undefined
25
+ lastPoint: Point | undefined
26
+ lastReturn: ActionDragChangeResult | undefined
23
27
  } = {} as any // this is initialized by `this.reset()`
24
28
 
29
+ debug: boolean | string = false
30
+ textHints: { actions: string[], errors: string[] } = { actions: [], errors: [] }
31
+ splitHints: {
32
+ action: string
33
+ transformError: (e: KnownError) => string
34
+ } = {
35
+ action: "Hold Alt to Split",
36
+ transformError: e => e.message
37
+ }
38
+
25
39
  handleEvent: (e: PointerEvent | KeyboardEvent, state: DragState) => boolean
26
- updateSplitDecos: (decos: SplitDeco[]) => void
40
+ = (e: PointerEvent | KeyboardEvent, state: DragState) => e.altKey || state.isDraggingFromWindowEdge
41
+
42
+ modifyDecos: (shapes: SplitDeco[]) => void = () => { }
27
43
  hooks: {
28
44
  onStart?: () => void
29
45
  onCancel?: () => void
@@ -31,26 +47,37 @@ export class SplitAction implements IDragAction {
31
47
  }
32
48
 
33
49
  constructor(
34
- handleEvent: SplitAction["handleEvent"],
35
- updateSplitDecos: SplitAction["updateSplitDecos"],
36
- hooks: SplitAction["hooks"] = {}
50
+ handleEvent?: SplitAction["handleEvent"],
51
+ /** Modify the created decos before they are rendered. */
52
+ modifyDecos?: SplitAction["modifyDecos"],
53
+ hooks: SplitAction["hooks"] = {},
54
+ config?: {
55
+ debug?: boolean | string
56
+ splitHints?: Partial<SplitAction["splitHints"]>
57
+ }
37
58
  ) {
38
- this.handleEvent = handleEvent
39
- this.updateSplitDecos = updateSplitDecos
59
+ if (handleEvent !== undefined) this.handleEvent = handleEvent
60
+ if (modifyDecos !== undefined) this.modifyDecos = modifyDecos
61
+
40
62
  this.hooks = hooks
41
63
  this.reset()
64
+ if (config?.debug) this.debug = true
65
+ if (config?.splitHints?.action) this.splitHints.action = config.splitHints.action
66
+ if (config?.splitHints?.transformError) this.splitHints.transformError = config.splitHints.transformError
42
67
  }
43
68
 
44
69
  reset(): void {
45
70
  this.state = {
46
71
  allowed: false,
47
72
  res: undefined,
48
- cacheKey: undefined
73
+ lastPoint: undefined,
74
+ lastReturn: undefined
49
75
  }
50
- this.updateSplitDecos([])
76
+ this.modifyDecos([])
51
77
  }
52
78
 
53
- updateDecos(state: DragState): void {
79
+ getDecos(state: DragState): SplitDeco[] {
80
+ let decos: SplitDeco[] = []
54
81
  const {
55
82
  win,
56
83
  isDragging,
@@ -67,15 +94,16 @@ export class SplitAction implements IDragAction {
67
94
  dragDirections[oppositeOrientation]!,
68
95
  dragPoint!
69
96
  )
70
- this.updateSplitDecos([deco])
71
- } else {
72
- this.updateSplitDecos([])
97
+ decos = [deco]
73
98
  }
99
+ this.modifyDecos(decos)
100
+ return decos
74
101
  }
75
102
 
76
103
  canHandleRequest(e: PointerEvent | KeyboardEvent, state: DragState): boolean {
77
104
  const { draggingEdges } = state
78
105
  if (draggingEdges.length !== 1) return false
106
+ this.setTextHints(state.isDragging === "edge" ? true : undefined)
79
107
  if (this.handleEvent(e, state)) {
80
108
  this.hooks.onStart?.()
81
109
  return true
@@ -84,26 +112,52 @@ export class SplitAction implements IDragAction {
84
112
  return false
85
113
  }
86
114
 
115
+ setTextHints(result: true | SplitInfo | KnownError | undefined): void {
116
+ if (result === undefined) {
117
+ this.textHints.actions = []
118
+ this.textHints.errors = []
119
+ } else if (result instanceof Error) {
120
+ this.textHints.actions = []
121
+ this.textHints.errors = [this.splitHints.transformError(result)]
122
+ } else {
123
+ this.textHints.actions = [this.splitHints.action]
124
+ this.textHints.errors = []
125
+ }
126
+ }
127
+
128
+ getTextHints(type: "start" | "move" | "end"): {
129
+ actions: string[]
130
+ errors: string[]
131
+ } {
132
+ if (type === "end") { this.setTextHints(undefined) }
133
+ return this.textHints
134
+ }
135
+
87
136
  calculateSplitRequest(state: DragState): boolean {
88
137
  const {
89
138
  dragHoveredFrame,
90
139
  dragDirections,
91
140
  draggingEdges,
92
- dragPoint
141
+ dragPoint,
142
+ win
93
143
  } = state
94
144
  const oppositeOrientation = oppositeSide(getEdgeOrientation(draggingEdges[0]))
145
+ const originalDragHoveredFrame = win.frames[dragHoveredFrame!.id]
95
146
  const canSplit = getFrameSplitInfo(
96
- dragHoveredFrame!,
147
+ originalDragHoveredFrame!,
97
148
  dragDirections[oppositeOrientation]!,
98
149
  dragPoint!
99
150
  )
151
+ this.setTextHints(canSplit)
100
152
  if (!(canSplit instanceof Error)) {
101
153
  this.state.allowed = true
102
154
  this.state.res = canSplit
103
- this.state.cacheKey = dragHoveredFrame?.id
155
+ this.state.lastPoint = dragPoint ? { ...dragPoint } : undefined
104
156
  return true
105
157
  } else {
106
158
  this.hooks.onError?.(canSplit)
159
+ this.state.lastPoint = undefined
160
+ this.state.allowed = false
107
161
  return false
108
162
  }
109
163
  }
@@ -112,29 +166,47 @@ export class SplitAction implements IDragAction {
112
166
  _type: "start" | "end" | "move",
113
167
  _e: PointerEvent | undefined,
114
168
  state: DragState
115
- ): true {
169
+ ): ActionDragChangeResult {
116
170
  const { dragHoveredFrame } = state
117
171
  let ok = false
118
172
  if (dragHoveredFrame) {
119
- if (this.state.cacheKey === dragHoveredFrame.id) {
120
- this.updateDecos(state)
121
- return true
173
+ if (this.state.lastPoint?.x === state.dragPoint?.x && this.state.lastPoint?.y === state.dragPoint?.y && this.state.lastReturn) {
174
+ return this.state.lastReturn!
122
175
  }
123
176
  ok = this.calculateSplitRequest(state)
124
177
  }
125
- this.updateDecos(state)
178
+ const decos = this.getDecos(state)
179
+
126
180
  if (!ok) {
127
- this.state.allowed = false
181
+ if (dragHoveredFrame && dragHoveredFrame.docked) {
182
+ const errorDeco: LayoutShape = {
183
+ type: "square",
184
+ data: {
185
+ x: dragHoveredFrame.x,
186
+ y: dragHoveredFrame.y,
187
+ width: dragHoveredFrame.width,
188
+ height: dragHoveredFrame.height
189
+ },
190
+ attrs: { class: "deco-split-error bg-red-500/50" }
191
+ }
192
+ this.state.lastReturn = { updateEdges: true, shapes: [errorDeco], showDragging: false }
193
+ return this.state.lastReturn
194
+ }
128
195
  }
129
- return true
196
+ this.state.lastReturn = { updateEdges: false, shapes: decos.flatMap(_ => _.shapes), showDragging: false }
197
+ return this.state.lastReturn
130
198
  }
131
199
 
132
200
  onDragApply(state: DragState): boolean {
133
- if (this.state.res) {
201
+ if (this.state.res && state.dragHoveredFrame) {
134
202
  // this only caches once per frame hovered over
135
- // so the drag position is outdated
136
- this.calculateSplitRequest(state)
137
- frameSplit(state.win, this.state.res!)
203
+ // so the drag position is outdated, we must recalculate
204
+ const ok = this.calculateSplitRequest(state)
205
+ if (ok) {
206
+ if (this.debug) { DragActionHandler.debugState(this.name, "before", state, this.state, this.debug) }
207
+ applyFrameChanges(state.win, this.state.res!)
208
+ if (this.debug) { DragActionHandler.debugState(this.name, "after", state, this.state, this.debug) }
209
+ }
138
210
  }
139
211
  this.reset()
140
212
  return true
@@ -0,0 +1,19 @@
1
+ import { CloseAction } from "./CloseAction"
2
+ import { FrameDragAction } from "./FrameDragAction.js"
3
+ import { SplitAction } from "./SplitAction.js"
4
+ import type { IDragAction } from "./types.js"
5
+
6
+ /**
7
+ * Creates the default drag actions (Split, Close, FrameDrag).
8
+ */
9
+ export function createDefaultHandlers(config: {
10
+ debugSplit?: boolean
11
+ debugClose?: boolean
12
+ debugFrameDrag?: boolean
13
+ } = {}): IDragAction[] {
14
+ return [
15
+ new SplitAction(undefined, undefined, undefined, { debug: config.debugSplit }),
16
+ new CloseAction(undefined, undefined, undefined, { debug: config.debugClose }),
17
+ new FrameDragAction(undefined, undefined, undefined, { debug: config.debugFrameDrag })
18
+ ]
19
+ }
@@ -0,0 +1,19 @@
1
+ import { CloseAction } from "./CloseAction"
2
+ import { FrameDragAction } from "./FrameDragAction.js"
3
+ import { SplitAction } from "./SplitAction.js"
4
+ import type { IDragAction } from "./types.js"
5
+
6
+ /**
7
+ * Creates the default drag actions (Split, Close, FrameDrag).
8
+ */
9
+ export function createDefaultHandlers(config: {
10
+ debugSplit?: boolean
11
+ debugClose?: boolean
12
+ debugFrameDrag?: boolean
13
+ }): IDragAction[] {
14
+ return [
15
+ new SplitAction(undefined, undefined, undefined, { debug: config.debugSplit }),
16
+ new CloseAction(undefined, undefined, undefined, { debug: config.debugClose }),
17
+ new FrameDragAction(undefined, undefined, undefined, { debug: config.debugFrameDrag })
18
+ ]
19
+ }