@witchcraft/layout 0.1.3 → 0.2.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 (189) hide show
  1. package/README.md +27 -24
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/FrameDragHandle.d.vue.ts +15 -0
  4. package/dist/runtime/components/FrameDragHandle.vue +28 -0
  5. package/dist/runtime/components/FrameDragHandle.vue.d.ts +15 -0
  6. package/dist/runtime/components/LayoutDecos.d.vue.ts +2 -4
  7. package/dist/runtime/components/LayoutDecos.vue +10 -29
  8. package/dist/runtime/components/LayoutDecos.vue.d.ts +2 -4
  9. package/dist/runtime/components/LayoutEdges.d.vue.ts +3 -3
  10. package/dist/runtime/components/LayoutEdges.vue +8 -8
  11. package/dist/runtime/components/LayoutEdges.vue.d.ts +3 -3
  12. package/dist/runtime/components/LayoutFrame.d.vue.ts +1 -1
  13. package/dist/runtime/components/LayoutFrame.vue +0 -1
  14. package/dist/runtime/components/LayoutFrame.vue.d.ts +1 -1
  15. package/dist/runtime/components/LayoutShapeSquare.d.vue.ts +3 -1
  16. package/dist/runtime/components/LayoutShapeSquare.vue.d.ts +3 -1
  17. package/dist/runtime/components/LayoutWindow.d.vue.ts +26 -12
  18. package/dist/runtime/components/LayoutWindow.vue +95 -84
  19. package/dist/runtime/components/LayoutWindow.vue.d.ts +26 -12
  20. package/dist/runtime/composables/useFrames.d.ts +15 -13
  21. package/dist/runtime/composables/useFrames.js +59 -39
  22. package/dist/runtime/demo/App.vue +116 -30
  23. package/dist/runtime/demo/DemoControls.d.vue.ts +4 -1
  24. package/dist/runtime/demo/DemoControls.vue +98 -4
  25. package/dist/runtime/demo/DemoControls.vue.d.ts +4 -1
  26. package/dist/runtime/drag/CloseAction.d.ts +26 -5
  27. package/dist/runtime/drag/CloseAction.js +87 -40
  28. package/dist/runtime/drag/DragActionHandler.d.ts +20 -8
  29. package/dist/runtime/drag/DragActionHandler.js +47 -12
  30. package/dist/runtime/drag/FrameDragAction.d.ts +45 -0
  31. package/dist/runtime/drag/FrameDragAction.js +143 -0
  32. package/dist/runtime/drag/SplitAction.d.ts +32 -11
  33. package/dist/runtime/drag/SplitAction.js +82 -24
  34. package/dist/runtime/drag/createDefaultHandlers.d.ts +9 -0
  35. package/dist/runtime/drag/createDefaultHandlers.js +10 -0
  36. package/dist/runtime/drag/defaultDragActions.d.ts +9 -0
  37. package/dist/runtime/drag/defaultDragActions.js +10 -0
  38. package/dist/runtime/drag/types.d.ts +82 -13
  39. package/dist/runtime/drag/types.js +1 -0
  40. package/dist/runtime/helpers/createZoneSideClipPath.d.ts +12 -0
  41. package/dist/runtime/helpers/createZoneSideClipPath.js +17 -0
  42. package/dist/runtime/helpers/doEdgesOverlap.d.ts +3 -1
  43. package/dist/runtime/helpers/doEdgesOverlap.js +5 -5
  44. package/dist/runtime/helpers/getDockBoundaries.d.ts +19 -0
  45. package/dist/runtime/helpers/getDockBoundaries.js +14 -0
  46. package/dist/runtime/helpers/getEdgeLength.d.ts +2 -0
  47. package/dist/runtime/helpers/getEdgeLength.js +5 -0
  48. package/dist/runtime/helpers/getIntersections.js +2 -2
  49. package/dist/runtime/helpers/getIntersectionsCss.js +2 -2
  50. package/dist/runtime/helpers/getMoveEdgeInfo.js +2 -2
  51. package/dist/runtime/helpers/getResizeLimit.js +2 -2
  52. package/dist/runtime/helpers/getShapeSquareCss.js +2 -2
  53. package/dist/runtime/helpers/getVisualEdgeCss.js +2 -2
  54. package/dist/runtime/helpers/getVisualEdges.d.ts +1 -1
  55. package/dist/runtime/helpers/getVisualEdges.js +4 -3
  56. package/dist/runtime/helpers/index.d.ts +4 -0
  57. package/dist/runtime/helpers/index.js +4 -0
  58. package/dist/runtime/helpers/isEdgeEqual.js +2 -4
  59. package/dist/runtime/helpers/isWindowEdge.js +2 -2
  60. package/dist/runtime/helpers/isWindowEdgePoint.js +2 -2
  61. package/dist/runtime/helpers/moveEdge.js +2 -2
  62. package/dist/runtime/helpers/numberToScaledPercent.d.ts +1 -1
  63. package/dist/runtime/helpers/numberToScaledPercent.js +2 -2
  64. package/dist/runtime/helpers/numberToScaledSize.js +2 -2
  65. package/dist/runtime/helpers/rotateFrames.d.ts +7 -0
  66. package/dist/runtime/helpers/rotateFrames.js +36 -0
  67. package/dist/runtime/helpers/scaledPointToPx.d.ts +13 -0
  68. package/dist/runtime/helpers/scaledPointToPx.js +7 -0
  69. package/dist/runtime/helpers/toWindowCoord.js +2 -2
  70. package/dist/runtime/layout/applyFrameChanges.d.ts +10 -0
  71. package/dist/runtime/layout/applyFrameChanges.js +29 -0
  72. package/dist/runtime/layout/createSplitDecoFromDrag.d.ts +6 -1
  73. package/dist/runtime/layout/createSplitDecoFromDrag.js +4 -4
  74. package/dist/runtime/layout/createSplitDecoShapes.d.ts +7 -0
  75. package/dist/runtime/layout/{createSplitDecoEdge.js → createSplitDecoShapes.js} +6 -3
  76. package/dist/runtime/layout/debugFrame.js +2 -1
  77. package/dist/runtime/layout/findSafeSplitEdge.js +2 -2
  78. package/dist/runtime/layout/frameCreate.js +2 -2
  79. package/dist/runtime/layout/getCloseFrameInfo.d.ts +7 -6
  80. package/dist/runtime/layout/getCloseFrameInfo.js +10 -3
  81. package/dist/runtime/layout/getDragZones.d.ts +8 -0
  82. package/dist/runtime/layout/getDragZones.js +32 -0
  83. package/dist/runtime/layout/getFillEmptySpaceInfo.d.ts +65 -0
  84. package/dist/runtime/layout/getFillEmptySpaceInfo.js +69 -0
  85. package/dist/runtime/layout/getFrameCollapseInfo.d.ts +13 -0
  86. package/dist/runtime/layout/getFrameCollapseInfo.js +93 -0
  87. package/dist/runtime/layout/getFrameDockInfo.d.ts +9 -0
  88. package/dist/runtime/layout/getFrameDockInfo.js +82 -0
  89. package/dist/runtime/layout/getFrameDragZones.d.ts +16 -0
  90. package/dist/runtime/layout/getFrameDragZones.js +74 -0
  91. package/dist/runtime/layout/getFrameRearrangeInfo.d.ts +139 -0
  92. package/dist/runtime/layout/getFrameRearrangeInfo.js +87 -0
  93. package/dist/runtime/layout/getFrameSplitInfo.d.ts +7 -5
  94. package/dist/runtime/layout/getFrameSplitInfo.js +10 -3
  95. package/dist/runtime/layout/getFrameSwapInfo.d.ts +9 -0
  96. package/dist/runtime/layout/getFrameSwapInfo.js +27 -0
  97. package/dist/runtime/layout/getFrameTo.js +2 -2
  98. package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +12 -0
  99. package/dist/runtime/layout/getFrameUncollapseInfo.js +88 -0
  100. package/dist/runtime/layout/getFrameUndockInfo.d.ts +13 -0
  101. package/dist/runtime/layout/getFrameUndockInfo.js +51 -0
  102. package/dist/runtime/layout/getFramesRedistributeInfo.d.ts +29 -0
  103. package/dist/runtime/layout/getFramesRedistributeInfo.js +53 -0
  104. package/dist/runtime/layout/getWindowDragZones.d.ts +6 -0
  105. package/dist/runtime/layout/getWindowDragZones.js +49 -0
  106. package/dist/runtime/layout/index.d.ts +14 -5
  107. package/dist/runtime/layout/index.js +14 -5
  108. package/dist/runtime/layout/isPointInRect.d.ts +7 -0
  109. package/dist/runtime/layout/{isPointInFrame.js → isPointInRect.js} +1 -1
  110. package/dist/runtime/layout/resizeFrame.js +2 -2
  111. package/dist/runtime/settings.d.ts +41 -16
  112. package/dist/runtime/settings.js +95 -53
  113. package/dist/runtime/types/index.d.ts +324 -54
  114. package/dist/runtime/types/index.js +54 -20
  115. package/package.json +28 -29
  116. package/src/runtime/components/FrameDragHandle.vue +30 -0
  117. package/src/runtime/components/LayoutDecos.vue +12 -36
  118. package/src/runtime/components/LayoutEdges.vue +27 -23
  119. package/src/runtime/components/LayoutFrame.vue +6 -5
  120. package/src/runtime/components/LayoutShapeSquare.vue +9 -3
  121. package/src/runtime/components/LayoutWindow.vue +110 -101
  122. package/src/runtime/composables/useFrames.ts +80 -50
  123. package/src/runtime/demo/App.vue +126 -36
  124. package/src/runtime/demo/DemoControls.vue +115 -6
  125. package/src/runtime/drag/CloseAction.ts +106 -44
  126. package/src/runtime/drag/DragActionHandler.ts +71 -20
  127. package/src/runtime/drag/FrameDragAction.ts +202 -0
  128. package/src/runtime/drag/SplitAction.ts +106 -34
  129. package/src/runtime/drag/createDefaultHandlers.ts +19 -0
  130. package/src/runtime/drag/defaultDragActions.ts +19 -0
  131. package/src/runtime/drag/types.ts +90 -20
  132. package/src/runtime/helpers/createZoneSideClipPath.ts +41 -0
  133. package/src/runtime/helpers/doEdgesOverlap.ts +11 -5
  134. package/src/runtime/helpers/getDockBoundaries.ts +36 -0
  135. package/src/runtime/helpers/getEdgeLength.ts +10 -0
  136. package/src/runtime/helpers/getIntersections.ts +2 -2
  137. package/src/runtime/helpers/getIntersectionsCss.ts +2 -2
  138. package/src/runtime/helpers/getMoveEdgeInfo.ts +2 -2
  139. package/src/runtime/helpers/getResizeLimit.ts +2 -2
  140. package/src/runtime/helpers/getShapeSquareCss.ts +2 -2
  141. package/src/runtime/helpers/getVisualEdgeCss.ts +2 -2
  142. package/src/runtime/helpers/getVisualEdges.ts +5 -4
  143. package/src/runtime/helpers/index.ts +4 -0
  144. package/src/runtime/helpers/isEdgeEqual.ts +2 -4
  145. package/src/runtime/helpers/isWindowEdge.ts +2 -2
  146. package/src/runtime/helpers/isWindowEdgePoint.ts +2 -2
  147. package/src/runtime/helpers/moveEdge.ts +2 -2
  148. package/src/runtime/helpers/numberToScaledPercent.ts +3 -3
  149. package/src/runtime/helpers/numberToScaledSize.ts +2 -2
  150. package/src/runtime/helpers/rotateFrames.ts +45 -0
  151. package/src/runtime/helpers/scaledPointToPx.ts +13 -0
  152. package/src/runtime/helpers/toWindowCoord.ts +2 -2
  153. package/src/runtime/layout/applyFrameChanges.ts +39 -0
  154. package/src/runtime/layout/createSplitDecoFromDrag.ts +12 -6
  155. package/src/runtime/layout/{createSplitDecoEdge.ts → createSplitDecoShapes.ts} +17 -7
  156. package/src/runtime/layout/debugFrame.ts +1 -1
  157. package/src/runtime/layout/findSafeSplitEdge.ts +3 -3
  158. package/src/runtime/layout/frameCreate.ts +2 -2
  159. package/src/runtime/layout/getCloseFrameInfo.ts +21 -8
  160. package/src/runtime/layout/getDragZones.ts +48 -0
  161. package/src/runtime/layout/getFillEmptySpaceInfo.ts +177 -0
  162. package/src/runtime/layout/getFrameCollapseInfo.ts +164 -0
  163. package/src/runtime/layout/getFrameDockInfo.ts +126 -0
  164. package/src/runtime/layout/getFrameDragZones.ts +100 -0
  165. package/src/runtime/layout/getFrameRearrangeInfo.ts +261 -0
  166. package/src/runtime/layout/getFrameSplitInfo.ts +21 -8
  167. package/src/runtime/layout/getFrameSwapInfo.ts +45 -0
  168. package/src/runtime/layout/getFrameTo.ts +2 -2
  169. package/src/runtime/layout/getFrameUncollapseInfo.ts +160 -0
  170. package/src/runtime/layout/getFrameUndockInfo.ts +97 -0
  171. package/src/runtime/layout/getFramesRedistributeInfo.ts +98 -0
  172. package/src/runtime/layout/getWindowDragZones.ts +59 -0
  173. package/src/runtime/layout/index.ts +14 -5
  174. package/src/runtime/layout/isPointInRect.ts +7 -0
  175. package/src/runtime/layout/resizeFrame.ts +2 -2
  176. package/src/runtime/settings.ts +69 -49
  177. package/src/runtime/types/index.ts +143 -28
  178. package/dist/runtime/layout/closeFrame.d.ts +0 -5
  179. package/dist/runtime/layout/closeFrame.js +0 -13
  180. package/dist/runtime/layout/closeFrames.d.ts +0 -2
  181. package/dist/runtime/layout/closeFrames.js +0 -8
  182. package/dist/runtime/layout/createSplitDecoEdge.d.ts +0 -2
  183. package/dist/runtime/layout/frameSplit.d.ts +0 -16
  184. package/dist/runtime/layout/frameSplit.js +0 -9
  185. package/dist/runtime/layout/isPointInFrame.d.ts +0 -2
  186. package/src/runtime/layout/closeFrame.ts +0 -33
  187. package/src/runtime/layout/closeFrames.ts +0 -14
  188. package/src/runtime/layout/frameSplit.ts +0 -31
  189. package/src/runtime/layout/isPointInFrame.ts +0 -7
@@ -1,12 +1,18 @@
1
- import type { Direction, Edge, IntersectionEntry, LayoutFrame, LayoutWindow, Orientation, Point } from "../types/index.js"
1
+ import type { ComputedRef, InjectionKey, Ref } from "vue"
2
+
3
+ import type { Direction, Edge, FrameId, IntersectionEntry, LayoutFrame, LayoutShape, LayoutWindow, Orientation, Point } from "../types/index.js"
2
4
 
3
5
  export type DragState = {
4
6
  /** The current directions in the corresponding orientations that the user is dragging in. */
5
7
  dragDirections: Record<Orientation, Direction | undefined>
6
8
  /** The curren point (in scaled window coordinates) the user is dragging at. */
7
9
  dragPoint?: Point
8
- /** Whether the user is currently dragging. Is true during all drag events. */
9
- isDragging: boolean
10
+ /** Whether the user is currently dragging and what type of drag. Is truthy string during all drag events. */
11
+ isDragging: boolean | "frame" | "edge"
12
+ /** Whether to show the moved frames while dragging. */
13
+ showDragging: boolean
14
+ /** The frame being dragged during a frame drag. Only set when isDragging is "frame". */
15
+ draggingFrameId?: FrameId
10
16
  /**
11
17
  * The edges that are currently being dragged. There are multiple edges if they drag an intersection since what's actually happening is we're just dragging the closest horizontal and vertical edges.
12
18
  */
@@ -46,18 +52,31 @@ export type DragState = {
46
52
  win: LayoutWindow
47
53
  }
48
54
 
55
+
56
+ export interface ActionDragChangeResult {
57
+ /** Whether the drag should update the edges. Defaults to false. */
58
+ updateEdges?: boolean
59
+ /** Deco shapes produced by this action during this drag step. */
60
+ shapes: LayoutShape[]
61
+ /** Whether to show the moved edges while dragging. Defaults to true. */
62
+ showDragging?: boolean
63
+ }
64
+ export type DragChangeResult = Omit<ActionDragChangeResult, "shapes">
65
+
49
66
  /**
50
- * Note that the return type only affects the `move` event but is also typed as `boolean` for other events for ease of use.
67
+ * Called when the drag coordinates change (during any event).
68
+ *
69
+ * Should return `{ allowed: true/false, shapes: LayoutShape[] }` to control whether the action is allowed and edges update and what deco shapes to render.
70
+ *
71
+ * Note that the allowed return type only affect the `move` event but is also typed as `boolean` for other events for ease of use.
51
72
  */
52
- export interface DragChangeHandler {
53
- <T extends "start" | "move" | "end">(
54
- type: T,
55
- e: T extends "end" ? PointerEvent | undefined : PointerEvent,
56
- state: DragState,
57
- forceRecalculateEdges: () => void,
58
- cancel: () => void
59
- ): boolean | undefined
60
- }
73
+ export type DragChangeHandler = <T extends "start" | "move" | "end">(
74
+ type: T,
75
+ e: T extends "end" ? PointerEvent | undefined : PointerEvent,
76
+ state: DragState,
77
+ forceRecalculateEdges: () => void,
78
+ cancel: (e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void
79
+ ) => ActionDragChangeResult
61
80
 
62
81
  /**
63
82
  * A drag action describes when and how to handle a drag event.
@@ -70,12 +89,6 @@ export interface DragChangeHandler {
70
89
  export interface IDragAction {
71
90
  /** A unique name for your action. */
72
91
  name: string
73
- /**
74
- * Called when the drag coordinates change (during any event). Should return true to allow the edges to be moved, or false to prevent it.
75
- *
76
- * Can be used to save some context/info to later apply safely during onDragApply.
77
- */
78
-
79
92
  onDragChange: DragChangeHandler
80
93
  /**
81
94
  *
@@ -103,5 +116,62 @@ export interface IDragAction {
103
116
  *
104
117
  * You should reset your state here.
105
118
  */
106
- cancel(): void
119
+ cancel(e: PointerEvent | KeyboardEvent | undefined, state: DragState): void
120
+ /**
121
+ * Plugins should implement some basic debug logs by calling {@link DragActionHandler.debugState } at least before and after applying actions in onDragApply. Debug can be a string because it can be an object key to filter on (see the debugState function).
122
+ *
123
+ *
124
+ * Calls look like this usually, where this.state is the plugin state:
125
+ * ```ts
126
+ * DragActionHandler.debugState(this.name, "before", state, this.state, this.debug)
127
+ * ```
128
+ */
129
+ debug: boolean | string
130
+ /**
131
+ * The action handler will call this regardless of whether the action is active or not.
132
+ *
133
+ * Can be used by actions to return display hints.
134
+ *
135
+ * Actions should keep the state of the hints locally and update them in canHandlerRequest/onDrag*, etc. and only use this to return the state of the actions, not update them as that could become expensive.
136
+ */
137
+ // todo think of a better way to do this, don't like that the action handler has to construct a bunch of objects on every change
138
+ getTextHints(type: "start" | "move" | "end"): {
139
+ /** Hint texts to display regarding the action state/usage. Undefined means no hint. */
140
+ actions?: string[]
141
+ /** Error texts/hints to display when the action produces an error. */
142
+ errors?: string[]
143
+ }
107
144
  }
145
+ export type EdgeDragStartData = { edge?: Edge, intersection?: IntersectionEntry }
146
+ export type FrameDragStartData = { frameId: FrameId }
147
+
148
+ // drag start overloads for triggering dragsj
149
+ export type DragStartFn = {
150
+ (e: PointerEvent, type: "edge", data: EdgeDragStartData): void
151
+ (e: PointerEvent, type: "frame", data: FrameDragStartData): void
152
+ }
153
+
154
+ export interface UseFramesContext {
155
+ dragStart: DragStartFn
156
+ dragMove: (e: PointerEvent) => void
157
+ dragEnd: (e?: PointerEvent, options?: { apply?: boolean }) => void
158
+ cancel: () => void
159
+ dragDirections: Ref<Record<Orientation, Direction | undefined>>
160
+ dragPoint: Ref<Point | undefined>
161
+ isDragging: Ref<false | "frame" | "edge">
162
+ draggingEdges: Ref<Edge[]>
163
+ draggingIntersection: Ref<IntersectionEntry | undefined>
164
+ visualEdges: Ref<Edge[]>
165
+ touchingFrames: Ref<Record<string, LayoutFrame>[]>
166
+ touchingFramesArrays: Ref<LayoutFrame[][]>
167
+ frames: ComputedRef<Record<string, LayoutFrame>>
168
+ dragHoveredFrame: ComputedRef<LayoutFrame | undefined>
169
+ intersections: ComputedRef<IntersectionEntry[]>
170
+ isDraggingFromWindowEdge: Ref<boolean>
171
+ forceRecalculateEdges: () => void
172
+ state: ComputedRef<DragState>
173
+ frameDragFrameId: Ref<FrameId | undefined>
174
+ }
175
+
176
+
177
+ export const dragContextInjectionKey = Symbol("dragContext") as InjectionKey<UseFramesContext>
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Get the clip-path polygon for a zone's side. Ends the shape at a 45° angle.
3
+ *
4
+ * e.g. for a right zone, it looks like it's cutting the top and bottom left corners.
5
+ *
6
+ * Accepts scaled coordinates.
7
+ */
8
+
9
+ import type { DragZone } from "../types/index.js"
10
+
11
+ export function createZoneSideClipPath(
12
+ zone: DragZone,
13
+ { frameEdgePx, windowEdgePx }: { frameEdgePx: number, windowEdgePx: number }
14
+ ): string | undefined {
15
+ const side = zone.side
16
+ const thicknessPx = zone.type === "window" ? windowEdgePx : frameEdgePx
17
+ const pxLength = zone.side === "center"
18
+ ? 0
19
+ : zone.side === "right" || zone.side === "left"
20
+ ? zone.pxHeight
21
+ : zone.pxWidth
22
+
23
+ if (side === "center") return undefined
24
+
25
+ const t = thicknessPx
26
+ const l = pxLength
27
+
28
+ if (side === "bottom")
29
+ return `polygon(${t}px 0px, ${l - t}px 0px, ${l}px ${t}px, 0px ${t}px)`
30
+
31
+ if (side === "left")
32
+ return `polygon(0px 0px, ${t}px ${t}px, ${t}px ${l - t}px, 0px ${l}px)`
33
+
34
+ if (side === "top")
35
+ return `polygon(0px 0px, ${t}px ${t}px, ${l - t}px ${t}px, ${l}px 0px)`
36
+
37
+ if (side === "right")
38
+ return `polygon(${t}px 0px, 0px ${t}px, 0px ${l - t}px, ${t}px ${l}px)`
39
+
40
+ return undefined
41
+ }
@@ -4,22 +4,28 @@ import { isEdgeParallel } from "./isEdgeParallel.js"
4
4
 
5
5
  import type { Edge, Orientation } from "../types/index.js"
6
6
 
7
- export function doEdgesOverlap(edgeA: Edge, edgeB: Edge, dir?: Orientation): boolean {
7
+ export function doEdgesOverlap(
8
+ edgeA: Edge,
9
+ edgeB: Edge,
10
+ dir?: Orientation,
11
+ /** If you pass inclusive true it will also match edges that start where the other edge ends or vice versa. */
12
+ inclusive: boolean = false
13
+ ): boolean {
8
14
  dir ??= getEdgeOrientation(edgeA)
9
15
  if (!isEdgeParallel(edgeA, edgeB)) return false
10
16
  if (dir === "horizontal") {
11
17
  if (edgeA.startY !== edgeB.startY) return false
12
18
  const leftMost = edgeA.startX <= edgeB.startX ? edgeA : edgeB
13
19
  const rightMost = edgeB === leftMost ? edgeA : edgeB
14
- const startOverlaps = inRange(leftMost.endX, rightMost.startX, rightMost.endX)
15
- const endOverlaps = inRange(rightMost.startX, leftMost.startX, leftMost.endX)
20
+ const startOverlaps = inRange(leftMost.endX, rightMost.startX, rightMost.endX, inclusive)
21
+ const endOverlaps = inRange(rightMost.startX, leftMost.startX, leftMost.endX, inclusive)
16
22
  return startOverlaps || endOverlaps
17
23
  } else {
18
24
  if (edgeA.startX !== edgeB.startX) return false
19
25
  const topMost = edgeA.startY <= edgeB.startY ? edgeA : edgeB
20
26
  const bottomMost = edgeB === topMost ? edgeA : edgeB
21
- const startOverlaps = inRange(topMost.endY, bottomMost.startY, bottomMost.endY)
22
- const endOverlaps = inRange(bottomMost.startY, topMost.startY, topMost.endY)
27
+ const startOverlaps = inRange(topMost.endY, bottomMost.startY, bottomMost.endY, inclusive)
28
+ const endOverlaps = inRange(bottomMost.startY, topMost.startY, topMost.endY, inclusive)
23
29
  return startOverlaps || endOverlaps
24
30
  }
25
31
  }
@@ -0,0 +1,36 @@
1
+ import { settings } from "../settings.js"
2
+ import type { LayoutWindow } from "../types/index.js"
3
+
4
+ /**
5
+ * Compute the inner boundaries of a docked region by looking at already docked frames.
6
+ *
7
+ * For example, if A, B, and C are docked, this returns the area of C.
8
+ * ┌─────────┐
9
+ * │A* │
10
+ * ├────┬────┤
11
+ * │B* │C │
12
+ * ├────┴────┤
13
+ * │D* │
14
+ * └─────────┘
15
+ */
16
+ export function getDockBoundaries(win: LayoutWindow): {
17
+ minX: number
18
+ maxX: number
19
+ minY: number
20
+ maxY: number
21
+ } {
22
+ let minX = 0
23
+ let maxX = settings.maxInt
24
+ let minY = 0
25
+ let maxY = settings.maxInt
26
+
27
+ for (const f of Object.values(win.frames)) {
28
+ if (f.docked === "left") minX = Math.max(minX, f.x + f.width)
29
+ if (f.docked === "right") maxX = Math.min(maxX, f.x)
30
+ if (f.docked === "top") minY = Math.max(minY, f.y + f.height)
31
+ if (f.docked === "bottom") maxY = Math.min(maxY, f.y)
32
+ }
33
+
34
+ return { minX, maxX, minY, maxY }
35
+ }
36
+
@@ -0,0 +1,10 @@
1
+ import { getEdgeOrientation } from "./getEdgeOrientation.js"
2
+
3
+ import type { Edge } from "../types/index.js"
4
+
5
+ export function getEdgeLength(edge: Edge): number {
6
+ const orientation = getEdgeOrientation(edge)
7
+ return orientation === "horizontal"
8
+ ? Math.abs(edge.endX - edge.startX)
9
+ : Math.abs(edge.endY - edge.startY)
10
+ }
@@ -6,7 +6,7 @@ import { edgeToPoints } from "./edgeToPoints.js"
6
6
  import { getEdgeOrientation } from "./getEdgeOrientation.js"
7
7
  import { inRange } from "./inRange.js"
8
8
 
9
- import { getMaxInt } from "../settings.js"
9
+ import { settings } from "../settings.js"
10
10
  import type {
11
11
  Edge, IntersectionEntry,
12
12
  Orientation,
@@ -25,7 +25,7 @@ export function getIntersections(
25
25
  }
26
26
 
27
27
  const points: IntersectionEntry[] = []
28
- const maxInt = getMaxInt()
28
+ const maxInt = settings.maxInt
29
29
  for (const x of keys(intersections)) {
30
30
  for (const y of keys(intersections[x])) {
31
31
  // careful, x and y are really strings
@@ -1,4 +1,4 @@
1
- import { getMaxInt } from "../settings.js"
1
+ import { settings } from "../settings.js"
2
2
  import type {
3
3
  IntersectionEntry, PointCss
4
4
  } from "../types/index.js"
@@ -13,7 +13,7 @@ export function getIntersectionsCss(entries: IntersectionEntry[],
13
13
  shiftAmount?: string
14
14
  } = {}
15
15
  ): (PointCss & { _shifted?: true }) [] {
16
- const unscale = getMaxInt() / 100
16
+ const unscale = settings.maxInt / 100
17
17
  return entries.map(entry => {
18
18
  const point = entry.point
19
19
  const css = {
@@ -2,7 +2,7 @@ import { clampNumber } from "./clampNumber.js"
2
2
  import { getEdgeOrientation } from "./getEdgeOrientation.js"
3
3
  import { getResizeLimit } from "./getResizeLimit.js"
4
4
 
5
- import { getMarginSize } from "../settings.js"
5
+ import { settings } from "../settings.js"
6
6
  import type {
7
7
  Direction,
8
8
  Edge, LayoutFrame,
@@ -14,7 +14,7 @@ export function getMoveEdgeInfo(
14
14
  edge: Edge,
15
15
  /** Window scaled/snaped position. See {@link toWindowCoord} */
16
16
  position: Point,
17
- margin: Size = getMarginSize(),
17
+ margin: Size = settings.minSizeScaled,
18
18
  clamp = true
19
19
  ): {
20
20
  x: number
@@ -1,7 +1,7 @@
1
1
  import { castType } from "@alanscodelog/utils/castType"
2
2
  import { keys } from "@alanscodelog/utils/keys"
3
3
 
4
- import { getMaxInt } from "../settings.js"
4
+ import { settings } from "../settings.js"
5
5
  import type {
6
6
  Direction,
7
7
  Edge, ExtendedDirection, LayoutFrame
@@ -51,7 +51,7 @@ export function getResizeLimit<TDir extends ExtendedDirection>(
51
51
  for (const key of keys(limits)) {
52
52
  // this happens when we drag window edges
53
53
  if (limits[key] === -Infinity) limits[key] = 0
54
- if (limits[key] === Infinity) limits[key] = getMaxInt()
54
+ if (limits[key] === Infinity) limits[key] = settings.maxInt
55
55
 
56
56
  limits[key]! += (dir === "left" || dir === "up" ? margin : -margin)
57
57
  }
@@ -1,4 +1,4 @@
1
- import { getMaxInt } from "../settings.js"
1
+ import { settings } from "../settings.js"
2
2
  import type { BaseSquare } from "../types/index.js"
3
3
 
4
4
  export function getShapeSquareCss(
@@ -10,7 +10,7 @@ export function getShapeSquareCss(
10
10
  width: string
11
11
  height: string
12
12
  } {
13
- const unscale = getMaxInt() / 100
13
+ const unscale = settings.maxInt / 100
14
14
  const css = {
15
15
  x: `${obj.x / unscale}%`,
16
16
  y: `${obj.y / unscale}%`,
@@ -1,6 +1,6 @@
1
1
  import { getEdgeOrientation } from "./getEdgeOrientation.js"
2
2
 
3
- import { getMaxInt } from "../settings.js"
3
+ import { settings } from "../settings.js"
4
4
  import type {
5
5
  Edge,
6
6
  EdgeCss } from "../types/index.js"
@@ -20,7 +20,7 @@ export function getVisualEdgeCss(
20
20
  } = {}
21
21
  ): EdgeCss {
22
22
  const dir = getEdgeOrientation(edge)
23
- const unscale = getMaxInt() / 100
23
+ const unscale = settings.maxInt / 100
24
24
  const w = (edge.endX - edge.startX) / unscale
25
25
  const h = (edge.endY - edge.startY) / unscale
26
26
  const width = dir === "vertical" ? edgeWidth : `${w}%`
@@ -10,7 +10,7 @@ import { inRange } from "./inRange.js"
10
10
  import { splitEdge } from "./splitEdge.js"
11
11
  import { unionEdges } from "./unionEdges.js"
12
12
 
13
- import { getMaxInt } from "../settings.js"
13
+ import { settings } from "../settings.js"
14
14
  import type {
15
15
  Edge,
16
16
  Intersections,
@@ -18,7 +18,7 @@ import type {
18
18
  } from "../types/index.js"
19
19
 
20
20
  /**
21
- * Returns the "visual" edges that can be dragged.
21
+ * Returns the "visual" edges that can be dragged (so will not return edges for collapsed frames without some area).
22
22
  *
23
23
  * Visual edges are a combination of all edges shared by frames that must be moved together.
24
24
  *
@@ -75,8 +75,9 @@ export function getVisualEdges<T extends boolean = false>(
75
75
  const intersections: Intersections = {
76
76
  // x: {y: count}
77
77
  }
78
- const max = getMaxInt()
78
+ const max = settings.maxInt
79
79
  for (const frame of frames) {
80
+ if (frame.collapsed && (frame.width === 0 || frame.height === 0)) continue
80
81
  const frameEdges = frameToEdges(frame)
81
82
  addPointsToIntersection(intersections, Object.values(frameToPoints(frame)))
82
83
 
@@ -94,7 +95,7 @@ export function getVisualEdges<T extends boolean = false>(
94
95
  if (containsEdge(edge, e, dir)) {
95
96
  continue secondlabel
96
97
  }
97
- if (doEdgesOverlap(e, edge, dir)) {
98
+ if (doEdgesOverlap(e, edge, dir, true)) {
98
99
  indexes.push(i)
99
100
  edges.push(e)
100
101
  }
@@ -15,6 +15,7 @@ export { containsEdge } from "./containsEdge.js"
15
15
  export { convertLayoutWindowToWorkspace } from "./convertLayoutWindowToWorkspace.js"
16
16
  export { copySize } from "./copySize.js"
17
17
  export { createEdge } from "./createEdge.js"
18
+ export { createZoneSideClipPath } from "./createZoneSideClipPath.js"
18
19
  export { dirToOrientation } from "./dirToOrientation.js"
19
20
  export { dirToSide } from "./dirToSide.js"
20
21
  export { doEdgesOverlap } from "./doEdgesOverlap.js"
@@ -24,6 +25,7 @@ export { findDraggableEdge } from "./findDraggableEdge.js"
24
25
  export { findFrameDraggableEdges } from "./findFrameDraggableEdges.js"
25
26
  export { frameToEdges } from "./frameToEdges.js"
26
27
  export { frameToPoints } from "./frameToPoints.js"
28
+ export { getEdgeLength } from "./getEdgeLength.js"
27
29
  export { getEdgeOrientation } from "./getEdgeOrientation.js"
28
30
  export { getEdgeSharedDirection } from "./getEdgeSharedDirection.js"
29
31
  export { getEdgeSide } from "./getEdgeSide.js"
@@ -55,6 +57,7 @@ export { numberToScaledPercent } from "./numberToScaledPercent.js"
55
57
  export { numberToScaledSize } from "./numberToScaledSize.js"
56
58
  export { oppositeSide } from "./oppositeSide.js"
57
59
  export { resizeByEdge } from "./resizeByEdge.js"
60
+ export { scaledPointToPx } from "./scaledPointToPx.js"
58
61
  export { sideToDirection } from "./sideToDirection.js"
59
62
  export { sideToOrientation } from "./sideToOrientation.js"
60
63
  export { splitEdge } from "./splitEdge.js"
@@ -62,3 +65,4 @@ export { toCoord } from "./toCoord.js"
62
65
  export { toId } from "./toId.js"
63
66
  export { toWindowCoord } from "./toWindowCoord.js"
64
67
  export { unionEdges } from "./unionEdges.js"
68
+ export { updateWindowWithEvent } from "./updateWindowSizeWithEvent.js"
@@ -1,14 +1,12 @@
1
1
  import { getEdgeOrientation } from "./getEdgeOrientation.js"
2
- import { isEdgeParallel } from "./isEdgeParallel.js"
3
2
 
4
3
  import type { Edge, Orientation } from "../types/index.js"
5
4
 
6
5
  export function isEdgeEqual(edgeA: Edge, edgeB: Edge, orientation?: Orientation): boolean {
7
6
  orientation ??= getEdgeOrientation(edgeA)
8
- if (!isEdgeParallel(edgeA, edgeB)) return false
9
7
  if (orientation === "horizontal") {
10
- return edgeA.startY === edgeB.startY && edgeB.endY === edgeA.endY
8
+ return edgeA.startX === edgeB.startX && edgeB.endX === edgeA.endX && edgeA.startY === edgeB.startY && edgeB.endY === edgeA.endY
11
9
  } else {
12
- return edgeA.startX === edgeB.startX && edgeB.endX === edgeA.endX
10
+ return edgeA.startX === edgeB.startX && edgeB.endX === edgeA.endX && edgeA.startY === edgeB.startY && edgeB.endY === edgeA.endY
13
11
  }
14
12
  }
@@ -1,11 +1,11 @@
1
1
  import { getEdgeOrientation } from "./getEdgeOrientation.js"
2
2
 
3
- import { getMaxInt } from "../settings.js"
3
+ import { settings } from "../settings.js"
4
4
  import type { Edge, Orientation } from "../types/index.js"
5
5
 
6
6
  export function isWindowEdge(edge: Edge, edgeDirection?: Orientation): boolean {
7
7
  edgeDirection ??= getEdgeOrientation(edge)
8
- const max = getMaxInt()
8
+ const max = settings.maxInt
9
9
  return (edgeDirection === "vertical" && (edge.startX === 0 || edge.startX === max))
10
10
  || (edgeDirection === "horizontal" && (edge.startY === 0 || edge.startY === max))
11
11
  }
@@ -1,8 +1,8 @@
1
- import { getMaxInt } from "../settings.js"
1
+ import { settings } from "../settings.js"
2
2
  import type { Point } from "../types/index.js"
3
3
 
4
4
  export function isWindowEdgePoint(point: Point): boolean {
5
- const max = getMaxInt()
5
+ const max = settings.maxInt
6
6
  return (point.x === 0 || point.x === max)
7
7
  || (point.y === 0 || point.y === max)
8
8
  }
@@ -1,7 +1,7 @@
1
1
  import { getMoveEdgeInfo } from "./getMoveEdgeInfo.js"
2
2
  import { resizeByEdge } from "./resizeByEdge.js"
3
3
 
4
- import { getMarginSize } from "../settings.js"
4
+ import { settings } from "../settings.js"
5
5
  import type {
6
6
  Edge, LayoutFrame,
7
7
  Point,
@@ -13,7 +13,7 @@ export function moveEdge(
13
13
  edge: Edge | undefined,
14
14
  /** Window scaled/snaped position. See {@link toWindowCoord} */
15
15
  position: Point,
16
- margin: Size = getMarginSize()
16
+ margin: Size = settings.minSizeScaled
17
17
  ): void {
18
18
  if (!edge || !touchingFrames) return
19
19
  const { pos, dir, distance } = getMoveEdgeInfo(touchingFrames, edge, position, margin)
@@ -1,9 +1,9 @@
1
1
  import { snapNumber } from "@alanscodelog/utils/snapNumber"
2
2
 
3
- import { getMaxInt } from "../settings.js"
3
+ import { settings } from "../settings.js"
4
4
 
5
5
  /**
6
- * Given a number (e.g. the x coordinate in px), and the max value it could be (e.g. the max width of it's container in px), returns it's position as a scaled percentage.
6
+ * Given a number (e.g. the x coordinate in px), and the max value it could be (e.g. the max width of it's container in px), returns it's position as a rounded scaled percentage.
7
7
  *
8
8
  * ```
9
9
  * -----------
@@ -14,6 +14,6 @@ import { getMaxInt } from "../settings.js"
14
14
  * // returns 50 / 100 * scale or 50000 (50%)
15
15
  * ```
16
16
  */
17
- export function numberToScaledPercent(num: number, max: number, scale: number = getMaxInt()): number {
17
+ export function numberToScaledPercent(num: number, max: number, scale: number = settings.maxInt): number {
18
18
  return snapNumber((num / max) * scale, 1)
19
19
  }
@@ -2,13 +2,13 @@ import { unreachable } from "@alanscodelog/utils/unreachable"
2
2
 
3
3
  import { numberToScaledPercent } from "./numberToScaledPercent.js"
4
4
 
5
- import { getMaxInt } from "../settings.js"
5
+ import { settings } from "../settings.js"
6
6
  import type { LayoutWindow, PxSize, Size } from "../types/index.js"
7
7
 
8
8
  export function numberToScaledSize(
9
9
  win: LayoutWindow,
10
10
  size: PxSize | number,
11
- scale: number = getMaxInt()
11
+ scale: number = settings.maxInt
12
12
  ): Size {
13
13
  const scaledSize = {
14
14
  width: numberToScaledPercent(
@@ -0,0 +1,45 @@
1
+ import { settings } from "../settings.js"
2
+ import type { LayoutFrame } from "../types/index.js"
3
+
4
+ /**
5
+ * Rotates the list of frames given by 90, 180, or 270 degrees around the 50% center.
6
+ *
7
+ * Used mainly in testing to catch issues with incorrect handling of the same scenario in different orientations.
8
+ */
9
+ export function rotateLayout(frames: LayoutFrame[], rotation: 90 | 180 | 270) {
10
+ const sides: NonNullable<LayoutFrame["docked"]>[] = ["top", "right", "bottom", "left"]
11
+ const shift = rotation / 90
12
+
13
+ for (const frame of frames) {
14
+ const { x, y, width, height, docked } = frame
15
+
16
+ if (docked) {
17
+ const currentIndex = sides.indexOf(docked)
18
+ if (currentIndex !== -1) {
19
+ frame.docked = sides[(currentIndex + shift) % 4]
20
+ }
21
+ }
22
+ switch (rotation) {
23
+ case 90:
24
+ frame.x = settings.maxInt - (y + height)
25
+ frame.y = x
26
+ frame.width = height
27
+ frame.height = width
28
+ break
29
+ case 180:
30
+ frame.x = settings.maxInt - (x + width)
31
+ frame.y = settings.maxInt - (y + height)
32
+ frame.width = width
33
+ frame.height = height
34
+ break
35
+ case 270:
36
+ frame.x = y
37
+ frame.y = settings.maxInt - (x + width)
38
+ frame.width = height
39
+ frame.height = width
40
+ break
41
+ default:
42
+ throw new Error(`Unknown rotation ${rotation}`)
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,13 @@
1
+ import { settings } from "../settings.js"
2
+
3
+ /** Convert a scaled point to pixel coords for a given window. */
4
+ export function scaledPointToPx(
5
+ point: { x: number, y: number },
6
+ win: { pxWidth: number, pxHeight: number, pxX: number, pxY: number },
7
+ scale: number = settings.maxInt
8
+ ): { x: number, y: number } {
9
+ return {
10
+ x: point.x / scale * win.pxWidth + win.pxX,
11
+ y: point.y / scale * win.pxHeight + win.pxY
12
+ }
13
+ }
@@ -2,13 +2,13 @@ import { snapNumber } from "@alanscodelog/utils/snapNumber"
2
2
 
3
3
  import { numberToScaledPercent } from "./numberToScaledPercent.js"
4
4
 
5
- import { getSnapPoint } from "../settings.js"
5
+ import { settings } from "../settings.js"
6
6
  import type { LayoutWindow, Point } from "../types/index.js"
7
7
 
8
8
  export function toWindowCoord(
9
9
  win: LayoutWindow,
10
10
  e: Pick<PointerEvent, "clientX" | "clientY">,
11
- snapAmount: Point = getSnapPoint()
11
+ snapAmount: Point = settings.snapPointScaled
12
12
  ): Point {
13
13
  const x = numberToScaledPercent((e.clientX - win.pxX), win.pxWidth)
14
14
  const y = numberToScaledPercent((e.clientY - win.pxY), win.pxHeight)
@@ -0,0 +1,39 @@
1
+ import { LAYOUT_ERROR, type LayoutChange, type LayoutWindow } from "../types/index.js"
2
+ import { KnownError } from "../utils/KnownError.js"
3
+
4
+ /**
5
+ *
6
+ * Most actions that change the layout will return a LayoutChange object without mutating the window.
7
+ *
8
+ * This function applies the changes returned by them.
9
+ *
10
+ * Mutates the frame positions in the window given.
11
+ */
12
+ export function applyFrameChanges(
13
+ win: LayoutWindow,
14
+ change: LayoutChange<any>
15
+ ): void {
16
+ for (const frame of change.modified) {
17
+ const target = win.frames[frame.id]
18
+ if (!target) {
19
+ throw new KnownError(LAYOUT_ERROR.INVALID_ID, `Frame ${frame.id} not found in window ${win.id}`, { id: frame.id })
20
+ }
21
+ if ("docked" in frame) { target.docked = frame.docked }
22
+ if ("collapsed" in frame) { target.collapsed = frame.collapsed }
23
+ target.x = frame.x
24
+ target.y = frame.y
25
+ target.width = frame.width
26
+ target.height = frame.height
27
+ }
28
+
29
+ for (const frame of change.created) {
30
+ if (win.frames[frame.id]) {
31
+ throw new KnownError(LAYOUT_ERROR.ID_ALREADY_EXISTS, `Frame ${frame.id} already exists in window ${win.id}`, { id: frame.id })
32
+ }
33
+ win.frames[frame.id] = frame
34
+ }
35
+
36
+ for (const frame of change.deleted) {
37
+ delete win.frames[frame.id]
38
+ }
39
+ }