@witchcraft/layout 0.4.3 → 0.4.4
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/LayoutWindow.d.vue.ts +10 -1
- package/dist/runtime/components/LayoutWindow.vue +4 -4
- package/dist/runtime/components/LayoutWindow.vue.d.ts +10 -1
- package/dist/runtime/composables/useFrames.d.ts +37 -22
- package/dist/runtime/composables/useFrames.js +32 -8
- package/dist/runtime/demo/tailwind.css +1 -1
- package/dist/runtime/drag/DragActionHandler.d.ts +4 -1
- package/dist/runtime/drag/DragActionHandler.js +2 -2
- package/dist/runtime/helpers/findEdgesTouchingWindow.d.ts +7 -0
- package/dist/runtime/helpers/findEdgesTouchingWindow.js +20 -0
- package/dist/runtime/helpers/index.d.ts +8 -1
- package/dist/runtime/helpers/index.js +8 -1
- package/dist/runtime/layout/debugFrame.js +5 -1
- 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/getFrameShrinkInfo.d.ts +14 -0
- package/dist/runtime/layout/getFrameShrinkInfo.js +48 -0
- package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +1 -1
- package/dist/runtime/layout/getFrameUncollapseInfo.js +6 -20
- package/dist/runtime/layout/getUpdateWindowSizeInfo.js +10 -12
- package/dist/runtime/layout/index.d.ts +3 -0
- package/dist/runtime/layout/index.js +3 -0
- package/dist/runtime/types/index.d.ts +70 -0
- package/dist/runtime/types/index.js +2 -1
- package/dist/runtime/types/vue.d.ts +12 -3
- package/package.json +3 -2
- package/src/runtime/components/LayoutWindow.vue +5 -5
- package/src/runtime/composables/useFrames.ts +46 -32
- package/src/runtime/demo/tailwind.css +4 -0
- package/src/runtime/drag/DragActionHandler.ts +3 -3
- package/src/runtime/helpers/findEdgesTouchingWindow.ts +30 -0
- package/src/runtime/helpers/index.ts +8 -1
- package/src/runtime/layout/debugFrame.ts +6 -1
- 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/getFrameShrinkInfo.ts +87 -0
- package/src/runtime/layout/getFrameUncollapseInfo.ts +8 -30
- package/src/runtime/layout/getUpdateWindowSizeInfo.ts +16 -22
- package/src/runtime/layout/index.ts +3 -0
- package/src/runtime/types/index.ts +39 -1
- package/src/runtime/types/vue.ts +5 -3
- package/dist/runtime/drag/defaultDragActions.d.ts +0 -9
- package/dist/runtime/drag/defaultDragActions.js +0 -10
- package/src/runtime/drag/defaultDragActions.ts +0 -20
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComputedRef, InjectionKey, Ref } from "vue";
|
|
2
|
-
import type { Direction, DragState, Edge, FrameId, IntersectionEntry, LayoutFrame, LayoutShape, LayoutWindow, Orientation, Point } from "./index.js";
|
|
2
|
+
import type { ActionHandler, Direction, DragState, Edge, FrameId, IntersectionEntry, LayoutFrame, LayoutShape, LayoutWindow, Orientation, Point } from "./index.js";
|
|
3
3
|
export type LayoutContext = ComputedRef<{
|
|
4
4
|
/** The owning window, needed so we can correctly scale coordinates. */
|
|
5
5
|
win: LayoutWindow;
|
|
@@ -8,14 +8,23 @@ export type LayoutContext = ComputedRef<{
|
|
|
8
8
|
}>;
|
|
9
9
|
export declare const layoutContextInjectionKey: InjectionKey<LayoutContext>;
|
|
10
10
|
export interface UseFramesContext {
|
|
11
|
+
actionHandler: ActionHandler;
|
|
11
12
|
dragStart: {
|
|
12
13
|
(e: PointerEvent, type: "edge", data: {
|
|
13
14
|
edge?: Edge;
|
|
14
15
|
intersection?: IntersectionEntry;
|
|
15
|
-
}
|
|
16
|
+
}, opts?: {
|
|
17
|
+
moveEvent?: string;
|
|
18
|
+
endEvent?: string;
|
|
19
|
+
context?: Record<string, unknown>;
|
|
20
|
+
}): Promise<any>;
|
|
16
21
|
(e: PointerEvent, type: "frame", data: {
|
|
17
22
|
frameId: FrameId;
|
|
18
|
-
}
|
|
23
|
+
}, opts?: {
|
|
24
|
+
moveEvent?: string;
|
|
25
|
+
endEvent?: string;
|
|
26
|
+
context?: Record<string, unknown>;
|
|
27
|
+
}): Promise<any>;
|
|
19
28
|
};
|
|
20
29
|
dragMove: (e: PointerEvent) => void;
|
|
21
30
|
dragEnd: (e?: PointerEvent, options?: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@witchcraft/layout",
|
|
3
3
|
"description": "Headless layout manager.",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"main": "./dist/runtime/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"@alanscodelog/semantic-release-config": "^6.0.2",
|
|
50
50
|
"@alanscodelog/tsconfigs": "^6.3.0",
|
|
51
51
|
"@alanscodelog/vite-config": "^0.0.7",
|
|
52
|
+
"@babel/runtime": "^7.20.7",
|
|
52
53
|
"@commitlint/cli": "^19.8.1",
|
|
53
54
|
"@nuxt/eslint-config": "^1.15.2",
|
|
54
55
|
"@nuxt/kit": "^4.4.6",
|
|
@@ -145,6 +146,6 @@
|
|
|
145
146
|
"doc:dev": "concurrently \"pnpm doc:watch\" \"pnpm doc:serve\"",
|
|
146
147
|
"doc:check-invalid": "typedoc --listInvalidSymbolLinks",
|
|
147
148
|
"actions:debug": "act -r -v -j release",
|
|
148
|
-
"gen:exports": "indexit update --ignore **/*.d.ts **.vue src/assets/** -o '${path}.js'"
|
|
149
|
+
"gen:exports": "indexit update --ignore **/*.d.ts **.vue src/assets/** **/demo/** **/dist/** -o '${path}.js'"
|
|
149
150
|
}
|
|
150
151
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
flex
|
|
8
8
|
flex-col
|
|
9
9
|
`,
|
|
10
|
-
isDragging && `dragging cursor-pointer user-select-none`,
|
|
10
|
+
isDragging && `dragging cursor-pointer user-select-none pointer-events-none`,
|
|
11
11
|
requestType && `request-${requestType}`,
|
|
12
12
|
($attrs as any).class
|
|
13
13
|
)"
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
<script lang="ts" setup>
|
|
71
71
|
import { useGlobalResizeObserver } from "@witchcraft/ui/composables/useGlobalResizeObserver"
|
|
72
72
|
import { twMerge } from "@witchcraft/ui/utils/twMerge"
|
|
73
|
-
import { computed, provide, reactive, ref, useAttrs, watch } from "vue"
|
|
73
|
+
import { computed, markRaw, provide, reactive, ref, useAttrs, watch } from "vue"
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
import { useFrames } from "../composables/useFrames.js"
|
|
@@ -90,7 +90,7 @@ const $attrs = useAttrs()
|
|
|
90
90
|
const win = defineModel<LayoutWindow>("win", { required: true })
|
|
91
91
|
|
|
92
92
|
const props = withDefaults(defineProps<{
|
|
93
|
-
/** If you really need it, you can provide your own drag action handler, note the actionHandler's prop will no longer have any effect.*/
|
|
93
|
+
/** If you really need it, you can provide your own drag action handler (remember to markRaw it), note the actionHandler's prop will no longer have any effect.*/
|
|
94
94
|
dragActionHandler?: DragActionHandler<any, any>
|
|
95
95
|
/** Custom drag action handlers. Falls back to default split/close/frame handlers if not provided. */
|
|
96
96
|
actionHandlers?: IDragAction[]
|
|
@@ -122,7 +122,7 @@ const windowEl = ref<HTMLElement | null>(null)
|
|
|
122
122
|
|
|
123
123
|
const requestType = ref<string | undefined | string>()
|
|
124
124
|
|
|
125
|
-
const dragActionHandler = props.dragActionHandler ?? new DragActionHandler(
|
|
125
|
+
const dragActionHandler = props.dragActionHandler ?? markRaw(new DragActionHandler(
|
|
126
126
|
[
|
|
127
127
|
...(props.actionHandlers ?? createDefaultHandlers())
|
|
128
128
|
],
|
|
@@ -141,7 +141,7 @@ const dragActionHandler = props.dragActionHandler ?? new DragActionHandler(
|
|
|
141
141
|
showDragging.value = true
|
|
142
142
|
},
|
|
143
143
|
}
|
|
144
|
-
)
|
|
144
|
+
))
|
|
145
145
|
dragActionHandler.shapes = reactive([])
|
|
146
146
|
dragActionHandler.textHints = reactive({ actions: [], errors: [] })
|
|
147
147
|
const shapes = dragActionHandler.shapes
|
|
@@ -12,30 +12,11 @@ import { moveEdge } from "../helpers/moveEdge.js"
|
|
|
12
12
|
import { toWindowCoord } from "../helpers/toWindowCoord.js"
|
|
13
13
|
import { findFramesTouchingEdge } from "../layout/findFramesTouchingEdge.js"
|
|
14
14
|
import { isPointInRect } from "../layout/isPointInRect.js"
|
|
15
|
-
import type {
|
|
15
|
+
import type { ActionHandler, Direction, DragState, Edge, EdgeDragStartData, FrameDragStartData, FrameId, IntersectionEntry, LayoutFrame, LayoutWindow, Orientation, Point } from "../types/index.js"
|
|
16
16
|
|
|
17
17
|
export function useFrames(
|
|
18
18
|
win: Ref<LayoutWindow>,
|
|
19
|
-
handler:
|
|
20
|
-
eventHandler: (e: KeyboardEvent, state: DragState, forceRecalculateEdges: () => void) => void
|
|
21
|
-
/**
|
|
22
|
-
* Called when the drag coordinates change (during any event). Should return true to allow the edges to be updated/moved, or false to prevent it.
|
|
23
|
-
*
|
|
24
|
-
* Can return anything for the end event as it's ignored.
|
|
25
|
-
*
|
|
26
|
-
* Can be used to save some context/info to later apply safely during onDragApply.
|
|
27
|
-
*/
|
|
28
|
-
onDragChange: (...args: Parameters<DragChangeHandler>) => DragChangeResult
|
|
29
|
-
/**
|
|
30
|
-
* Called when drag will be applied. If dragEnd was called with apply false, it will not be called.
|
|
31
|
-
* Return false to not apply the regular drag end changes (i.e. return false to reset to the position before dragging).
|
|
32
|
-
*/
|
|
33
|
-
onDragApply: ((state: DragState, forceRecalculateEdges: () => void) => boolean)
|
|
34
|
-
/**
|
|
35
|
-
* Called after visual edges are recalculated. Action handlers can annotate edges with error info.
|
|
36
|
-
*/
|
|
37
|
-
annotateEdges?: (edges: Edge[], frames: LayoutFrame[]) => void
|
|
38
|
-
}
|
|
19
|
+
handler: ActionHandler
|
|
39
20
|
) {
|
|
40
21
|
const draggingEdges = ref<Edge[]>([])
|
|
41
22
|
|
|
@@ -66,6 +47,7 @@ export function useFrames(
|
|
|
66
47
|
const isDraggingFromWindowEdge = ref<boolean>(false)
|
|
67
48
|
|
|
68
49
|
const frameDragFrameId = ref<FrameId | undefined>()
|
|
50
|
+
const eventContext = ref<Record<string, unknown> | undefined>()
|
|
69
51
|
|
|
70
52
|
const frames = computed(() =>
|
|
71
53
|
isDragging.value && showDragging.value
|
|
@@ -126,14 +108,21 @@ export function useFrames(
|
|
|
126
108
|
dragHoveredFrame: dragHoveredFrame.value,
|
|
127
109
|
intersections: intersections.value,
|
|
128
110
|
isDraggingFromWindowEdge: isDraggingFromWindowEdge.value,
|
|
111
|
+
eventContext: eventContext.value,
|
|
129
112
|
win: win.value
|
|
130
113
|
} satisfies DragState))
|
|
131
114
|
|
|
132
115
|
let controller: AbortController
|
|
116
|
+
let dragResolve: ((result: any) => void) | undefined
|
|
117
|
+
let dragResult: any
|
|
133
118
|
|
|
134
119
|
function resetState(): void {
|
|
135
120
|
dragStartPoint = undefined
|
|
136
121
|
|
|
122
|
+
dragResolve?.(dragResult)
|
|
123
|
+
dragResolve = undefined
|
|
124
|
+
dragResult = undefined
|
|
125
|
+
|
|
137
126
|
draggingEdges.value = []
|
|
138
127
|
draggingIntersection.value = undefined
|
|
139
128
|
isDragging.value = false
|
|
@@ -141,6 +130,7 @@ export function useFrames(
|
|
|
141
130
|
dragDistance.value = -1
|
|
142
131
|
touchingFrames.value = []
|
|
143
132
|
frameDragFrameId.value = undefined
|
|
133
|
+
eventContext.value = undefined
|
|
144
134
|
dragDirStore.reset()
|
|
145
135
|
showDragging.value = false
|
|
146
136
|
forceRecalculateEdges()
|
|
@@ -150,19 +140,38 @@ export function useFrames(
|
|
|
150
140
|
e: PointerEvent,
|
|
151
141
|
type: T,
|
|
152
142
|
// someday this will work
|
|
153
|
-
data: T extends "edge" ? EdgeDragStartData : FrameDragStartData
|
|
154
|
-
|
|
143
|
+
data: T extends "edge" ? EdgeDragStartData : FrameDragStartData,
|
|
144
|
+
{
|
|
145
|
+
moveEvent = "pointermove",
|
|
146
|
+
endEvent = "pointerup",
|
|
147
|
+
context
|
|
148
|
+
}: {
|
|
149
|
+
moveEvent?: string
|
|
150
|
+
endEvent?: string
|
|
151
|
+
context?: Record<string, unknown>
|
|
152
|
+
} = {}
|
|
153
|
+
): Promise<any> {
|
|
154
|
+
// if already dragging, abort previous (resetState resolves the old promise)
|
|
155
|
+
if (controller) {
|
|
156
|
+
controller.abort()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const promise = new Promise<any>(resolve => {
|
|
160
|
+
dragResolve = resolve
|
|
161
|
+
})
|
|
162
|
+
|
|
155
163
|
controller = new AbortController()
|
|
156
164
|
controller.signal.addEventListener("abort", () => resetState())
|
|
157
165
|
|
|
158
166
|
e.preventDefault()
|
|
159
|
-
window.addEventListener(
|
|
160
|
-
window.addEventListener(
|
|
167
|
+
window.addEventListener(moveEvent, dragMove as EventListener, { signal: controller.signal })
|
|
168
|
+
window.addEventListener(endEvent, dragEnd as EventListener, { signal: controller.signal })
|
|
161
169
|
|
|
162
170
|
const point = toWindowCoord(win.value, e)
|
|
163
171
|
dragPoint.value = point
|
|
164
172
|
dragStartPoint = { ...point }
|
|
165
173
|
dragDirStore.update(point)
|
|
174
|
+
eventContext.value = context
|
|
166
175
|
|
|
167
176
|
isDragging.value = type
|
|
168
177
|
showDragging.value = true
|
|
@@ -174,7 +183,7 @@ export function useFrames(
|
|
|
174
183
|
draggingIntersection.value = intersection
|
|
175
184
|
|
|
176
185
|
draggingEdges.value = edge
|
|
177
|
-
? [edge]
|
|
186
|
+
? [edge!]
|
|
178
187
|
: [
|
|
179
188
|
...(draggingIntersection.value?.sharedEdges.horizontal ?? []),
|
|
180
189
|
...(draggingIntersection.value?.sharedEdges.vertical ?? [])
|
|
@@ -206,6 +215,7 @@ export function useFrames(
|
|
|
206
215
|
|
|
207
216
|
const res = handler.onDragChange("start", e, state.value, forceRecalculateEdges, cancel)
|
|
208
217
|
showDragging.value = res.showDragging ?? true
|
|
218
|
+
return promise
|
|
209
219
|
}
|
|
210
220
|
|
|
211
221
|
function dragMove(e: PointerEvent): void {
|
|
@@ -246,17 +256,20 @@ export function useFrames(
|
|
|
246
256
|
dragPoint.value = point
|
|
247
257
|
}
|
|
248
258
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
259
|
+
if (apply) {
|
|
260
|
+
const applyResult = handler.onDragApply(state.value, forceRecalculateEdges)
|
|
261
|
+
dragResult = applyResult.result
|
|
262
|
+
if (applyResult.apply) {
|
|
263
|
+
for (const frame of touchingFramesArrays.value.flat()) {
|
|
264
|
+
win!.value.frames[frame.id] = frame
|
|
265
|
+
}
|
|
253
266
|
}
|
|
254
267
|
}
|
|
255
268
|
|
|
256
269
|
handler.onDragChange("end", e, state.value, forceRecalculateEdges, cancel)
|
|
257
270
|
|
|
258
271
|
// this can get called from elsewhere
|
|
259
|
-
// also takes care of cleanup
|
|
272
|
+
// also takes care of cleanup nd resolving promise
|
|
260
273
|
controller?.abort()
|
|
261
274
|
}
|
|
262
275
|
function cancel(): void {
|
|
@@ -296,6 +309,7 @@ export function useFrames(
|
|
|
296
309
|
forceRecalculateEdges,
|
|
297
310
|
state,
|
|
298
311
|
showDragging,
|
|
299
|
-
frameDragFrameId
|
|
312
|
+
frameDragFrameId,
|
|
313
|
+
actionHandler: handler
|
|
300
314
|
}
|
|
301
315
|
}
|
|
@@ -191,14 +191,14 @@ export class DragActionHandler<
|
|
|
191
191
|
onDragApply(
|
|
192
192
|
state: DragState,
|
|
193
193
|
forceRecalculateEdges: () => void
|
|
194
|
-
): boolean {
|
|
194
|
+
): { apply: boolean, result: any } {
|
|
195
195
|
if (this.activeAction) {
|
|
196
196
|
const res = this.actions[this.activeAction]!.onDragApply(state, forceRecalculateEdges)
|
|
197
197
|
this.hooks.onEnd?.({ cancelled: false, applied: res })
|
|
198
|
-
return
|
|
198
|
+
return { apply: res, result: undefined }
|
|
199
199
|
}
|
|
200
200
|
this.hooks.onEnd?.({ cancelled: false, applied: false })
|
|
201
|
-
return true
|
|
201
|
+
return { apply: true, result: undefined }
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
cancel(e: PointerEvent | KeyboardEvent | undefined, state: DragState): void {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { settings } from "../settings.js"
|
|
2
|
+
import type { EdgeSide, LayoutFrame } from "../types/index.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks which window edges a frame touches and how.
|
|
6
|
+
*
|
|
7
|
+
* @returns Record mapping touched sides to `"full"` (spans the entire edge) or `"partial"` (touches but doesn't span fully). Only touched sides are included as keys.
|
|
8
|
+
*/
|
|
9
|
+
export function findEdgesTouchingWindow(frame: LayoutFrame): Partial<Record<EdgeSide, "full" | "partial">> {
|
|
10
|
+
const maxInt = settings.maxInt
|
|
11
|
+
const result: Partial<Record<EdgeSide, "full" | "partial">> = {}
|
|
12
|
+
|
|
13
|
+
const spansVertical = frame.y === 0 && frame.y + frame.height === maxInt
|
|
14
|
+
const spansHorizontal = frame.x === 0 && frame.x + frame.width === maxInt
|
|
15
|
+
|
|
16
|
+
if (frame.x === 0) {
|
|
17
|
+
result.left = spansVertical ? "full" : "partial"
|
|
18
|
+
}
|
|
19
|
+
if (frame.x + frame.width === maxInt) {
|
|
20
|
+
result.right = spansVertical ? "full" : "partial"
|
|
21
|
+
}
|
|
22
|
+
if (frame.y === 0) {
|
|
23
|
+
result.top = spansHorizontal ? "full" : "partial"
|
|
24
|
+
}
|
|
25
|
+
if (frame.y + frame.height === maxInt) {
|
|
26
|
+
result.bottom = spansHorizontal ? "full" : "partial"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
@@ -12,6 +12,7 @@ export { assertValidWinAndFrameIds } from "./assertValidWinAndFrameIds.js"
|
|
|
12
12
|
export { assertWindowHasActiveFrame } from "./assertWindowHasActiveFrame.js"
|
|
13
13
|
export { cloneFrame } from "./cloneFrame.js"
|
|
14
14
|
export { cloneFrames } from "./cloneFrames.js"
|
|
15
|
+
export { consoleDebugWindow } from "./consoleDebugWindow.js"
|
|
15
16
|
export { containsEdge } from "./containsEdge.js"
|
|
16
17
|
export { convertLayoutWindowToWorkspace } from "./convertLayoutWindowToWorkspace.js"
|
|
17
18
|
export { copySize } from "./copySize.js"
|
|
@@ -24,17 +25,21 @@ export { doesEdgeContinueEdge } from "./doesEdgeContinueEdge.js"
|
|
|
24
25
|
export { edgeToPoints } from "./edgeToPoints.js"
|
|
25
26
|
export { findDraggableEdge } from "./findDraggableEdge.js"
|
|
26
27
|
export { findFrameDraggableEdges } from "./findFrameDraggableEdges.js"
|
|
28
|
+
export { framesRedistributeFix } from "./framesRedistributeFix.js"
|
|
27
29
|
export { frameToEdges } from "./frameToEdges.js"
|
|
28
30
|
export { frameToPoints } from "./frameToPoints.js"
|
|
31
|
+
export { getDockBoundaries } from "./getDockBoundaries.js"
|
|
29
32
|
export { getEdgeLength } from "./getEdgeLength.js"
|
|
30
33
|
export { getEdgeOrientation } from "./getEdgeOrientation.js"
|
|
31
34
|
export { getEdgeSharedDirection } from "./getEdgeSharedDirection.js"
|
|
32
35
|
export { getEdgeSide } from "./getEdgeSide.js"
|
|
33
36
|
export { getFrameById } from "./getFrameById.js"
|
|
34
37
|
export { getFrameConstant } from "./getFrameConstant.js"
|
|
38
|
+
export { findEdgesTouchingWindow } from "./findEdgesTouchingWindow.js"
|
|
35
39
|
export { getIntersections } from "./getIntersections.js"
|
|
36
40
|
export { getIntersectionsCss } from "./getIntersectionsCss.js"
|
|
37
41
|
export { getMoveEdgeInfo } from "./getMoveEdgeInfo.js"
|
|
42
|
+
export { getPinnedEdgesForCollapsedFrames } from "./getPinnedEdgesForCollapsedFrames.js"
|
|
38
43
|
export { getResizeLimit } from "./getResizeLimit.js"
|
|
39
44
|
export { getShapeRectCss } from "./getShapeRectCss.js"
|
|
40
45
|
export { getSideTouching } from "./getSideTouching.js"
|
|
@@ -55,9 +60,10 @@ export { isWindowEdge } from "./isWindowEdge.js"
|
|
|
55
60
|
export { isWindowEdgePoint } from "./isWindowEdgePoint.js"
|
|
56
61
|
export { moveEdge } from "./moveEdge.js"
|
|
57
62
|
export { numberToScaledPercent } from "./numberToScaledPercent.js"
|
|
58
|
-
export { pxSizeToScaledSize } from "./pxSizeToScaledSize.js"
|
|
59
63
|
export { oppositeSide } from "./oppositeSide.js"
|
|
64
|
+
export { pxSizeToScaledSize } from "./pxSizeToScaledSize.js"
|
|
60
65
|
export { resizeByEdge } from "./resizeByEdge.js"
|
|
66
|
+
export { rotateLayout } from "./rotateFrames.js"
|
|
61
67
|
export { scaledPointToPx } from "./scaledPointToPx.js"
|
|
62
68
|
export { sideToDirection } from "./sideToDirection.js"
|
|
63
69
|
export { sideToOrientation } from "./sideToOrientation.js"
|
|
@@ -67,3 +73,4 @@ export { toId } from "./toId.js"
|
|
|
67
73
|
export { toWindowCoord } from "./toWindowCoord.js"
|
|
68
74
|
export { unionEdges } from "./unionEdges.js"
|
|
69
75
|
export { updateWindowWithEvent } from "./updateWindowSizeWithEvent.js"
|
|
76
|
+
export { validateLayoutShape } from "./validateLayoutShape.js"
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { keys } from "@alanscodelog/utils/keys"
|
|
2
|
+
|
|
1
3
|
import type { LayoutFrame } from "../types/index.js"
|
|
2
4
|
|
|
5
|
+
const builtinProperties = ["id", "x", "y", "width", "height", "docked", "collapsed"]
|
|
3
6
|
export function debugFrame(frame: LayoutFrame): string {
|
|
4
7
|
const f = frame
|
|
5
|
-
|
|
8
|
+
const otherProperties = keys(f).filter(k => !builtinProperties.includes(k)).map(k => `${k}: ${f[k]}`)
|
|
9
|
+
const otherPropertiesString = otherProperties.length > 0 ? `, ${otherProperties.join(", ")}` : ""
|
|
10
|
+
return `id: ${f.id.slice(0, 4)}, x: ${f.x}, y: ${f.y}, w: ${f.width}, h: ${f.height}\ndocked: ${f.docked}, collapsed: ${f.collapsed}${otherPropertiesString}`
|
|
6
11
|
}
|
|
@@ -2,13 +2,10 @@ 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 { getFrameShrinkInfo } from "./getFrameShrinkInfo.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 { settings } from "../settings.js"
|
|
11
|
-
import type {
|
|
8
|
+
import type { LayoutChange, LayoutWindow, Size } from "../types/index.js"
|
|
12
9
|
import { LAYOUT_ERROR } from "../types/index.js"
|
|
13
10
|
import { KnownError } from "../utils/KnownError.js"
|
|
14
11
|
|
|
@@ -32,7 +29,8 @@ export function getFrameCollapseInfo(
|
|
|
32
29
|
} = {}
|
|
33
30
|
): LayoutChange
|
|
34
31
|
| KnownError<typeof LAYOUT_ERROR.CANT_COLLAPSE_NOT_DOCKED>
|
|
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
|
collapseSizeScaled = collapseSizeScaled ?? settings.getCollapseSizeScaled(win)
|
|
37
35
|
win = walk(win, undefined, { save: true }) as typeof win
|
|
38
36
|
const frame = win.frames[frameId]
|
|
@@ -51,55 +49,28 @@ export function getFrameCollapseInfo(
|
|
|
51
49
|
|
|
52
50
|
const isVertical = frame.docked === "left" || frame.docked === "right"
|
|
53
51
|
const sizeKey = isVertical ? "width" : "height" as const
|
|
54
|
-
const posKey = isVertical ? "x" : "y"
|
|
55
|
-
const oppositePosKey = isVertical ? "y" : "x"
|
|
56
|
-
const oppositeSizeKey = isVertical ? "height" : "width"
|
|
57
|
-
|
|
58
52
|
|
|
59
53
|
const currentSize = frame[sizeKey]
|
|
60
|
-
const
|
|
61
|
-
const shrinkAmount = currentSize - collapseAmount
|
|
62
|
-
|
|
63
|
-
const dockedSide = frame.docked as EdgeSide
|
|
64
|
-
|
|
65
|
-
const { applyFixes, toFix } = framesRedistributeFix(win, frame, dockedSide, posKey, sizeKey, "shrink")
|
|
66
|
-
|
|
67
|
-
// note fully collapsed frames without an area are already excluded by getFramesRedistributeInfo
|
|
68
|
-
const otherFrameIds = Object.keys(win.frames).filter(id => id !== frameId)
|
|
69
|
-
|
|
70
|
-
const redistributeSide = oppositeSide(dockedSide)
|
|
54
|
+
const shrinkAmount = currentSize - collapseSizeScaled[sizeKey]
|
|
71
55
|
|
|
72
|
-
const
|
|
56
|
+
const shrinkResult = getFrameShrinkInfo(win, frameId, shrinkAmount, frame.docked, { allowOutOfBounds: true })
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (changes instanceof KnownError) {
|
|
77
|
-
// we should never get out of bounds and because this is a collapse there should always be space
|
|
78
|
-
if (changes.code === LAYOUT_ERROR.REDISTRIBUTE_OUT_OF_BOUNDS || changes.code === LAYOUT_ERROR.NO_SPACE_TO_REDISTRIBUTE) {
|
|
79
|
-
changes.message = `This error should never happen, please file a bug report: ${changes.message}`
|
|
80
|
-
throw changes
|
|
81
|
-
}
|
|
82
|
-
return changes
|
|
58
|
+
if (shrinkResult instanceof KnownError) {
|
|
59
|
+
return shrinkResult
|
|
83
60
|
}
|
|
84
|
-
applyFrameChanges(win, changes)
|
|
85
|
-
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
86
|
-
|
|
87
61
|
|
|
88
|
-
|
|
89
|
-
pushIfNotIn(toExtract,
|
|
62
|
+
applyFrameChanges(win, shrinkResult)
|
|
63
|
+
pushIfNotIn(toExtract, shrinkResult.modified.map(_ => _.id))
|
|
90
64
|
|
|
91
|
-
if (frame.docked === "right" || frame.docked === "bottom") {
|
|
92
|
-
frame[posKey] = frame[posKey] + (frame[sizeKey] - collapseSizeScaled[sizeKey])
|
|
93
|
-
}
|
|
94
|
-
frame[sizeKey] = collapseSizeScaled[sizeKey]
|
|
95
65
|
frame.collapsed = currentSize
|
|
96
66
|
// when we collapse to 0 it's a special case where the frame will always fit the entire window edge
|
|
97
67
|
// for proper uncollapsing later
|
|
98
68
|
if (collapseSizeScaled[sizeKey] === 0) {
|
|
69
|
+
const oppositePosKey = isVertical ? "y" : "x"
|
|
70
|
+
const oppositeSizeKey = isVertical ? "height" : "width"
|
|
99
71
|
frame[oppositePosKey] = 0
|
|
100
72
|
frame[oppositeSizeKey] = settings.maxInt
|
|
101
73
|
}
|
|
102
74
|
|
|
103
75
|
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
104
76
|
}
|
|
105
|
-
|
|
@@ -3,10 +3,12 @@ import { walk } from "@alanscodelog/utils/walk"
|
|
|
3
3
|
|
|
4
4
|
import { applyFrameChanges } from "./applyFrameChanges.js"
|
|
5
5
|
import { getFillEmptySpaceInfo } from "./getFillEmptySpaceInfo.js"
|
|
6
|
+
import { getFrameShrinkInfo } from "./getFrameShrinkInfo.js"
|
|
6
7
|
import { getFramesRedistributeInfo } from "./getFramesRedistributeInfo.js"
|
|
7
8
|
import { getFrameUndockInfo } from "./getFrameUndockInfo.js"
|
|
8
9
|
|
|
9
10
|
import { cloneFrame } from "../helpers/cloneFrame.js"
|
|
11
|
+
import { findEdgesTouchingWindow } from "../helpers/findEdgesTouchingWindow.js"
|
|
10
12
|
import { getDockBoundaries } from "../helpers/getDockBoundaries.js"
|
|
11
13
|
import { getPinnedEdgesForCollapsedFrames } from "../helpers/getPinnedEdgesForCollapsedFrames.js"
|
|
12
14
|
import { oppositeSide } from "../helpers/oppositeSide.js"
|
|
@@ -33,7 +35,8 @@ export function getFrameDockInfo(
|
|
|
33
35
|
| KnownError<typeof LAYOUT_ERROR.CANT_LEAVE_NO_UNDOCKED_FRAMES>
|
|
34
36
|
| KnownError<typeof LAYOUT_ERROR.FRAME_ALREADY_DOCKED_ON_SIDE>
|
|
35
37
|
| KnownError<typeof LAYOUT_ERROR.CANT_UNDOCK_COLLAPSED_FRAME>
|
|
36
|
-
| KnownError<typeof LAYOUT_ERROR.NO_FILL_CANDIDATES>
|
|
38
|
+
| KnownError<typeof LAYOUT_ERROR.NO_FILL_CANDIDATES>
|
|
39
|
+
| KnownError<typeof LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME> {
|
|
37
40
|
// its easier to just clone the window and extract changes later
|
|
38
41
|
// setting the var ensures we don't accidentally mutate the original
|
|
39
42
|
win = walk(win, undefined, { save: true }) as typeof win
|
|
@@ -57,8 +60,9 @@ export function getFrameDockInfo(
|
|
|
57
60
|
|
|
58
61
|
const isHorizontal = side === "left" || side === "right"
|
|
59
62
|
const perpendicular = isHorizontal ? "width" : "height"
|
|
63
|
+
const posKey = isHorizontal ? "x" : "y"
|
|
64
|
+
const sizeKey = isHorizontal ? "width" : "height"
|
|
60
65
|
|
|
61
|
-
const oldFrame = cloneFrame(frame)
|
|
62
66
|
const otherFrameIds = Object.keys(win.frames).filter(_ => _ !== frameId)
|
|
63
67
|
const nonDockedFrameIds = otherFrameIds.filter(id => !win.frames[id].docked)
|
|
64
68
|
if (nonDockedFrameIds.length === 0) {
|
|
@@ -66,68 +70,84 @@ export function getFrameDockInfo(
|
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
// if its the only frame allow it to be as big as it likes
|
|
69
|
-
const
|
|
70
|
-
const perpendicularLength = otherFrameIds.length > 0 ? Math.min(frame[perpendicular],
|
|
73
|
+
const maxPerpendicular = maxPerpendicularLength ?? settings.maxPerpendicularLengthScaled.width
|
|
74
|
+
const perpendicularLength = otherFrameIds.length > 0 ? Math.min(frame[perpendicular], maxPerpendicular) : frame[perpendicular]
|
|
71
75
|
|
|
72
76
|
frame.docked = side
|
|
73
77
|
frame.collapsed = undefined
|
|
74
78
|
|
|
75
79
|
const toExtract = [frame.id]
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (changes instanceof Error) return changes
|
|
80
|
-
applyFrameChanges(win, changes)
|
|
81
|
-
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// redistribute other non-docked frames to make room for the new dock.
|
|
85
|
-
const sideToPushTowards = oppositeSide(side)
|
|
86
|
-
|
|
87
|
-
const posKey = isHorizontal ? "x" : "y"
|
|
88
|
-
const sizeKey = isHorizontal ? "width" : "height"
|
|
89
|
-
|
|
90
|
-
const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, side, posKey, sizeKey)
|
|
91
|
-
|
|
92
|
-
const redistributeChanges = getFramesRedistributeInfo(win, sideToPushTowards, nonDockedFrameIds, perpendicularLength, { pinnedEdgeCoordinates })
|
|
93
|
-
|
|
94
|
-
if (redistributeChanges instanceof KnownError) {
|
|
95
|
-
return redistributeChanges
|
|
81
|
+
if (frame[perpendicular] === perpendicularLength) {
|
|
82
|
+
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
96
83
|
}
|
|
97
|
-
applyFrameChanges(win, redistributeChanges)
|
|
98
|
-
pushIfNotIn(toExtract, redistributeChanges.modified.map(_ => _.id))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const { minX, maxX, minY, maxY } = getDockBoundaries(win)
|
|
102
|
-
switch (side) {
|
|
103
|
-
case "left":
|
|
104
|
-
frame.x = 0
|
|
105
|
-
frame.y = minY
|
|
106
|
-
frame.width = perpendicularLength
|
|
107
|
-
frame.height = maxY - minY
|
|
108
|
-
break
|
|
109
|
-
case "right":
|
|
110
|
-
frame.x = settings.maxInt - perpendicularLength
|
|
111
|
-
frame.y = minY
|
|
112
|
-
frame.width = perpendicularLength
|
|
113
|
-
frame.height = maxY - minY
|
|
114
|
-
break
|
|
115
|
-
case "top":
|
|
116
|
-
frame.x = minX
|
|
117
|
-
frame.y = 0
|
|
118
|
-
frame.width = maxX - minX
|
|
119
|
-
frame.height = perpendicularLength
|
|
120
|
-
break
|
|
121
|
-
case "bottom":
|
|
122
|
-
frame.x = minX
|
|
123
|
-
frame.y = settings.maxInt - perpendicularLength
|
|
124
|
-
frame.width = maxX - minX
|
|
125
|
-
frame.height = perpendicularLength
|
|
126
|
-
break
|
|
127
|
-
}
|
|
128
|
-
|
|
129
84
|
|
|
130
|
-
|
|
85
|
+
// check if the frame touches the full edge it will be docked to (not just it's dock boundary)
|
|
86
|
+
// if so, we can use getFrameShrinkInfo instead of fill-empty-space + redistribute
|
|
87
|
+
// this feels better as otherwise, the other frames feel like they are unnecessarily moved to the other side
|
|
88
|
+
// this way where the frame *was* feels like it's taken into account
|
|
89
|
+
const touchesEntireEdge = findEdgesTouchingWindow(frame)[side] === "full"
|
|
90
|
+
|
|
91
|
+
if (touchesEntireEdge) {
|
|
92
|
+
const shrinkAmount = frame[sizeKey] - perpendicularLength
|
|
93
|
+
if (shrinkAmount > 0) {
|
|
94
|
+
const shrinkResult = getFrameShrinkInfo(win, frameId, shrinkAmount, side)
|
|
95
|
+
if (shrinkResult instanceof KnownError) {
|
|
96
|
+
return shrinkResult
|
|
97
|
+
}
|
|
98
|
+
applyFrameChanges(win, shrinkResult)
|
|
99
|
+
pushIfNotIn(toExtract, shrinkResult.modified.map(_ => _.id))
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
const oldFrame = cloneFrame(frame)
|
|
103
|
+
|
|
104
|
+
// fills just the hole left by the frame when it was moved
|
|
105
|
+
const changes = getFillEmptySpaceInfo(win, oldFrame, [], [frameId])
|
|
106
|
+
if (changes instanceof Error) return changes
|
|
107
|
+
applyFrameChanges(win, changes)
|
|
108
|
+
pushIfNotIn(toExtract, changes.modified.map(_ => _.id))
|
|
109
|
+
|
|
110
|
+
// redistribute other non-docked frames to make room for the new dock.
|
|
111
|
+
const sideToPushTowards = oppositeSide(side)
|
|
112
|
+
|
|
113
|
+
const pinnedEdgeCoordinates: number[] = getPinnedEdgesForCollapsedFrames(win, frame, side, posKey, sizeKey)
|
|
114
|
+
|
|
115
|
+
const redistributeChanges = getFramesRedistributeInfo(win, sideToPushTowards, nonDockedFrameIds, perpendicularLength, { pinnedEdgeCoordinates })
|
|
116
|
+
|
|
117
|
+
if (redistributeChanges instanceof KnownError) {
|
|
118
|
+
return redistributeChanges
|
|
119
|
+
}
|
|
120
|
+
applyFrameChanges(win, redistributeChanges)
|
|
121
|
+
pushIfNotIn(toExtract, redistributeChanges.modified.map(_ => _.id))
|
|
122
|
+
|
|
123
|
+
const { minX, maxX, minY, maxY } = getDockBoundaries(win)
|
|
124
|
+
switch (side) {
|
|
125
|
+
case "left":
|
|
126
|
+
frame.x = 0
|
|
127
|
+
frame.y = minY
|
|
128
|
+
frame.width = perpendicularLength
|
|
129
|
+
frame.height = maxY - minY
|
|
130
|
+
break
|
|
131
|
+
case "right":
|
|
132
|
+
frame.x = settings.maxInt - perpendicularLength
|
|
133
|
+
frame.y = minY
|
|
134
|
+
frame.width = perpendicularLength
|
|
135
|
+
frame.height = maxY - minY
|
|
136
|
+
break
|
|
137
|
+
case "top":
|
|
138
|
+
frame.x = minX
|
|
139
|
+
frame.y = 0
|
|
140
|
+
frame.width = maxX - minX
|
|
141
|
+
frame.height = perpendicularLength
|
|
142
|
+
break
|
|
143
|
+
case "bottom":
|
|
144
|
+
frame.x = minX
|
|
145
|
+
frame.y = settings.maxInt - perpendicularLength
|
|
146
|
+
frame.width = maxX - minX
|
|
147
|
+
frame.height = perpendicularLength
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
}
|
|
131
151
|
|
|
132
|
-
return { modified:
|
|
152
|
+
return { modified: toExtract.map(_ => win.frames[_]), created: [], deleted: [] }
|
|
133
153
|
}
|