@witchcraft/layout 0.4.3 → 0.5.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 (126) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/FrameDragHandle.vue +3 -3
  3. package/dist/runtime/components/LayoutDragEdgeGrabbed.vue +3 -3
  4. package/dist/runtime/components/LayoutDragEdgeHandle.d.vue.ts +2 -2
  5. package/dist/runtime/components/LayoutDragEdgeHandle.vue +2 -2
  6. package/dist/runtime/components/LayoutDragEdgeHandle.vue.d.ts +2 -2
  7. package/dist/runtime/components/LayoutDragEdges.vue +5 -5
  8. package/dist/runtime/components/LayoutEdgesActiveFrame.vue +2 -2
  9. package/dist/runtime/components/LayoutFrame.vue +2 -2
  10. package/dist/runtime/components/LayoutIntersectionHandle.d.vue.ts +2 -2
  11. package/dist/runtime/components/LayoutIntersectionHandle.vue +2 -2
  12. package/dist/runtime/components/LayoutIntersectionHandle.vue.d.ts +2 -2
  13. package/dist/runtime/components/LayoutIntersections.vue +4 -4
  14. package/dist/runtime/components/LayoutWindow.d.vue.ts +54 -45
  15. package/dist/runtime/components/LayoutWindow.vue +29 -29
  16. package/dist/runtime/components/LayoutWindow.vue.d.ts +54 -45
  17. package/dist/runtime/composables/useFrames.d.ts +142 -129
  18. package/dist/runtime/composables/useFrames.js +122 -94
  19. package/dist/runtime/demo/App.vue +16 -16
  20. package/dist/runtime/demo/DemoControls.d.vue.ts +2 -2
  21. package/dist/runtime/demo/DemoControls.vue +4 -4
  22. package/dist/runtime/demo/DemoControls.vue.d.ts +2 -2
  23. package/dist/runtime/demo/tailwind.css +1 -1
  24. package/dist/runtime/helpers/createZoneSideClipPath.d.ts +2 -2
  25. package/dist/runtime/helpers/findEdgesTouchingWindow.d.ts +7 -0
  26. package/dist/runtime/helpers/findEdgesTouchingWindow.js +20 -0
  27. package/dist/runtime/helpers/{findFrameDraggableEdges.d.ts → findFrameMoveableEdges.d.ts} +2 -2
  28. package/dist/runtime/helpers/{findFrameDraggableEdges.js → findFrameMoveableEdges.js} +3 -3
  29. package/dist/runtime/helpers/{findDraggableEdge.d.ts → findMoveableEdge.d.ts} +1 -1
  30. package/dist/runtime/helpers/{findDraggableEdge.js → findMoveableEdge.js} +1 -1
  31. package/dist/runtime/helpers/index.d.ts +10 -3
  32. package/dist/runtime/helpers/index.js +10 -3
  33. package/dist/runtime/layout/createSplitDecoFromDrag.d.ts +1 -1
  34. package/dist/runtime/layout/createSplitDecoFromDrag.js +2 -2
  35. package/dist/runtime/layout/debugFrame.js +5 -1
  36. package/dist/runtime/layout/findSafeSplitEdge.d.ts +1 -1
  37. package/dist/runtime/layout/findSafeSplitEdge.js +2 -2
  38. package/dist/runtime/layout/getCloseFrameInfo.js +4 -4
  39. package/dist/runtime/layout/getFrameCollapseInfo.d.ts +1 -1
  40. package/dist/runtime/layout/getFrameCollapseInfo.js +9 -29
  41. package/dist/runtime/layout/getFrameDockInfo.d.ts +1 -1
  42. package/dist/runtime/layout/getFrameDockInfo.js +61 -44
  43. package/dist/runtime/layout/getFrameExpandInfo.d.ts +11 -0
  44. package/dist/runtime/layout/getFrameExpandInfo.js +49 -0
  45. package/dist/runtime/layout/getFrameRearrangeInfo.d.ts +2 -2
  46. package/dist/runtime/layout/getFrameRearrangeInfo.js +8 -8
  47. package/dist/runtime/layout/getFrameShrinkInfo.d.ts +14 -0
  48. package/dist/runtime/layout/getFrameShrinkInfo.js +48 -0
  49. package/dist/runtime/layout/getFrameSplitInfo.d.ts +1 -1
  50. package/dist/runtime/layout/getFrameSplitInfo.js +2 -2
  51. package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +1 -1
  52. package/dist/runtime/layout/getFrameUncollapseInfo.js +6 -20
  53. package/dist/runtime/layout/{getFrameDragZones.d.ts → getFrameZones.d.ts} +3 -3
  54. package/dist/runtime/layout/{getFrameDragZones.js → getFrameZones.js} +1 -1
  55. package/dist/runtime/layout/getUpdateWindowSizeInfo.js +10 -12
  56. package/dist/runtime/layout/{getWindowDragZones.d.ts → getWindowZones.d.ts} +1 -1
  57. package/dist/runtime/layout/{getWindowDragZones.js → getWindowZones.js} +1 -1
  58. package/dist/runtime/layout/getZones.d.ts +8 -0
  59. package/dist/runtime/layout/getZones.js +32 -0
  60. package/dist/runtime/layout/index.d.ts +6 -3
  61. package/dist/runtime/layout/index.js +6 -3
  62. package/dist/runtime/{drag/DragActionHandler.d.ts → move/ActionHandler.d.ts} +18 -18
  63. package/dist/runtime/{drag/DragActionHandler.js → move/ActionHandler.js} +19 -19
  64. package/dist/runtime/{drag → move}/CloseAction.d.ts +8 -8
  65. package/dist/runtime/{drag → move}/CloseAction.js +29 -26
  66. package/dist/runtime/{drag → move}/FrameDragAction.d.ts +8 -8
  67. package/dist/runtime/{drag → move}/FrameDragAction.js +23 -21
  68. package/dist/runtime/{drag/DragDirectionStore.d.ts → move/MoveDirectionStore.d.ts} +4 -4
  69. package/dist/runtime/{drag/DragDirectionStore.js → move/MoveDirectionStore.js} +9 -9
  70. package/dist/runtime/{drag → move}/SplitAction.d.ts +9 -9
  71. package/dist/runtime/{drag → move}/SplitAction.js +46 -43
  72. package/dist/runtime/{drag → move}/createDefaultHandlers.d.ts +2 -2
  73. package/dist/runtime/types/index.d.ts +242 -158
  74. package/dist/runtime/types/index.js +4 -3
  75. package/dist/runtime/types/vue.d.ts +25 -18
  76. package/dist/runtime/types/vue.js +1 -1
  77. package/package.json +3 -2
  78. package/src/runtime/components/FrameDragHandle.vue +3 -3
  79. package/src/runtime/components/LayoutDragEdgeGrabbed.vue +3 -3
  80. package/src/runtime/components/LayoutDragEdgeHandle.vue +4 -4
  81. package/src/runtime/components/LayoutDragEdges.vue +5 -5
  82. package/src/runtime/components/LayoutEdgesActiveFrame.vue +2 -2
  83. package/src/runtime/components/LayoutFrame.vue +2 -2
  84. package/src/runtime/components/LayoutIntersectionHandle.vue +4 -4
  85. package/src/runtime/components/LayoutIntersections.vue +4 -4
  86. package/src/runtime/components/LayoutWindow.vue +35 -35
  87. package/src/runtime/composables/useFrames.ts +141 -121
  88. package/src/runtime/demo/App.vue +18 -18
  89. package/src/runtime/demo/DemoControls.vue +5 -5
  90. package/src/runtime/demo/tailwind.css +4 -0
  91. package/src/runtime/helpers/createZoneSideClipPath.ts +2 -2
  92. package/src/runtime/helpers/findEdgesTouchingWindow.ts +30 -0
  93. package/src/runtime/helpers/{findFrameDraggableEdges.ts → findFrameMoveableEdges.ts} +4 -4
  94. package/src/runtime/helpers/{findDraggableEdge.ts → findMoveableEdge.ts} +1 -1
  95. package/src/runtime/helpers/index.ts +10 -3
  96. package/src/runtime/layout/createSplitDecoFromDrag.ts +2 -2
  97. package/src/runtime/layout/debugFrame.ts +6 -1
  98. package/src/runtime/layout/findSafeSplitEdge.ts +4 -4
  99. package/src/runtime/layout/getCloseFrameInfo.ts +4 -4
  100. package/src/runtime/layout/getFrameCollapseInfo.ts +12 -41
  101. package/src/runtime/layout/getFrameDockInfo.ts +77 -57
  102. package/src/runtime/layout/getFrameExpandInfo.ts +84 -0
  103. package/src/runtime/layout/getFrameRearrangeInfo.ts +10 -10
  104. package/src/runtime/layout/getFrameShrinkInfo.ts +87 -0
  105. package/src/runtime/layout/getFrameSplitInfo.ts +5 -5
  106. package/src/runtime/layout/getFrameUncollapseInfo.ts +8 -30
  107. package/src/runtime/layout/{getFrameDragZones.ts → getFrameZones.ts} +4 -4
  108. package/src/runtime/layout/getUpdateWindowSizeInfo.ts +16 -22
  109. package/src/runtime/layout/{getWindowDragZones.ts → getWindowZones.ts} +1 -1
  110. package/src/runtime/layout/getZones.ts +48 -0
  111. package/src/runtime/layout/index.ts +6 -3
  112. package/src/runtime/{drag/DragActionHandler.ts → move/ActionHandler.ts} +44 -42
  113. package/src/runtime/{drag → move}/CloseAction.ts +39 -36
  114. package/src/runtime/{drag → move}/FrameDragAction.ts +36 -35
  115. package/src/runtime/{drag/DragDirectionStore.ts → move/MoveDirectionStore.ts} +10 -10
  116. package/src/runtime/{drag → move}/SplitAction.ts +56 -53
  117. package/src/runtime/{drag/defaultDragActions.ts → move/createDefaultHandlers.ts} +2 -2
  118. package/src/runtime/types/index.ts +104 -55
  119. package/src/runtime/types/vue.ts +18 -16
  120. package/dist/runtime/drag/defaultDragActions.d.ts +0 -9
  121. package/dist/runtime/drag/defaultDragActions.js +0 -10
  122. package/dist/runtime/layout/getDragZones.d.ts +0 -8
  123. package/dist/runtime/layout/getDragZones.js +0 -32
  124. package/src/runtime/drag/createDefaultHandlers.ts +0 -20
  125. package/src/runtime/layout/getDragZones.ts +0 -48
  126. /package/dist/runtime/{drag → move}/createDefaultHandlers.js +0 -0
@@ -2,11 +2,8 @@ import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
2
2
  import { walk } from "@alanscodelog/utils/walk"
3
3
 
4
4
  import { applyFrameChanges } from "./applyFrameChanges.js"
5
- import { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
5
+ import { getFrameExpandInfo } from "./getFrameExpandInfo.js"
6
6
 
7
- import { framesRedistributeFix } from "../helpers/framesRedistributeFix.js"
8
- import { getPinnedEdgesForCollapsedFrames } from "../helpers/getPinnedEdgesForCollapsedFrames.js"
9
- import { oppositeSide } from "../helpers/oppositeSide.js"
10
7
  import type { EdgeSide, LayoutChange, LayoutWindow, Size } from "../types/index.js"
11
8
  import { LAYOUT_ERROR } from "../types/index.js"
12
9
  import { KnownError } from "../utils/KnownError.js"
@@ -32,7 +29,8 @@ export function getFrameUncollapseInfo(
32
29
  | KnownError<typeof LAYOUT_ERROR.CANT_UNCOLLAPSE_NOT_COLLAPSED>
33
30
  | KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS>
34
31
  | KnownError<typeof LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE>
35
- | KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES> {
32
+ | KnownError<typeof LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES>
33
+ | KnownError<typeof LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME> {
36
34
  win = walk(win, undefined, { save: true })
37
35
  const frame = win.frames[frameId]
38
36
  if (!frame) { throw new Error(`Unknown frame ${frameId}`) }
@@ -42,7 +40,6 @@ export function getFrameUncollapseInfo(
42
40
 
43
41
  const isVertical = frame.docked === "left" || frame.docked === "right"
44
42
  const sizeKey = isVertical ? "width" : "height" as const
45
- const posKey = isVertical ? "x" : "y"
46
43
  const currentSize = frame[sizeKey]
47
44
 
48
45
  const storedSize = (restoreSize !== undefined ? restoreSize[sizeKey] : frame.collapsed)!
@@ -60,34 +57,15 @@ export function getFrameUncollapseInfo(
60
57
 
61
58
  const dockedSide = frame.docked as EdgeSide
62
59
 
63
- const { applyFixes, toFix } = framesRedistributeFix(win, frame, dockedSide, posKey, sizeKey, "expand")
64
-
65
- // note fully collapsed frames without an area are already excluded by getFramesRedistributeInfo
66
- const otherFrameIds = Object.keys(win.frames).filter(id => id !== frameId)
67
-
68
- const redistributeSide = oppositeSide(frame.docked)
69
-
70
- const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, dockedSide, posKey, sizeKey)
71
-
72
- const changes = getFramesRedistributeInfo(win, redistributeSide, otherFrameIds, expandAmount, { pinnedEdgeCoordinates })
73
-
74
- if (changes instanceof KnownError) {
75
- return changes
60
+ const expandResult = getFrameExpandInfo(win, frameId, expandAmount, dockedSide)
61
+ if (expandResult instanceof KnownError) {
62
+ return expandResult
76
63
  }
64
+ applyFrameChanges(win, expandResult)
65
+ pushIfNotIn(toExtract, expandResult.modified.map(_ => _.id))
77
66
 
78
- applyFrameChanges(win, changes)
79
- pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
80
-
81
- applyFixes()
82
- pushIfNotIn(toExtract, toFix)
83
-
84
- frame[sizeKey] = storedSize
85
67
  frame.collapsed = undefined
86
68
 
87
- if (frame.docked === "right" || frame.docked === "bottom") {
88
- frame[posKey] -= expandAmount
89
- }
90
-
91
69
  return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
92
70
  }
93
71
 
@@ -1,5 +1,5 @@
1
1
  import { settings } from "../settings.js"
2
- import type { DragZone } from "../types/index.js"
2
+ import type { Zone } from "../types/index.js"
3
3
 
4
4
  /**
5
5
  * Returns frame drag zones (in **unrounded** scaled coordinate space).
@@ -10,12 +10,12 @@ import type { DragZone } from "../types/index.js"
10
10
  *
11
11
  * Unrounded because these are for display purposes only.
12
12
  */
13
- export function getFrameDragZones(
13
+ export function getFrameZones(
14
14
  frame: { x: number, y: number, width: number, height: number },
15
15
  thresholdPx: number,
16
16
  windowPxWidth: number,
17
17
  windowPxHeight: number
18
- ): DragZone[] {
18
+ ): Zone[] {
19
19
  // we do not round as we might undo this transform and drag along errors
20
20
  // plugs this is is for display purposes only
21
21
  const thX = thresholdPx / windowPxWidth * settings.maxInt
@@ -36,7 +36,7 @@ export function getFrameDragZones(
36
36
  }]
37
37
  }
38
38
 
39
- const zones: DragZone[] = []
39
+ const zones: Zone[] = []
40
40
 
41
41
  zones.push({
42
42
  type: "frame",
@@ -2,8 +2,8 @@ import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn"
2
2
  import { walk } from "@alanscodelog/utils/walk"
3
3
 
4
4
  import { applyFrameChanges } from "../layout/applyFrameChanges.js"
5
- import { getFrameCollapseInfo } from "../layout/getFrameCollapseInfo.js"
6
- import { getFrameUncollapseInfo } from "../layout/getFrameUncollapseInfo.js"
5
+ import { getFrameExpandInfo } from "../layout/getFrameExpandInfo.js"
6
+ import { getFrameShrinkInfo } from "../layout/getFrameShrinkInfo.js"
7
7
  import { getRelaxFramesInfo } from "../layout/getRelaxFramesInfo.js"
8
8
  import { settings } from "../settings.js"
9
9
  import type { LayoutChange, LayoutWindow, PxSize } from "../types/index.js"
@@ -48,29 +48,27 @@ export function getUpdateWindowSizeInfo(
48
48
  for (const frameId of dockedFrameIds) {
49
49
  const frame = win.frames[frameId]
50
50
  const orientation = (frame.docked === "left" || frame.docked === "right") ? "horizontal" : "vertical"
51
- const sizeKey = orientation === "horizontal" ? "width" : "height" as const
51
+ const sizeKey = orientation === "horizontal" ? "width" : "height"
52
52
  const targetPxSize = settings.collapseSizePx[sizeKey]
53
53
  const targetScaled = Math.round((targetPxSize / (orientation === "horizontal" ? win.pxWidth : win.pxHeight)) * maxInt)
54
54
 
55
-
56
55
  if (frame[sizeKey] === targetScaled) continue
57
56
  if (frame[sizeKey] === 0) continue
58
57
 
59
- let result: LayoutChange | KnownError<any>
60
-
61
- // the logic needed to do this is nearly identical to the collapse/uncollapse logic
62
- // so we abuse the functions a bit, in future might allow a more flexible collapse/uncollapse function
63
- const wasCollapsed = frame.collapsed
64
- const wasMinSize = { ...settings.minSize }
65
- settings.minSize = 1
66
- if (targetScaled < frame[sizeKey]) {
67
- // clear temporarily so collapse doesn't error
68
- frame.collapsed = undefined
69
- result = getFrameCollapseInfo(win, frame.id, { collapseSizeScaled: { width: targetScaled, height: targetScaled } })
58
+ const side = frame.docked!
59
+ const diff = targetScaled - frame[sizeKey]
60
+
61
+ let result: ReturnType<typeof getFrameShrinkInfo> | ReturnType<typeof getFrameExpandInfo>
62
+
63
+ if (diff < 0) {
64
+ result = getFrameShrinkInfo(win, frame.id, -diff, side, { allowOutOfBounds: true })
70
65
  } else {
71
- // set to something, could be anything
72
- frame.collapsed = Infinity
73
- result = getFrameUncollapseInfo(win, frame.id, { restoreSize: { width: targetScaled, height: targetScaled } })
66
+ // in worst case scenario allow frames to shrink
67
+ // minSize must be 1 (not 0) to avoid zero-size frames during redistribution
68
+ const wasMinSize = { ...settings.minSize }
69
+ settings.minSize = 1
70
+ result = getFrameExpandInfo(win, frame.id, diff, side)
71
+ settings.minSize = wasMinSize
74
72
  }
75
73
 
76
74
  if (!(result instanceof KnownError)) {
@@ -78,10 +76,6 @@ export function getUpdateWindowSizeInfo(
78
76
  pushIfNotIn(toExtract, result.modified.map(f => f.id))
79
77
  }
80
78
 
81
- // restore original collapsed value
82
- frame.collapsed = wasCollapsed
83
- settings.minSize = wasMinSize
84
-
85
79
 
86
80
  // attempt relaxation
87
81
  const result2 = getRelaxFramesInfo(win, relaxPasses)
@@ -6,7 +6,7 @@ import type { LayoutWindow, WindowEdgeZone } from "../types/index.js"
6
6
  * Returns window edge drag zones (in scaled coordinates)
7
7
  *
8
8
  */
9
- export function getWindowDragZones(
9
+ export function getWindowZones(
10
10
  win: LayoutWindow,
11
11
  thresholdPx: number
12
12
  ): WindowEdgeZone[] {
@@ -0,0 +1,48 @@
1
+ import { getFrameZones } from "./getFrameZones.js"
2
+ import { getWindowZones } from "./getWindowZones.js"
3
+ import { isPointInRect } from "./isPointInRect.js"
4
+
5
+ import type { MoveState, WindowEdgeZone, Zone } from "../types/index.js"
6
+
7
+ /**
8
+ * Find the drag zone under the drag point.
9
+ */
10
+ export function getZones(
11
+ state: Pick<MoveState, "movePoint" | "moveHoveredFrame" | "frames" | "win">,
12
+ { frameEdgePx, windowEdgePx }: { frameEdgePx: number, windowEdgePx: number }
13
+ ): Zone | undefined {
14
+ const { movePoint, win } = state
15
+ const frameId = state.moveHoveredFrame?.id
16
+ if (!frameId) return
17
+
18
+ const frame = state.frames[frameId]
19
+ if (!frame || !movePoint) return
20
+
21
+ const frameZones = getFrameZones(frame, frameEdgePx, win.pxWidth, win.pxHeight)
22
+
23
+ const windowZones = getWindowZones(win, windowEdgePx)
24
+
25
+ let matchedZone: Zone | undefined
26
+ for (const zone of windowZones) {
27
+ if (isPointInRect(zone, movePoint)) {
28
+ matchedZone = {
29
+ ...zone,
30
+ type: "window"
31
+ } as WindowEdgeZone
32
+ break
33
+ }
34
+ }
35
+
36
+ if (!matchedZone) {
37
+ for (const zone of frameZones) {
38
+ if (isPointInRect(zone, movePoint)) {
39
+ matchedZone = zone
40
+ break
41
+ }
42
+ }
43
+ }
44
+
45
+ if (!matchedZone) return undefined
46
+
47
+ return matchedZone
48
+ }
@@ -9,20 +9,23 @@ export { findSafeSplitEdgeAndPosition } from "./findSafeSplitEdge.js"
9
9
  export { findVisualEdge } from "./findVisualEdge.js"
10
10
  export { frameCreate } from "./frameCreate.js"
11
11
  export { getCloseFrameInfo } from "./getCloseFrameInfo.js"
12
- export { getDragZones } from "./getDragZones.js"
12
+ export { getZones } from "./getZones.js"
13
13
  export { getFillEmptySpaceInfo } from "./getFillEmptySpaceInfo.js"
14
14
  export { getFrameCollapseInfo } from "./getFrameCollapseInfo.js"
15
15
  export { getFrameDockInfo } from "./getFrameDockInfo.js"
16
- export { getFrameDragZones } from "./getFrameDragZones.js"
16
+ export { getFrameZones } from "./getFrameZones.js"
17
+ export { getFrameExpandInfo } from "./getFrameExpandInfo.js"
17
18
  export { getFrameRearrangeInfo } from "./getFrameRearrangeInfo.js"
19
+ export { getFrameShrinkInfo } from "./getFrameShrinkInfo.js"
18
20
  export { getFrameSplitInfo } from "./getFrameSplitInfo.js"
19
21
  export { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
20
22
  export { getFrameSwapInfo } from "./getFrameSwapInfo.js"
21
23
  export { getFrameTo } from "./getFrameTo.js"
22
24
  export { getFrameUncollapseInfo } from "./getFrameUncollapseInfo.js"
23
25
  export { getFrameUndockInfo } from "./getFrameUndockInfo.js"
26
+ export { getRelaxFramesInfo } from "./getRelaxFramesInfo.js"
24
27
  export { getUpdateWindowSizeInfo } from "./getUpdateWindowSizeInfo.js"
25
- export { getWindowDragZones } from "./getWindowDragZones.js"
28
+ export { getWindowZones } from "./getWindowZones.js"
26
29
  export { isPointInRect } from "./isPointInRect.js"
27
30
  export { layoutAddWindow } from "./layoutAddWindow.js"
28
31
  export { layoutCreate } from "./layoutCreate.js"
@@ -2,36 +2,36 @@ import { get, type RecordFromArray } from "@alanscodelog/utils"
2
2
 
3
3
  import { isWindowEdge } from "../helpers/isWindowEdge.js"
4
4
  import { findFramesTouchingEdge } from "../layout/findFramesTouchingEdge.js"
5
- import type { DragChangeHandler, DragChangeResult, DragState, Edge, IDragAction, LayoutFrame, LayoutShape } from "../types/index.js"
5
+ import type { ActionHandlerApplyResult, Edge, IAction, LayoutFrame, LayoutShape, MoveChangeHandler, MoveChangeResult, MoveState } from "../types/index.js"
6
6
  import { LAYOUT_ERROR } from "../types/index.js"
7
7
  import { KnownError } from "../utils/KnownError.js"
8
8
 
9
9
  /**
10
- * Handles the lifecycle of a drag actions {@link IDragAction} and provides additional hooks.
10
+ * Handles the lifecycle of a drag actions {@link IAction} and provides additional hooks.
11
11
  *
12
12
  * The first action instance that can handle the request is passed control of the event handlers until the request changes.
13
13
  */
14
- export class DragActionHandler<
15
- TRawDragActions extends IDragAction[],
16
- TDragActions extends RecordFromArray<TRawDragActions, "name"> = RecordFromArray<TRawDragActions, "name">
14
+ export class ActionHandler<
15
+ TRawActions extends IAction[],
16
+ TActions extends RecordFromArray<TRawActions, "name"> = RecordFromArray<TRawActions, "name">
17
17
  > {
18
- activeAction?: keyof TDragActions
18
+ activeAction?: keyof TActions
19
19
 
20
- actions: TDragActions
20
+ actions: TActions
21
21
 
22
- eventCanceller: ((e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void) | undefined = undefined
22
+ eventCanceller: ((e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void) | undefined = undefined
23
23
 
24
- boundCancel: (e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void
24
+ boundCancel: (e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void
25
25
 
26
- defaultOnDragChange: DragChangeHandler = (type, _e, state, _forceRecalculateEdges, _cancel) => {
26
+ defaultOnMoveChange: MoveChangeHandler = (type, _e, state, _forceRecalculateEdges, _cancel) => {
27
27
  const isTouchingCollapsedFrameEdge = type === "move"
28
- && state.isDragging === "edge"
28
+ && state.isMoving === "edge"
29
29
  && state.touchingFramesArrays.some(frames =>
30
30
  // we check with a falsy check on PURPOSE, frames collapsed to 0 aren't an issue, they are ignored anyways
31
31
  Object.values(frames).some(f => f.collapsed)
32
32
  )
33
33
  return {
34
- updateEdges: type === "move" ? !(state.isDraggingFromWindowEdge || isTouchingCollapsedFrameEdge) : true,
34
+ updateEdges: type === "move" ? !(state.isMovingFromWindowEdge || isTouchingCollapsedFrameEdge) : true,
35
35
  shapes: []
36
36
  }
37
37
  }
@@ -46,7 +46,7 @@ export class DragActionHandler<
46
46
  */
47
47
  onEvent?: (e: PointerEvent | KeyboardEvent | undefined, cancel: () => void) => void
48
48
  /** Called when the action requested changes. */
49
- onRequestChange?: (type: keyof TDragActions | undefined) => void
49
+ onRequestChange?: (type: keyof TActions | undefined) => void
50
50
  /** Called when the drag action ends either because it was completed or cancelled. */
51
51
  onEnd?: (context: { cancelled: boolean, applied: boolean }) => void
52
52
  }
@@ -54,22 +54,22 @@ export class DragActionHandler<
54
54
  /** All action shapes merged into a single array. If using vue you can set this to a reactive array for reactivity. */
55
55
  shapes: LayoutShape[] = []
56
56
 
57
- /** All hint/error text from all actions, updated on every onDragChange. If using vue you can set this to a reactive object for reactivity. */
57
+ /** All hint/error text from all actions, updated on every onMoveChange. If using vue you can set this to a reactive object for reactivity. */
58
58
  textHints: { actions: string[], errors: string[] } = { actions: [], errors: [] }
59
59
 
60
60
  constructor(
61
- actions: TRawDragActions,
62
- hooks: DragActionHandler<TRawDragActions, TDragActions>["hooks"] = {},
61
+ actions: TRawActions,
62
+ hooks: ActionHandler<TRawActions, TActions>["hooks"] = {},
63
63
  /**
64
- * Default onDragChange handler for when no action can handle the request.
64
+ * Default onMoveChange handler for when no action can handle the request.
65
65
  *
66
66
  * Should return true to allow the edges to be moved, or false to prevent it.
67
67
  *
68
68
  * The default prevents movement when the edge is a window edge and when the edge is touching a collapsed frame.
69
69
  */
70
- defaultOnDragChange?: DragChangeHandler
70
+ defaultOnMoveChange?: MoveChangeHandler
71
71
  ) {
72
- if (defaultOnDragChange) this.defaultOnDragChange = defaultOnDragChange
72
+ if (defaultOnMoveChange) this.defaultOnMoveChange = defaultOnMoveChange
73
73
  this.hooks = hooks
74
74
  this.actions = {} as any
75
75
  for (const action of actions) {
@@ -80,10 +80,10 @@ export class DragActionHandler<
80
80
 
81
81
  eventHandler(
82
82
  e: KeyboardEvent | PointerEvent,
83
- state: DragState,
83
+ state: MoveState,
84
84
  forceRecalculateEdges: () => void
85
85
  ) {
86
- if (state.isDragging) {
86
+ if (state.isMoving) {
87
87
  e.preventDefault()
88
88
  }
89
89
  let cancelled = false
@@ -95,7 +95,7 @@ export class DragActionHandler<
95
95
 
96
96
  let found = false
97
97
  const oldActiveAction = this.activeAction
98
- for (const action of Object.values<TRawDragActions[number]>(this.actions)) {
98
+ for (const action of Object.values<TRawActions[number]>(this.actions)) {
99
99
  if (action.canHandleRequest(e, state, forceRecalculateEdges)) {
100
100
  // if (this.activeAction !== action.name) {
101
101
  // }
@@ -116,38 +116,40 @@ export class DragActionHandler<
116
116
  this.hooks.onRequestChange?.(this.activeAction)
117
117
  }
118
118
 
119
- if (state.isDragging) {
119
+ if (state.isMoving) {
120
120
  forceRecalculateEdges()
121
121
  this.hooks.onRecalculate?.()
122
122
  }
123
123
  return undefined
124
124
  }
125
125
 
126
- onDragChange<T extends "start" | "move" | "end">(
126
+ onMoveChange<T extends "start" | "move" | "end">(
127
127
  type: T,
128
128
  e: T extends "end" ? PointerEvent | undefined : PointerEvent,
129
- state: DragState,
129
+ state: MoveState,
130
130
  forceRecalculateEdges: () => void,
131
- cancel: (e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void
132
- ): DragChangeResult {
131
+ cancel: T extends "end" ? undefined : (e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void,
132
+ resolve: T extends "end" ? undefined : (opts: ActionHandlerApplyResult) => void
133
+ ): MoveChangeResult {
133
134
  if (type === "start") {
134
135
  this.eventCanceller = cancel
135
136
  this.eventHandler(e!, state, forceRecalculateEdges)
136
137
  }
137
- if (type === "end") {
138
- this.activeAction = undefined
139
- }
140
138
 
141
139
  if (this.activeAction) {
142
- const res = this.actions[this.activeAction]!.onDragChange(type, e, state, forceRecalculateEdges, this.boundCancel)
140
+ const res = this.actions[this.activeAction]!.onMoveChange(type, e, state, forceRecalculateEdges, this.boundCancel as any, resolve as any)
143
141
  // in case it's a vue reactive array
144
142
  this.shapes.splice(0, this.shapes.length, ...res.shapes)
145
143
  this.setTextHints(type)
146
144
  return res
147
145
  }
148
146
  this.setTextHints(type)
149
- const res = this.defaultOnDragChange(type, e, state, forceRecalculateEdges, this.boundCancel)
147
+ const res = this.defaultOnMoveChange(type, e, state, forceRecalculateEdges, this.boundCancel as any, resolve as any)
150
148
  this.shapes.splice(0, this.shapes.length, ...res.shapes)
149
+
150
+ if (type === "end") {
151
+ this.activeAction = undefined
152
+ }
151
153
  return res
152
154
  }
153
155
 
@@ -155,7 +157,7 @@ export class DragActionHandler<
155
157
  // again in case it's a vue reactive object
156
158
  this.textHints.actions = []
157
159
  this.textHints.errors = []
158
- for (const action of Object.values<IDragAction>(this.actions)) {
160
+ for (const action of Object.values<IAction>(this.actions)) {
159
161
  const res = action.getTextHints?.(type)
160
162
  this.textHints.actions.push(...(res?.actions ?? []))
161
163
  this.textHints.errors.push(...(res?.errors ?? []))
@@ -166,7 +168,7 @@ export class DragActionHandler<
166
168
  for (const edge of edges) {
167
169
  // edge already has an error
168
170
  if (edge.error) continue
169
- for (const action of Object.values<IDragAction>(this.actions)) {
171
+ for (const action of Object.values<IAction>(this.actions)) {
170
172
  action.annotateEdge?.(edge, frames)
171
173
  if (edge.error) break
172
174
  }
@@ -188,20 +190,20 @@ export class DragActionHandler<
188
190
  }
189
191
  }
190
192
 
191
- onDragApply(
192
- state: DragState,
193
+ onMoveApply(
194
+ state: MoveState,
193
195
  forceRecalculateEdges: () => void
194
- ): boolean {
196
+ ): ActionHandlerApplyResult {
195
197
  if (this.activeAction) {
196
- const res = this.actions[this.activeAction]!.onDragApply(state, forceRecalculateEdges)
198
+ const res = this.actions[this.activeAction]!.onMoveApply(state, forceRecalculateEdges)
197
199
  this.hooks.onEnd?.({ cancelled: false, applied: res })
198
- return false
200
+ return { apply: res, result: undefined }
199
201
  }
200
202
  this.hooks.onEnd?.({ cancelled: false, applied: false })
201
- return true
203
+ return { apply: true, result: undefined }
202
204
  }
203
205
 
204
- cancel(e: PointerEvent | KeyboardEvent | undefined, state: DragState): void {
206
+ cancel(e: PointerEvent | KeyboardEvent | undefined, state: MoveState): void {
205
207
  if (this.activeAction) {
206
208
  this.actions[this.activeAction].cancel(e, state)
207
209
  }
@@ -213,7 +215,7 @@ export class DragActionHandler<
213
215
  static debugState(
214
216
  pluginName: string,
215
217
  type: "before" | "after" | string,
216
- state: DragState,
218
+ state: MoveState,
217
219
  pluginState: Record<string, any> = {},
218
220
  /** Object key to filter the state by, e.g. state.win.frames. If false is ignored. The idea is you pass this.debug and users can set this.debug to a string to filter. */
219
221
  key?: string | boolean
@@ -1,4 +1,4 @@
1
- import { DragActionHandler } from "./DragActionHandler.js"
1
+ import { ActionHandler } from "./ActionHandler.js"
2
2
 
3
3
  import { dirToOrientation } from "../helpers/dirToOrientation.js"
4
4
  import { getEdgeOrientation } from "../helpers/getEdgeOrientation.js"
@@ -6,12 +6,12 @@ import { oppositeSide } from "../helpers/oppositeSide.js"
6
6
  import { applyFrameChanges } from "../layout/applyFrameChanges.js"
7
7
  import { findFramesTouchingEdge } from "../layout/findFramesTouchingEdge.js"
8
8
  import { getCloseFrameInfo } from "../layout/getCloseFrameInfo.js"
9
- import type { ActionDragChangeResult, CloseDeco, DragState, IDragAction } from "../types/index.js"
9
+ import type { ActionChangeResult, CloseDeco, IAction, MoveState } from "../types/index.js"
10
10
  import type { KnownError } from "../utils/KnownError.js"
11
11
 
12
12
  export type CloseInfo = Exclude<ReturnType<typeof getCloseFrameInfo>, KnownError>
13
13
 
14
- export class CloseAction implements IDragAction {
14
+ export class CloseAction implements IAction {
15
15
  name = "close" as const
16
16
  minDragDistance = 5
17
17
 
@@ -20,13 +20,13 @@ export class CloseAction implements IDragAction {
20
20
  force: boolean
21
21
  res: CloseInfo
22
22
  cacheKey: string | undefined
23
- lastReturn: ActionDragChangeResult
23
+ lastReturn: ActionChangeResult
24
24
  } | {
25
25
  allowed: false
26
26
  force: boolean
27
27
  res: CloseInfo | undefined
28
28
  cacheKey: string | undefined
29
- lastReturn: ActionDragChangeResult | undefined
29
+ lastReturn: ActionChangeResult | undefined
30
30
  } = {} as any // this is initialized by this.reset()
31
31
 
32
32
  debug: boolean | string = false
@@ -40,7 +40,7 @@ export class CloseAction implements IDragAction {
40
40
  transformError: e => e.message
41
41
  }
42
42
 
43
- handleEvent: (e: PointerEvent | KeyboardEvent, state: DragState) => boolean | "force" = (e: PointerEvent | KeyboardEvent) => {
43
+ handleEvent: (e: PointerEvent | KeyboardEvent, state: MoveState) => boolean | "force" = (e: PointerEvent | KeyboardEvent) => {
44
44
  if (e.ctrlKey && e.shiftKey) {
45
45
  return "force"
46
46
  }
@@ -86,10 +86,10 @@ export class CloseAction implements IDragAction {
86
86
  this.modifyDecos([])
87
87
  }
88
88
 
89
- private _getDecos(state: DragState): CloseDeco[] {
89
+ private _getDecos(state: MoveState): CloseDeco[] {
90
90
  let decos: CloseDeco[] = []
91
- const { isDragging } = state
92
- if (isDragging && this.state.allowed && this.state.res) {
91
+ const { isMoving } = state
92
+ if (isMoving && this.state.allowed && this.state.res) {
93
93
  const { force } = this.state
94
94
  decos = this.state.res.deleted.map(_ => ({
95
95
  id: _.id,
@@ -136,12 +136,12 @@ export class CloseAction implements IDragAction {
136
136
  }
137
137
 
138
138
 
139
- canHandleRequest(e: PointerEvent | KeyboardEvent, state: DragState): boolean {
140
- const { draggingEdges } = state
141
- if (draggingEdges.length !== 1) return false
139
+ canHandleRequest(e: PointerEvent | KeyboardEvent, state: MoveState): boolean {
140
+ const { movingEdges } = state
141
+ if (movingEdges.length !== 1) return false
142
142
  const res = this.handleEvent(e, state)
143
143
  this.state.force = res === "force"
144
- this.setTextHints(state.isDragging === "edge" ? true : undefined)
144
+ this.setTextHints(state.isMoving === "edge" ? true : undefined)
145
145
  if (res) {
146
146
  this.hooks.onStart?.(true)
147
147
  return true
@@ -150,47 +150,51 @@ export class CloseAction implements IDragAction {
150
150
  return false
151
151
  }
152
152
 
153
- onDragChange(
154
- _type: "start" | "end" | "move",
153
+ onMoveChange(
154
+ type: "start" | "end" | "move",
155
155
  _e: PointerEvent | undefined,
156
- state: DragState
157
- ): ActionDragChangeResult {
158
- if (state.dragDistance <= this.minDragDistance) {
156
+ state: MoveState
157
+ ): ActionChangeResult {
158
+ if (type === "end") {
159
+ this.reset()
160
+ return { shapes: [] }
161
+ }
162
+ if (state.moveDistance <= this.minDragDistance) {
159
163
  return { updateEdges: false, shapes: [] }
160
164
  }
161
165
  const {
162
166
  touchingFramesArrays,
163
- dragDirections,
164
- isDragging,
165
- draggingEdges,
167
+ moveDirections,
168
+ isMoving,
169
+ movingEdges,
166
170
  visualEdges,
167
171
  frames,
168
- isDraggingFromWindowEdge,
169
- dragPoint
172
+ isMovingFromWindowEdge,
173
+ movePoint
170
174
  } = state
171
- const oppositeOrientation = oppositeSide(getEdgeOrientation(draggingEdges[0]))
172
- const cacheKey = `${dragPoint?.x}-${dragPoint?.y}-${dragDirections[oppositeOrientation]!}-${this.state.force}`
175
+ const oppositeOrientation = oppositeSide(getEdgeOrientation(movingEdges[0]))
176
+ const cacheKey = `${movePoint?.x}-${movePoint?.y}-${moveDirections[oppositeOrientation]!}-${this.state.force}`
173
177
  if (this.state.allowed) {
174
178
  if (this.state.cacheKey === cacheKey) {
175
179
  return this.state.lastReturn
176
180
  }
177
181
  }
178
- if (isDragging && draggingEdges.length === 1) {
182
+ if (isMoving && movingEdges.length === 1) {
179
183
  const res = findFramesTouchingEdge(
180
- draggingEdges[0],
184
+ movingEdges[0],
181
185
  touchingFramesArrays[0],
182
186
  {
183
- // referencePoint: dragPoint.value, // if force pick smallest frame?
184
- searchDirections: [dragDirections[oppositeOrientation]!]
187
+ // referencePoint: movePoint.value, // if force pick smallest frame?
188
+ searchDirections: [moveDirections[oppositeOrientation]!]
185
189
  }
186
190
  )
187
191
  if (res.length > 0) {
188
- const orientation = dirToOrientation(dragDirections[oppositeOrientation]!)
192
+ const orientation = dirToOrientation(moveDirections[oppositeOrientation]!)
189
193
  const sizeKey = orientation === "horizontal" ? "width" : "height"
190
194
  const smallestFrameSize = Math.min(...res.map(_ => _.frame[sizeKey]))
191
195
  const frame = res.find(_ => _.frame[sizeKey] === smallestFrameSize)!.frame!
192
196
 
193
- const closeInfo = getCloseFrameInfo(Object.values(frames), visualEdges, frame, dragDirections[oppositeOrientation]!, "dir", this.state.force)
197
+ const closeInfo = getCloseFrameInfo(Object.values(frames), visualEdges, frame, moveDirections[oppositeOrientation]!, "dir", this.state.force)
194
198
  this.state.cacheKey = cacheKey
195
199
  this.setTextHints(closeInfo)
196
200
  if (!(closeInfo instanceof Error)) {
@@ -205,17 +209,16 @@ export class CloseAction implements IDragAction {
205
209
  }
206
210
  }
207
211
  const decos = this._getDecos(state)
208
- this.state.lastReturn = { updateEdges: !isDraggingFromWindowEdge, shapes: decos.flatMap(_ => _.shapes) }
212
+ this.state.lastReturn = { updateEdges: !isMovingFromWindowEdge, shapes: decos.flatMap(_ => _.shapes) }
209
213
  return this.state.lastReturn
210
214
  }
211
215
 
212
- onDragApply(state: DragState): boolean {
216
+ onMoveApply(state: MoveState): boolean {
213
217
  if (this.state.res) {
214
218
  const win = state.win
215
- if (this.debug) { DragActionHandler.debugState(this.name, "before", state, this.state, this.debug) }
219
+ if (this.debug) { ActionHandler.debugState(this.name, "before", state, this.state, this.debug) }
216
220
  applyFrameChanges(win, this.state.res)
217
- if (this.debug) { DragActionHandler.debugState(this.name, "after", state, this.state, this.debug) }
218
- this.reset()
221
+ if (this.debug) { ActionHandler.debugState(this.name, "after", state, this.state, this.debug) }
219
222
  return true
220
223
  }
221
224
  return false