@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.
- package/dist/module.json +1 -1
- package/dist/runtime/components/FrameDragHandle.vue +3 -3
- package/dist/runtime/components/LayoutDragEdgeGrabbed.vue +3 -3
- package/dist/runtime/components/LayoutDragEdgeHandle.d.vue.ts +2 -2
- package/dist/runtime/components/LayoutDragEdgeHandle.vue +2 -2
- package/dist/runtime/components/LayoutDragEdgeHandle.vue.d.ts +2 -2
- package/dist/runtime/components/LayoutDragEdges.vue +5 -5
- package/dist/runtime/components/LayoutEdgesActiveFrame.vue +2 -2
- package/dist/runtime/components/LayoutFrame.vue +2 -2
- package/dist/runtime/components/LayoutIntersectionHandle.d.vue.ts +2 -2
- package/dist/runtime/components/LayoutIntersectionHandle.vue +2 -2
- package/dist/runtime/components/LayoutIntersectionHandle.vue.d.ts +2 -2
- package/dist/runtime/components/LayoutIntersections.vue +4 -4
- package/dist/runtime/components/LayoutWindow.d.vue.ts +54 -45
- package/dist/runtime/components/LayoutWindow.vue +29 -29
- package/dist/runtime/components/LayoutWindow.vue.d.ts +54 -45
- package/dist/runtime/composables/useFrames.d.ts +142 -129
- package/dist/runtime/composables/useFrames.js +122 -94
- package/dist/runtime/demo/App.vue +16 -16
- package/dist/runtime/demo/DemoControls.d.vue.ts +2 -2
- package/dist/runtime/demo/DemoControls.vue +4 -4
- package/dist/runtime/demo/DemoControls.vue.d.ts +2 -2
- package/dist/runtime/demo/tailwind.css +1 -1
- package/dist/runtime/helpers/createZoneSideClipPath.d.ts +2 -2
- package/dist/runtime/helpers/findEdgesTouchingWindow.d.ts +7 -0
- package/dist/runtime/helpers/findEdgesTouchingWindow.js +20 -0
- package/dist/runtime/helpers/{findFrameDraggableEdges.d.ts → findFrameMoveableEdges.d.ts} +2 -2
- package/dist/runtime/helpers/{findFrameDraggableEdges.js → findFrameMoveableEdges.js} +3 -3
- package/dist/runtime/helpers/{findDraggableEdge.d.ts → findMoveableEdge.d.ts} +1 -1
- package/dist/runtime/helpers/{findDraggableEdge.js → findMoveableEdge.js} +1 -1
- package/dist/runtime/helpers/index.d.ts +10 -3
- package/dist/runtime/helpers/index.js +10 -3
- package/dist/runtime/layout/createSplitDecoFromDrag.d.ts +1 -1
- package/dist/runtime/layout/createSplitDecoFromDrag.js +2 -2
- package/dist/runtime/layout/debugFrame.js +5 -1
- package/dist/runtime/layout/findSafeSplitEdge.d.ts +1 -1
- package/dist/runtime/layout/findSafeSplitEdge.js +2 -2
- package/dist/runtime/layout/getCloseFrameInfo.js +4 -4
- package/dist/runtime/layout/getFrameCollapseInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameCollapseInfo.js +9 -29
- package/dist/runtime/layout/getFrameDockInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameDockInfo.js +61 -44
- package/dist/runtime/layout/getFrameExpandInfo.d.ts +11 -0
- package/dist/runtime/layout/getFrameExpandInfo.js +49 -0
- package/dist/runtime/layout/getFrameRearrangeInfo.d.ts +2 -2
- package/dist/runtime/layout/getFrameRearrangeInfo.js +8 -8
- package/dist/runtime/layout/getFrameShrinkInfo.d.ts +14 -0
- package/dist/runtime/layout/getFrameShrinkInfo.js +48 -0
- package/dist/runtime/layout/getFrameSplitInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameSplitInfo.js +2 -2
- package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameUncollapseInfo.js +6 -20
- package/dist/runtime/layout/{getFrameDragZones.d.ts → getFrameZones.d.ts} +3 -3
- package/dist/runtime/layout/{getFrameDragZones.js → getFrameZones.js} +1 -1
- package/dist/runtime/layout/getUpdateWindowSizeInfo.js +10 -12
- package/dist/runtime/layout/{getWindowDragZones.d.ts → getWindowZones.d.ts} +1 -1
- package/dist/runtime/layout/{getWindowDragZones.js → getWindowZones.js} +1 -1
- package/dist/runtime/layout/getZones.d.ts +8 -0
- package/dist/runtime/layout/getZones.js +32 -0
- package/dist/runtime/layout/index.d.ts +6 -3
- package/dist/runtime/layout/index.js +6 -3
- package/dist/runtime/{drag/DragActionHandler.d.ts → move/ActionHandler.d.ts} +18 -18
- package/dist/runtime/{drag/DragActionHandler.js → move/ActionHandler.js} +19 -19
- package/dist/runtime/{drag → move}/CloseAction.d.ts +8 -8
- package/dist/runtime/{drag → move}/CloseAction.js +29 -26
- package/dist/runtime/{drag → move}/FrameDragAction.d.ts +8 -8
- package/dist/runtime/{drag → move}/FrameDragAction.js +23 -21
- package/dist/runtime/{drag/DragDirectionStore.d.ts → move/MoveDirectionStore.d.ts} +4 -4
- package/dist/runtime/{drag/DragDirectionStore.js → move/MoveDirectionStore.js} +9 -9
- package/dist/runtime/{drag → move}/SplitAction.d.ts +9 -9
- package/dist/runtime/{drag → move}/SplitAction.js +46 -43
- package/dist/runtime/{drag → move}/createDefaultHandlers.d.ts +2 -2
- package/dist/runtime/types/index.d.ts +242 -158
- package/dist/runtime/types/index.js +4 -3
- package/dist/runtime/types/vue.d.ts +25 -18
- package/dist/runtime/types/vue.js +1 -1
- package/package.json +3 -2
- package/src/runtime/components/FrameDragHandle.vue +3 -3
- package/src/runtime/components/LayoutDragEdgeGrabbed.vue +3 -3
- package/src/runtime/components/LayoutDragEdgeHandle.vue +4 -4
- package/src/runtime/components/LayoutDragEdges.vue +5 -5
- package/src/runtime/components/LayoutEdgesActiveFrame.vue +2 -2
- package/src/runtime/components/LayoutFrame.vue +2 -2
- package/src/runtime/components/LayoutIntersectionHandle.vue +4 -4
- package/src/runtime/components/LayoutIntersections.vue +4 -4
- package/src/runtime/components/LayoutWindow.vue +35 -35
- package/src/runtime/composables/useFrames.ts +141 -121
- package/src/runtime/demo/App.vue +18 -18
- package/src/runtime/demo/DemoControls.vue +5 -5
- package/src/runtime/demo/tailwind.css +4 -0
- package/src/runtime/helpers/createZoneSideClipPath.ts +2 -2
- package/src/runtime/helpers/findEdgesTouchingWindow.ts +30 -0
- package/src/runtime/helpers/{findFrameDraggableEdges.ts → findFrameMoveableEdges.ts} +4 -4
- package/src/runtime/helpers/{findDraggableEdge.ts → findMoveableEdge.ts} +1 -1
- package/src/runtime/helpers/index.ts +10 -3
- package/src/runtime/layout/createSplitDecoFromDrag.ts +2 -2
- package/src/runtime/layout/debugFrame.ts +6 -1
- package/src/runtime/layout/findSafeSplitEdge.ts +4 -4
- package/src/runtime/layout/getCloseFrameInfo.ts +4 -4
- package/src/runtime/layout/getFrameCollapseInfo.ts +12 -41
- package/src/runtime/layout/getFrameDockInfo.ts +77 -57
- package/src/runtime/layout/getFrameExpandInfo.ts +84 -0
- package/src/runtime/layout/getFrameRearrangeInfo.ts +10 -10
- package/src/runtime/layout/getFrameShrinkInfo.ts +87 -0
- package/src/runtime/layout/getFrameSplitInfo.ts +5 -5
- package/src/runtime/layout/getFrameUncollapseInfo.ts +8 -30
- package/src/runtime/layout/{getFrameDragZones.ts → getFrameZones.ts} +4 -4
- package/src/runtime/layout/getUpdateWindowSizeInfo.ts +16 -22
- package/src/runtime/layout/{getWindowDragZones.ts → getWindowZones.ts} +1 -1
- package/src/runtime/layout/getZones.ts +48 -0
- package/src/runtime/layout/index.ts +6 -3
- package/src/runtime/{drag/DragActionHandler.ts → move/ActionHandler.ts} +44 -42
- package/src/runtime/{drag → move}/CloseAction.ts +39 -36
- package/src/runtime/{drag → move}/FrameDragAction.ts +36 -35
- package/src/runtime/{drag/DragDirectionStore.ts → move/MoveDirectionStore.ts} +10 -10
- package/src/runtime/{drag → move}/SplitAction.ts +56 -53
- package/src/runtime/{drag/defaultDragActions.ts → move/createDefaultHandlers.ts} +2 -2
- package/src/runtime/types/index.ts +104 -55
- package/src/runtime/types/vue.ts +18 -16
- package/dist/runtime/drag/defaultDragActions.d.ts +0 -9
- package/dist/runtime/drag/defaultDragActions.js +0 -10
- package/dist/runtime/layout/getDragZones.d.ts +0 -8
- package/dist/runtime/layout/getDragZones.js +0 -32
- package/src/runtime/drag/createDefaultHandlers.ts +0 -20
- package/src/runtime/layout/getDragZones.ts +0 -48
- /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 {
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
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 {
|
|
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
|
|
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
|
-
):
|
|
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:
|
|
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 {
|
|
6
|
-
import {
|
|
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"
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export class ActionHandler<
|
|
15
|
+
TRawActions extends IAction[],
|
|
16
|
+
TActions extends RecordFromArray<TRawActions, "name"> = RecordFromArray<TRawActions, "name">
|
|
17
17
|
> {
|
|
18
|
-
activeAction?: keyof
|
|
18
|
+
activeAction?: keyof TActions
|
|
19
19
|
|
|
20
|
-
actions:
|
|
20
|
+
actions: TActions
|
|
21
21
|
|
|
22
|
-
eventCanceller: ((e: PointerEvent | KeyboardEvent | undefined, state:
|
|
22
|
+
eventCanceller: ((e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void) | undefined = undefined
|
|
23
23
|
|
|
24
|
-
boundCancel: (e: PointerEvent | KeyboardEvent | undefined, state:
|
|
24
|
+
boundCancel: (e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
defaultOnMoveChange: MoveChangeHandler = (type, _e, state, _forceRecalculateEdges, _cancel) => {
|
|
27
27
|
const isTouchingCollapsedFrameEdge = type === "move"
|
|
28
|
-
&& state.
|
|
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.
|
|
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
|
|
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
|
|
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:
|
|
62
|
-
hooks:
|
|
61
|
+
actions: TRawActions,
|
|
62
|
+
hooks: ActionHandler<TRawActions, TActions>["hooks"] = {},
|
|
63
63
|
/**
|
|
64
|
-
* Default
|
|
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
|
-
|
|
70
|
+
defaultOnMoveChange?: MoveChangeHandler
|
|
71
71
|
) {
|
|
72
|
-
if (
|
|
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:
|
|
83
|
+
state: MoveState,
|
|
84
84
|
forceRecalculateEdges: () => void
|
|
85
85
|
) {
|
|
86
|
-
if (state.
|
|
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<
|
|
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.
|
|
119
|
+
if (state.isMoving) {
|
|
120
120
|
forceRecalculateEdges()
|
|
121
121
|
this.hooks.onRecalculate?.()
|
|
122
122
|
}
|
|
123
123
|
return undefined
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
onMoveChange<T extends "start" | "move" | "end">(
|
|
127
127
|
type: T,
|
|
128
128
|
e: T extends "end" ? PointerEvent | undefined : PointerEvent,
|
|
129
|
-
state:
|
|
129
|
+
state: MoveState,
|
|
130
130
|
forceRecalculateEdges: () => void,
|
|
131
|
-
cancel: (e: PointerEvent | KeyboardEvent | undefined, state:
|
|
132
|
-
|
|
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]!.
|
|
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.
|
|
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<
|
|
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<
|
|
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
|
-
|
|
192
|
-
state:
|
|
193
|
+
onMoveApply(
|
|
194
|
+
state: MoveState,
|
|
193
195
|
forceRecalculateEdges: () => void
|
|
194
|
-
):
|
|
196
|
+
): ActionHandlerApplyResult {
|
|
195
197
|
if (this.activeAction) {
|
|
196
|
-
const res = this.actions[this.activeAction]!.
|
|
198
|
+
const res = this.actions[this.activeAction]!.onMoveApply(state, forceRecalculateEdges)
|
|
197
199
|
this.hooks.onEnd?.({ cancelled: false, applied: res })
|
|
198
|
-
return
|
|
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:
|
|
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:
|
|
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 {
|
|
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 {
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
89
|
+
private _getDecos(state: MoveState): CloseDeco[] {
|
|
90
90
|
let decos: CloseDeco[] = []
|
|
91
|
-
const {
|
|
92
|
-
if (
|
|
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:
|
|
140
|
-
const {
|
|
141
|
-
if (
|
|
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.
|
|
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
|
-
|
|
154
|
-
|
|
153
|
+
onMoveChange(
|
|
154
|
+
type: "start" | "end" | "move",
|
|
155
155
|
_e: PointerEvent | undefined,
|
|
156
|
-
state:
|
|
157
|
-
):
|
|
158
|
-
if (
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
moveDirections,
|
|
168
|
+
isMoving,
|
|
169
|
+
movingEdges,
|
|
166
170
|
visualEdges,
|
|
167
171
|
frames,
|
|
168
|
-
|
|
169
|
-
|
|
172
|
+
isMovingFromWindowEdge,
|
|
173
|
+
movePoint
|
|
170
174
|
} = state
|
|
171
|
-
const oppositeOrientation = oppositeSide(getEdgeOrientation(
|
|
172
|
-
const cacheKey = `${
|
|
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 (
|
|
182
|
+
if (isMoving && movingEdges.length === 1) {
|
|
179
183
|
const res = findFramesTouchingEdge(
|
|
180
|
-
|
|
184
|
+
movingEdges[0],
|
|
181
185
|
touchingFramesArrays[0],
|
|
182
186
|
{
|
|
183
|
-
// referencePoint:
|
|
184
|
-
searchDirections: [
|
|
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(
|
|
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,
|
|
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: !
|
|
212
|
+
this.state.lastReturn = { updateEdges: !isMovingFromWindowEdge, shapes: decos.flatMap(_ => _.shapes) }
|
|
209
213
|
return this.state.lastReturn
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
|
|
216
|
+
onMoveApply(state: MoveState): boolean {
|
|
213
217
|
if (this.state.res) {
|
|
214
218
|
const win = state.win
|
|
215
|
-
if (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) {
|
|
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
|