@witchcraft/layout 0.4.2 → 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.
Files changed (65) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/LayoutDragEdgeGrabbed.vue +1 -1
  3. package/dist/runtime/components/LayoutDragEdgeHandle.vue +1 -2
  4. package/dist/runtime/components/LayoutWindow.d.vue.ts +12 -0
  5. package/dist/runtime/components/LayoutWindow.vue +5 -4
  6. package/dist/runtime/components/LayoutWindow.vue.d.ts +12 -0
  7. package/dist/runtime/composables/useFrames.d.ts +38 -22
  8. package/dist/runtime/composables/useFrames.js +44 -8
  9. package/dist/runtime/demo/tailwind.css +1 -1
  10. package/dist/runtime/drag/CloseAction.d.ts +3 -0
  11. package/dist/runtime/drag/CloseAction.js +5 -0
  12. package/dist/runtime/drag/DragActionHandler.d.ts +4 -1
  13. package/dist/runtime/drag/DragActionHandler.js +2 -2
  14. package/dist/runtime/drag/FrameDragAction.d.ts +3 -0
  15. package/dist/runtime/drag/FrameDragAction.js +5 -0
  16. package/dist/runtime/drag/SplitAction.d.ts +3 -0
  17. package/dist/runtime/drag/SplitAction.js +8 -3
  18. package/dist/runtime/helpers/findEdgesTouchingWindow.d.ts +7 -0
  19. package/dist/runtime/helpers/findEdgesTouchingWindow.js +20 -0
  20. package/dist/runtime/helpers/index.d.ts +8 -1
  21. package/dist/runtime/helpers/index.js +8 -1
  22. package/dist/runtime/layout/debugFrame.js +5 -1
  23. package/dist/runtime/layout/getFrameCollapseInfo.d.ts +1 -1
  24. package/dist/runtime/layout/getFrameCollapseInfo.js +9 -29
  25. package/dist/runtime/layout/getFrameDockInfo.d.ts +1 -1
  26. package/dist/runtime/layout/getFrameDockInfo.js +61 -44
  27. package/dist/runtime/layout/getFrameExpandInfo.d.ts +11 -0
  28. package/dist/runtime/layout/getFrameExpandInfo.js +49 -0
  29. package/dist/runtime/layout/getFrameShrinkInfo.d.ts +14 -0
  30. package/dist/runtime/layout/getFrameShrinkInfo.js +48 -0
  31. package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +1 -1
  32. package/dist/runtime/layout/getFrameUncollapseInfo.js +6 -20
  33. package/dist/runtime/layout/getUpdateWindowSizeInfo.js +10 -12
  34. package/dist/runtime/layout/index.d.ts +3 -0
  35. package/dist/runtime/layout/index.js +3 -0
  36. package/dist/runtime/settings.js +1 -1
  37. package/dist/runtime/types/index.d.ts +72 -0
  38. package/dist/runtime/types/index.js +2 -1
  39. package/dist/runtime/types/vue.d.ts +12 -3
  40. package/package.json +3 -2
  41. package/src/runtime/components/LayoutDragEdgeGrabbed.vue +1 -1
  42. package/src/runtime/components/LayoutDragEdgeHandle.vue +1 -2
  43. package/src/runtime/components/LayoutWindow.vue +8 -6
  44. package/src/runtime/composables/useFrames.ts +61 -32
  45. package/src/runtime/demo/tailwind.css +4 -0
  46. package/src/runtime/drag/CloseAction.ts +7 -0
  47. package/src/runtime/drag/DragActionHandler.ts +3 -3
  48. package/src/runtime/drag/FrameDragAction.ts +9 -0
  49. package/src/runtime/drag/SplitAction.ts +11 -3
  50. package/src/runtime/helpers/findEdgesTouchingWindow.ts +30 -0
  51. package/src/runtime/helpers/index.ts +8 -1
  52. package/src/runtime/layout/debugFrame.ts +6 -1
  53. package/src/runtime/layout/getFrameCollapseInfo.ts +12 -41
  54. package/src/runtime/layout/getFrameDockInfo.ts +77 -57
  55. package/src/runtime/layout/getFrameExpandInfo.ts +84 -0
  56. package/src/runtime/layout/getFrameShrinkInfo.ts +87 -0
  57. package/src/runtime/layout/getFrameUncollapseInfo.ts +8 -30
  58. package/src/runtime/layout/getUpdateWindowSizeInfo.ts +16 -22
  59. package/src/runtime/layout/index.ts +3 -0
  60. package/src/runtime/settings.ts +1 -1
  61. package/src/runtime/types/index.ts +41 -1
  62. package/src/runtime/types/vue.ts +5 -3
  63. package/dist/runtime/drag/defaultDragActions.d.ts +0 -9
  64. package/dist/runtime/drag/defaultDragActions.js +0 -10
  65. package/src/runtime/drag/defaultDragActions.ts +0 -20
@@ -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
 
@@ -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)
@@ -14,13 +14,16 @@ export { getFillEmptySpaceInfo } from "./getFillEmptySpaceInfo.js"
14
14
  export { getFrameCollapseInfo } from "./getFrameCollapseInfo.js"
15
15
  export { getFrameDockInfo } from "./getFrameDockInfo.js"
16
16
  export { getFrameDragZones } from "./getFrameDragZones.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
28
  export { getWindowDragZones } from "./getWindowDragZones.js"
26
29
  export { isPointInRect } from "./isPointInRect.js"
@@ -11,7 +11,7 @@ export class Settings {
11
11
  resetToDefaults() {
12
12
  this.scale = 3
13
13
  this.snapPoint = 0.5
14
- this.minSize = 5
14
+ this.minSize = 3
15
15
  this.collapseSizePx = 15
16
16
  this.maxPerpendicularLength = 20
17
17
  this.zoneSizes = { frameEdgePx: 40, windowEdgePx: 20 }
@@ -287,7 +287,8 @@ export const LAYOUT_ERROR = enumFromArray([
287
287
  "CANT_COLLAPSE_NOT_DOCKED",
288
288
  "CANT_UNCOLLAPSE_NOT_COLLAPSED",
289
289
  "NO_FILL_CANDIDATES",
290
- "CANT_RESIZE_COLLAPSED_FRAME"
290
+ "CANT_RESIZE_COLLAPSED_FRAME",
291
+ "CANT_RESIZE_SINGLE_FRAME"
291
292
  ])
292
293
 
293
294
  export type LayoutError = EnumLike<typeof LAYOUT_ERROR>
@@ -376,6 +377,9 @@ export type LayoutErrorsInfo = {
376
377
  [LAYOUT_ERROR.CANT_RESIZE_COLLAPSED_FRAME]: {
377
378
  frame: LayoutFrame
378
379
  }
380
+ [LAYOUT_ERROR.CANT_RESIZE_SINGLE_FRAME]: {
381
+ frame: LayoutFrame
382
+ }
379
383
  [LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES]: {
380
384
  problemEdgeCoordinates: number[]
381
385
  }
@@ -442,6 +446,8 @@ export type DragState = {
442
446
  dragDirections: Record<Orientation, Direction | undefined>
443
447
  /** The curren point (in scaled window coordinates) the user is dragging at. */
444
448
  dragPoint?: Point
449
+ /** Cumulative pixel distance the user has dragged from the initial click point. 0 on start, increases on each move. */
450
+ dragDistance: number
445
451
  /** Whether the user is currently dragging and what type of drag. Is truthy string during all drag events. */
446
452
  isDragging: boolean | "frame" | "edge"
447
453
  /** Whether to show the moved frames while dragging. */
@@ -484,6 +490,8 @@ export type DragState = {
484
490
  * Whether the drag was initiated from a point along the window edge.
485
491
  */
486
492
  isDraggingFromWindowEdge: boolean
493
+ /** Custom context passed to dragStart, available to action handlers via state.eventContext. */
494
+ eventContext?: Record<string, unknown>
487
495
  win: LayoutWindow
488
496
  }
489
497
 
@@ -600,6 +608,38 @@ export interface IDragAction {
600
608
  export type EdgeDragStartData = { edge?: Edge, intersection?: IntersectionEntry }
601
609
  export type FrameDragStartData = { frameId: FrameId }
602
610
 
611
+ /**
612
+ * Handler interface for drag actions.
613
+ */
614
+ export interface ActionHandler {
615
+ eventHandler: (e: KeyboardEvent, state: DragState, forceRecalculateEdges: () => void) => void
616
+ /**
617
+ * 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.
618
+ *
619
+ * Can return anything for the end event as it's ignored.
620
+ *
621
+ * Can be used to save some context/info to later apply safely during onDragApply.
622
+ */
623
+ onDragChange: (...args: Parameters<DragChangeHandler>) => DragChangeResult
624
+ /**
625
+ * Called when drag will be applied. If dragEnd was called with apply false, it will not be called.
626
+ * Return false to not apply the regular drag end changes (i.e. return false to reset to the position before dragging).
627
+ */
628
+ onDragApply: (
629
+ state: DragState,
630
+ forceRecalculateEdges: () => void
631
+ ) => {
632
+ /** Whether to apply the regular drag end changes. Return false to reset to the position before dragging. */
633
+ apply: boolean
634
+ /** Value to resolve the drag promise with. Ignored if `apply` is false. */
635
+ result: any
636
+ }
637
+ /**
638
+ * Called after visual edges are recalculated. Action handlers can annotate edges with error info.
639
+ */
640
+ annotateEdges?: (edges: Edge[], frames: LayoutFrame[]) => void
641
+ }
642
+
603
643
  // drag start overloads for triggering dragsj
604
644
  export type DragStartFn = {
605
645
  (e: PointerEvent, type: "edge", data: EdgeDragStartData): void
@@ -1,10 +1,11 @@
1
1
  import type { ComputedRef, InjectionKey, Ref } from "vue"
2
2
 
3
3
  // eslint-disable-next-line no-restricted-imports
4
- import type { Direction, DragState, Edge, FrameId, IntersectionEntry, LayoutFrame, LayoutShape, LayoutWindow, Orientation, Point } from "./index.js"
4
+ import type { ActionHandler, Direction, DragState, Edge, FrameId, IntersectionEntry, LayoutFrame, LayoutShape, LayoutWindow, Orientation, Point } from "./index.js"
5
5
 
6
6
  export type LayoutContext = ComputedRef<
7
7
  & {
8
+
8
9
  /** The owning window, needed so we can correctly scale coordinates. */
9
10
  win: LayoutWindow
10
11
  onFocus: (frameId: string) => void
@@ -14,9 +15,10 @@ export type LayoutContext = ComputedRef<
14
15
  export const layoutContextInjectionKey = Symbol.for("@witchcraft/layout:context") as InjectionKey<LayoutContext>
15
16
 
16
17
  export interface UseFramesContext {
18
+ actionHandler: ActionHandler
17
19
  dragStart: {
18
- (e: PointerEvent, type: "edge", data: { edge?: Edge, intersection?: IntersectionEntry }): void
19
- (e: PointerEvent, type: "frame", data: { frameId: FrameId }): void
20
+ (e: PointerEvent, type: "edge", data: { edge?: Edge, intersection?: IntersectionEntry }, opts?: { moveEvent?: string, endEvent?: string, context?: Record<string, unknown> }): Promise<any>
21
+ (e: PointerEvent, type: "frame", data: { frameId: FrameId }, opts?: { moveEvent?: string, endEvent?: string, context?: Record<string, unknown> }): Promise<any>
20
22
  }
21
23
  dragMove: (e: PointerEvent) => void
22
24
  dragEnd: (e?: PointerEvent, options?: { apply?: boolean }) => void
@@ -1,9 +0,0 @@
1
- import type { IDragAction } from "../types/index.js";
2
- /**
3
- * Creates the default drag actions (Split, Close, FrameDrag).
4
- */
5
- export declare function createDefaultHandlers(config: {
6
- debugSplit?: boolean;
7
- debugClose?: boolean;
8
- debugFrameDrag?: boolean;
9
- }): IDragAction[];
@@ -1,10 +0,0 @@
1
- import { CloseAction } from "./CloseAction.js";
2
- import { FrameDragAction } from "./FrameDragAction.js";
3
- import { SplitAction } from "./SplitAction.js";
4
- export function createDefaultHandlers(config) {
5
- return [
6
- new SplitAction(void 0, void 0, void 0, { debug: config.debugSplit }),
7
- new CloseAction(void 0, void 0, void 0, { debug: config.debugClose }),
8
- new FrameDragAction(void 0, void 0, void 0, { debug: config.debugFrameDrag })
9
- ];
10
- }
@@ -1,20 +0,0 @@
1
- import { CloseAction } from "./CloseAction"
2
- import { FrameDragAction } from "./FrameDragAction.js"
3
- import { SplitAction } from "./SplitAction.js"
4
-
5
- import type { IDragAction } from "../types/index.js"
6
-
7
- /**
8
- * Creates the default drag actions (Split, Close, FrameDrag).
9
- */
10
- export function createDefaultHandlers(config: {
11
- debugSplit?: boolean
12
- debugClose?: boolean
13
- debugFrameDrag?: boolean
14
- }): IDragAction[] {
15
- return [
16
- new SplitAction(undefined, undefined, undefined, { debug: config.debugSplit }),
17
- new CloseAction(undefined, undefined, undefined, { debug: config.debugClose }),
18
- new FrameDragAction(undefined, undefined, undefined, { debug: config.debugFrameDrag })
19
- ]
20
- }