@witchcraft/layout 0.3.0 → 0.4.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 (167) hide show
  1. package/README.md +79 -74
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +6 -1
  4. package/dist/runtime/components/FrameDragHandle.vue +1 -1
  5. package/dist/runtime/components/LayoutDecosEdges.d.vue.ts +15 -0
  6. package/dist/runtime/components/LayoutDecosEdges.vue +34 -0
  7. package/dist/runtime/components/LayoutDecosEdges.vue.d.ts +15 -0
  8. package/dist/runtime/components/LayoutDecosRects.d.vue.ts +15 -0
  9. package/dist/runtime/components/LayoutDecosRects.vue +37 -0
  10. package/dist/runtime/components/LayoutDecosRects.vue.d.ts +15 -0
  11. package/dist/runtime/components/LayoutDragEdgeGrabbed.d.vue.ts +2 -0
  12. package/dist/runtime/components/LayoutDragEdgeGrabbed.vue +43 -0
  13. package/dist/runtime/components/LayoutDragEdgeGrabbed.vue.d.ts +2 -0
  14. package/dist/runtime/components/LayoutDragEdgeHandle.d.vue.ts +9 -0
  15. package/dist/runtime/components/LayoutDragEdgeHandle.vue +44 -0
  16. package/dist/runtime/components/LayoutDragEdgeHandle.vue.d.ts +9 -0
  17. package/dist/runtime/components/{LayoutDecos.d.vue.ts → LayoutDragEdgeVisible.d.vue.ts} +3 -2
  18. package/dist/runtime/components/LayoutDragEdgeVisible.vue +38 -0
  19. package/dist/runtime/components/{LayoutDecos.vue.d.ts → LayoutDragEdgeVisible.vue.d.ts} +3 -2
  20. package/dist/runtime/components/LayoutDragEdges.d.vue.ts +21 -0
  21. package/dist/runtime/components/LayoutDragEdges.vue +67 -0
  22. package/dist/runtime/components/LayoutDragEdges.vue.d.ts +21 -0
  23. package/dist/runtime/components/LayoutEdgesActiveFrame.d.vue.ts +2 -0
  24. package/dist/runtime/components/LayoutEdgesActiveFrame.vue +35 -0
  25. package/dist/runtime/components/LayoutEdgesActiveFrame.vue.d.ts +2 -0
  26. package/dist/runtime/components/LayoutFrame.d.vue.ts +7 -6
  27. package/dist/runtime/components/LayoutFrame.vue +18 -14
  28. package/dist/runtime/components/LayoutFrame.vue.d.ts +7 -6
  29. package/dist/runtime/components/LayoutIntersectionHandle.d.vue.ts +10 -0
  30. package/dist/runtime/components/LayoutIntersectionHandle.vue +36 -0
  31. package/dist/runtime/components/LayoutIntersectionHandle.vue.d.ts +10 -0
  32. package/dist/runtime/components/LayoutIntersectionVisible.d.vue.ts +6 -0
  33. package/dist/runtime/components/LayoutIntersectionVisible.vue +34 -0
  34. package/dist/runtime/components/LayoutIntersectionVisible.vue.d.ts +6 -0
  35. package/dist/runtime/components/LayoutIntersections.d.vue.ts +21 -0
  36. package/dist/runtime/components/LayoutIntersections.vue +52 -0
  37. package/dist/runtime/components/LayoutIntersections.vue.d.ts +21 -0
  38. package/dist/runtime/components/{LayoutShapeSquare.d.vue.ts → LayoutShapeRect.d.vue.ts} +2 -2
  39. package/dist/runtime/components/{LayoutShapeSquare.vue.d.ts → LayoutShapeRect.vue.d.ts} +2 -2
  40. package/dist/runtime/components/LayoutWindow.d.vue.ts +252 -14
  41. package/dist/runtime/components/LayoutWindow.vue +27 -48
  42. package/dist/runtime/components/LayoutWindow.vue.d.ts +252 -14
  43. package/dist/runtime/composables/useFrames.d.ts +859 -2
  44. package/dist/runtime/composables/useFrames.js +2 -0
  45. package/dist/runtime/demo/App.vue +122 -74
  46. package/dist/runtime/demo/DemoControls.vue +7 -5
  47. package/dist/runtime/demo/index.html +12 -0
  48. package/dist/runtime/demo/tailwind.css +1 -1
  49. package/dist/runtime/drag/CloseAction.d.ts +1 -2
  50. package/dist/runtime/drag/CloseAction.js +1 -1
  51. package/dist/runtime/drag/DragActionHandler.d.ts +5 -3
  52. package/dist/runtime/drag/DragActionHandler.js +40 -6
  53. package/dist/runtime/drag/FrameDragAction.js +6 -7
  54. package/dist/runtime/drag/SplitAction.d.ts +1 -2
  55. package/dist/runtime/drag/SplitAction.js +1 -1
  56. package/dist/runtime/drag/createDefaultHandlers.d.ts +1 -1
  57. package/dist/runtime/drag/defaultDragActions.d.ts +1 -1
  58. package/dist/runtime/helpers/consoleDebugWindow.d.ts +43 -0
  59. package/dist/runtime/helpers/consoleDebugWindow.js +222 -0
  60. package/dist/runtime/helpers/convertLayoutWindowToWorkspace.js +2 -2
  61. package/dist/runtime/helpers/framesRedistributeFix.d.ts +87 -0
  62. package/dist/runtime/helpers/framesRedistributeFix.js +54 -0
  63. package/dist/runtime/helpers/getMoveEdgeInfo.d.ts +6 -2
  64. package/dist/runtime/helpers/getMoveEdgeInfo.js +11 -2
  65. package/dist/runtime/helpers/getPinnedEdgesForCollapsedFrames.d.ts +5 -0
  66. package/dist/runtime/helpers/getPinnedEdgesForCollapsedFrames.js +18 -0
  67. package/dist/runtime/helpers/getShapeRectCss.d.ts +7 -0
  68. package/dist/runtime/helpers/{getShapeSquareCss.js → getShapeRectCss.js} +1 -1
  69. package/dist/runtime/helpers/index.d.ts +3 -3
  70. package/dist/runtime/helpers/index.js +3 -3
  71. package/dist/runtime/helpers/moveEdge.js +3 -1
  72. package/dist/runtime/helpers/pxSizeToScaledSize.d.ts +2 -0
  73. package/dist/runtime/helpers/{numberToScaledSize.js → pxSizeToScaledSize.js} +1 -1
  74. package/dist/runtime/helpers/updateWindowSizeWithEvent.d.ts +3 -0
  75. package/dist/runtime/helpers/validateLayoutShape.d.ts +5 -0
  76. package/dist/runtime/helpers/validateLayoutShape.js +28 -0
  77. package/dist/runtime/layout/applyFrameChanges.js +6 -0
  78. package/dist/runtime/layout/createSplitDecoShapes.js +1 -1
  79. package/dist/runtime/layout/getFrameCollapseInfo.d.ts +5 -2
  80. package/dist/runtime/layout/getFrameCollapseInfo.js +16 -46
  81. package/dist/runtime/layout/getFrameDockInfo.d.ts +1 -1
  82. package/dist/runtime/layout/getFrameDockInfo.js +5 -1
  83. package/dist/runtime/layout/getFrameUncollapseInfo.d.ts +5 -2
  84. package/dist/runtime/layout/getFrameUncollapseInfo.js +16 -50
  85. package/dist/runtime/layout/getFramesRedistributeInfo.d.ts +20 -4
  86. package/dist/runtime/layout/getFramesRedistributeInfo.js +31 -2
  87. package/dist/runtime/layout/getRelaxFramesInfo.d.ts +7 -0
  88. package/dist/runtime/layout/getRelaxFramesInfo.js +91 -0
  89. package/dist/runtime/layout/getUpdateWindowSizeInfo.d.ts +13 -0
  90. package/dist/runtime/layout/getUpdateWindowSizeInfo.js +65 -0
  91. package/dist/runtime/layout/index.d.ts +1 -0
  92. package/dist/runtime/layout/index.js +1 -0
  93. package/dist/runtime/layout/resizeFrame.js +1 -3
  94. package/dist/runtime/settings.d.ts +26 -19
  95. package/dist/runtime/settings.js +57 -22
  96. package/dist/runtime/types/index.d.ts +1243 -30
  97. package/dist/runtime/types/index.js +22 -8
  98. package/dist/runtime/types/vue.d.ts +41 -0
  99. package/dist/runtime/{drag/types.js → types/vue.js} +1 -0
  100. package/dist/runtime/utils/cssToKey.d.ts +5 -0
  101. package/dist/runtime/utils/cssToKey.js +3 -0
  102. package/package.json +11 -12
  103. package/src/module.ts +6 -1
  104. package/src/runtime/components/FrameDragHandle.vue +1 -1
  105. package/src/runtime/components/LayoutDecosEdges.vue +38 -0
  106. package/src/runtime/components/LayoutDecosRects.vue +42 -0
  107. package/src/runtime/components/LayoutDragEdgeGrabbed.vue +50 -0
  108. package/src/runtime/components/LayoutDragEdgeHandle.vue +52 -0
  109. package/src/runtime/components/LayoutDragEdgeVisible.vue +45 -0
  110. package/src/runtime/components/LayoutDragEdges.vue +78 -0
  111. package/src/runtime/components/LayoutEdgesActiveFrame.vue +43 -0
  112. package/src/runtime/components/LayoutFrame.vue +22 -15
  113. package/src/runtime/components/LayoutIntersectionHandle.vue +45 -0
  114. package/src/runtime/components/LayoutIntersectionVisible.vue +41 -0
  115. package/src/runtime/components/LayoutIntersections.vue +64 -0
  116. package/src/runtime/components/{LayoutShapeSquare.vue → LayoutShapeRect.vue} +2 -2
  117. package/src/runtime/components/LayoutWindow.vue +29 -49
  118. package/src/runtime/composables/useFrames.ts +7 -2
  119. package/src/runtime/demo/App.vue +128 -78
  120. package/src/runtime/demo/DemoControls.vue +7 -5
  121. package/src/runtime/demo/index.html +12 -0
  122. package/src/runtime/demo/tailwind.css +3 -0
  123. package/src/runtime/drag/CloseAction.ts +2 -3
  124. package/src/runtime/drag/DragActionHandler.ts +48 -10
  125. package/src/runtime/drag/FrameDragAction.ts +4 -5
  126. package/src/runtime/drag/SplitAction.ts +4 -3
  127. package/src/runtime/drag/createDefaultHandlers.ts +2 -1
  128. package/src/runtime/drag/defaultDragActions.ts +2 -1
  129. package/src/runtime/helpers/consoleDebugWindow.ts +331 -0
  130. package/src/runtime/helpers/convertLayoutWindowToWorkspace.ts +2 -2
  131. package/src/runtime/helpers/framesRedistributeFix.ts +162 -0
  132. package/src/runtime/helpers/getMoveEdgeInfo.ts +22 -5
  133. package/src/runtime/helpers/getPinnedEdgesForCollapsedFrames.ts +29 -0
  134. package/src/runtime/helpers/{getShapeSquareCss.ts → getShapeRectCss.ts} +3 -3
  135. package/src/runtime/helpers/index.ts +4 -3
  136. package/src/runtime/helpers/moveEdge.ts +3 -1
  137. package/src/runtime/helpers/{numberToScaledSize.ts → pxSizeToScaledSize.ts} +2 -2
  138. package/src/runtime/helpers/updateWindowSizeWithEvent.ts +3 -0
  139. package/src/runtime/helpers/validateLayoutShape.ts +46 -0
  140. package/src/runtime/layout/applyFrameChanges.ts +5 -0
  141. package/src/runtime/layout/createSplitDecoShapes.ts +1 -1
  142. package/src/runtime/layout/getFrameCollapseInfo.ts +26 -85
  143. package/src/runtime/layout/getFrameDockInfo.ts +8 -1
  144. package/src/runtime/layout/getFrameUncollapseInfo.ts +29 -96
  145. package/src/runtime/layout/getFramesRedistributeInfo.ts +60 -5
  146. package/src/runtime/layout/getRelaxFramesInfo.ts +139 -0
  147. package/src/runtime/layout/getUpdateWindowSizeInfo.ts +105 -0
  148. package/src/runtime/layout/index.ts +1 -0
  149. package/src/runtime/layout/resizeFrame.ts +4 -3
  150. package/src/runtime/settings.ts +78 -25
  151. package/src/runtime/types/index.ts +205 -33
  152. package/src/runtime/types/vue.ts +41 -0
  153. package/src/runtime/utils/cssToKey.ts +8 -0
  154. package/dist/runtime/components/LayoutDecos.vue +0 -26
  155. package/dist/runtime/components/LayoutEdges.d.vue.ts +0 -17
  156. package/dist/runtime/components/LayoutEdges.vue +0 -144
  157. package/dist/runtime/components/LayoutEdges.vue.d.ts +0 -17
  158. package/dist/runtime/drag/types.d.ts +0 -161
  159. package/dist/runtime/helpers/clampNumber.d.ts +0 -1
  160. package/dist/runtime/helpers/clampNumber.js +0 -3
  161. package/dist/runtime/helpers/getShapeSquareCss.d.ts +0 -7
  162. package/dist/runtime/helpers/numberToScaledSize.d.ts +0 -2
  163. package/src/runtime/components/LayoutDecos.vue +0 -28
  164. package/src/runtime/components/LayoutEdges.vue +0 -170
  165. package/src/runtime/drag/types.ts +0 -177
  166. package/src/runtime/helpers/clampNumber.ts +0 -9
  167. /package/dist/runtime/components/{LayoutShapeSquare.vue → LayoutShapeRect.vue} +0 -0
@@ -1,8 +1,35 @@
1
+ import { numberToScaledPercent } from "./helpers/numberToScaledPercent.js"
1
2
  import type { Point, Size } from "./types/index.js"
2
3
 
3
4
  export class Settings {
4
- private _scale = 3
5
- private _maxInt = 100 * (10 ** 3)
5
+ private initialized = false
6
+ constructor() {
7
+ this.resetToDefaults()
8
+ this.initialized = true
9
+ }
10
+
11
+ resetToDefaults() {
12
+ this.scale = 3
13
+ this.snapPoint = 0.5
14
+ this.minSize = 5
15
+ this.collapseSizePx = 15
16
+ this.maxPerpendicularLength = 20
17
+ this.zoneSizes = { frameEdgePx: 40, windowEdgePx: 20 }
18
+ this._recalcAll()
19
+ }
20
+
21
+ _zoneSizes!: { frameEdgePx: number, windowEdgePx: number }
22
+ get zoneSizes(): { frameEdgePx: number, windowEdgePx: number } { return this._zoneSizes }
23
+ set zoneSizes(v: number | { frameEdgePx: number, windowEdgePx: number }) {
24
+ if (typeof v === "number") {
25
+ this._zoneSizes = { frameEdgePx: v, windowEdgePx: v }
26
+ } else {
27
+ this._zoneSizes = { frameEdgePx: v.frameEdgePx, windowEdgePx: v.windowEdgePx }
28
+ }
29
+ }
30
+
31
+ private _scale!: number
32
+ private _maxInt!: number
6
33
  get scale() { return this._scale }
7
34
  set scale(v: number) {
8
35
  this._scale = v
@@ -13,44 +40,72 @@ export class Settings {
13
40
  get maxInt() { return this._maxInt }
14
41
 
15
42
 
16
- private _snapPoint = { x: 0.5, y: 0.5 }
17
- private _snapPointScaled = { x: Math.round(0.5 * (10 ** 3)), y: Math.round(0.5 * (10 ** 3)) }
43
+ private _snapPoint!: Point
44
+ private _snapPointScaled!: Point
18
45
 
19
- get snapPoint() { return this._snapPoint }
46
+ get snapPoint(): Point { return this._snapPoint }
20
47
  set snapPoint(v: number | Point) {
21
- if (typeof v === "number") { this._snapPoint = { x: v, y: v } } else { this._snapPoint = { x: v.x, y: v.y } }
48
+ if (typeof v === "number") {
49
+ this._snapPoint = { x: v, y: v }
50
+ } else {
51
+ this._snapPoint = { x: v.x, y: v.y }
52
+ }
22
53
  this._snapPointScaled = this._scalePoint(this._snapPoint)
23
54
  }
24
55
 
25
56
  get snapPointScaled() { return this._snapPointScaled }
26
57
 
27
58
 
28
- private _minSize = { width: 10 ** 3, height: 10 ** 3 }
29
- private _minSizeScaled = { width: 10 ** 3, height: 10 ** 3 }
59
+ private _minSize!: Size
60
+ private _minSizeScaled!: Size
30
61
 
31
- get minSize() { return this._minSize }
62
+ get minSize(): Size { return this._minSize }
32
63
  set minSize(v: number | Size) {
33
- if (typeof v === "number") { this._minSize = { width: v, height: v } } else { this._minSize = { width: v.width, height: v.height } }
64
+ if (typeof v === "number") {
65
+ this._minSize = { width: v, height: v }
66
+ } else {
67
+ this._minSize = { width: v.width, height: v.height }
68
+ }
34
69
  this._minSizeScaled = this._scaleSize(this._minSize)
35
70
  }
36
71
 
37
- get minSizeScaled() { return this._minSizeScaled }
72
+ get minSizeScaled(): Size { return this._minSizeScaled }
38
73
 
39
- private _collapseSize = { width: 0, height: 0 }
40
- private _collapseSizeScaled = { width: 0, height: 0 }
74
+ private _collapseSizePx!: Size
41
75
 
42
- get collapseSize() { return this._collapseSize }
43
- set collapseSize(v: number | Size) {
44
- if (typeof v === "number") { this._collapseSize = { width: v, height: v } } else { this._collapseSize = { width: v.width, height: v.height } }
45
- this._collapseSizeScaled = this._scaleSize(this._collapseSize)
76
+ get collapseSizePx(): Size { return this._collapseSizePx }
77
+ set collapseSizePx(v: number | Size) {
78
+ if (typeof v === "number") {
79
+ this._collapseSizePx = { width: v, height: v }
80
+ } else {
81
+ this._collapseSizePx = { width: v.width, height: v.height }
82
+ }
46
83
  }
47
84
 
48
- get collapseSizeScaled() { return this._collapseSizeScaled }
85
+ getCollapseSizeScaled(win: { pxWidth: number, pxHeight: number }): Size {
86
+ const size = this.collapseSizePx
87
+ return {
88
+ width: win.pxWidth === 0
89
+ ? 0
90
+ : numberToScaledPercent(
91
+ typeof size === "number" ? size : size.width,
92
+ win.pxWidth,
93
+ this.maxInt
94
+ ),
95
+ height: win.pxHeight === 0
96
+ ? 0
97
+ : numberToScaledPercent(
98
+ typeof size === "number" ? size : size.height,
99
+ win.pxHeight,
100
+ this.maxInt
101
+ )
102
+ }
103
+ }
49
104
 
50
- private _maxPerpendicularLength = { width: 20, height: 20 }
51
- private _maxPerpendicularLengthScaled = { width: Math.round(20 * (10 ** 3)), height: Math.round(20 * (10 ** 3)) }
105
+ private _maxPerpendicularLength!: Size
106
+ private _maxPerpendicularLengthScaled!: Size
52
107
 
53
- get maxPerpendicularLength() { return this._maxPerpendicularLength }
108
+ get maxPerpendicularLength(): Size { return this._maxPerpendicularLength }
54
109
  set maxPerpendicularLength(v: number | Size) {
55
110
  if (typeof v === "number") { this._maxPerpendicularLength = { width: v, height: v } } else { this._maxPerpendicularLength = { ...v } }
56
111
  this._maxPerpendicularLengthScaled = this._scaleSize(this._maxPerpendicularLength)
@@ -70,14 +125,12 @@ export class Settings {
70
125
  }
71
126
 
72
127
  private _recalcAll() {
128
+ if (!this.initialized) return
73
129
  this.snapPoint = this._snapPoint
74
130
  this.minSize = this._minSize
75
- this.collapseSize = this._collapseSize
131
+ this.collapseSizePx = this._collapseSizePx
76
132
  this.maxPerpendicularLength = this._maxPerpendicularLength
77
133
  }
78
-
79
- // ==== px sized, don't require recalc
80
- zoneSizes = { frameEdgePx: 40, windowEdgePx: 20 }
81
134
  }
82
135
 
83
136
  export const settings = new Settings()
@@ -2,9 +2,7 @@ import type { EnumLike } from "@alanscodelog/utils"
2
2
  import { enumFromArray } from "@alanscodelog/utils/enumFromArray"
3
3
  import { z } from "zod"
4
4
 
5
- export * from "../drag/types.js"
6
-
7
- import { settings } from "../settings.js"
5
+ import { KnownError } from "../utils/KnownError.js"
8
6
 
9
7
  export const zUuid = z.uuid()
10
8
  export type AnyUuid = z.infer<typeof zUuid>
@@ -17,11 +15,24 @@ export const zFrameIdConstants = z.enum(["ACTIVE"])
17
15
  export const zFrameId = z.uuid().or(zFrameIdConstants)
18
16
  export type FrameId = z.infer<typeof zFrameId>
19
17
 
18
+ import { settings } from "../settings.js"
19
+
20
20
  export const zScaledIntPercentage = z.number()
21
21
  .int()
22
22
  .min(0)
23
- .max(settings.maxInt)
24
23
  .nonnegative()
24
+ .superRefine((val, ctx) => {
25
+ if (val > settings.maxInt) {
26
+ ctx.addIssue({
27
+ code: "too_big",
28
+ type: "number",
29
+ origin: "number",
30
+ maximum: settings.maxInt,
31
+ inclusive: true,
32
+ message: `Value ${val} must be less than or equal to ${settings.maxInt}`
33
+ })
34
+ }
35
+ })
25
36
 
26
37
  const zPx = z.number()
27
38
  .int()
@@ -65,7 +76,8 @@ export const zEdge = z.object({
65
76
  startX: zScaledIntPercentage,
66
77
  startY: zScaledIntPercentage,
67
78
  endX: zScaledIntPercentage,
68
- endY: zScaledIntPercentage
79
+ endY: zScaledIntPercentage,
80
+ error: z.instanceof<typeof KnownError>(KnownError).or(z.object({ code: z.string(), message: z.string() })).optional()
69
81
  })
70
82
 
71
83
  export type Edge = z.infer<typeof zEdge>
@@ -83,9 +95,9 @@ export type Orientation = z.infer<typeof zOrientation>
83
95
  export type EdgeSide = z.infer<typeof zSide>
84
96
  export type ExtendedEdgeSide = z.infer<typeof zExtendedSide>
85
97
 
86
- export const zBaseSquare = zSize.extend(zPos.shape)
87
- export type BaseSquare = z.infer<typeof zBaseSquare>
88
- export type BaseSquareCss = {
98
+ export const zBaseRect = zSize.extend(zPos.shape)
99
+ export type BaseRect = z.infer<typeof zBaseRect>
100
+ export type BaseRectCss = {
89
101
  x: string
90
102
  y: string
91
103
  width: string
@@ -93,10 +105,10 @@ export type BaseSquareCss = {
93
105
  translate?: string
94
106
  }
95
107
 
96
- export type EdgeCss = BaseSquareCss & {
108
+ export type EdgeCss = BaseRectCss & {
97
109
  translate: string
98
110
  }
99
- export type PointCss = BaseSquareCss & {
111
+ export type PointCss = BaseRectCss & {
100
112
  translate: string
101
113
  }
102
114
 
@@ -111,7 +123,7 @@ export type IntersectionEntry = {
111
123
  }
112
124
 
113
125
 
114
- export const zLayoutFrame = zBaseSquare.extend({
126
+ export const zLayoutFrame = zBaseRect.extend({
115
127
  id: z.uuid(),
116
128
  docked: zSide.optional(),
117
129
  collapsed: z.number().min(1).optional()
@@ -204,7 +216,7 @@ export type Layout = ExtendedLayout & {
204
216
  windows: LayoutWindows
205
217
  }
206
218
  const zLayoutShape = z.discriminatedUnion("type", [
207
- z.object({ type: z.literal("square"), data: zBaseSquare, attrs: z.record(z.string(), z.string()).optional() }),
219
+ z.object({ type: z.literal("rect"), data: zBaseRect, attrs: z.record(z.string(), z.string()).optional() }),
208
220
  z.object({ type: z.literal("edge"), data: zEdge, attrs: z.record(z.string(), z.string()).optional() })
209
221
  ])
210
222
 
@@ -268,12 +280,14 @@ export const LAYOUT_ERROR = enumFromArray([
268
280
  "CANT_SPLIT_DOCKED_FRAME",
269
281
  "NO_SPACE_TO_REDISTRIBUTE",
270
282
  "REDISTRIBUTE_OUT_OF_BOUNDS",
283
+ "REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES",
271
284
  "FRAME_ALREADY_DOCKED_ON_SIDE",
272
285
  "CANT_LEAVE_NO_UNDOCKED_FRAMES",
273
286
  "CANT_UNDOCK_COLLAPSED_FRAME",
274
287
  "CANT_COLLAPSE_NOT_DOCKED",
275
288
  "CANT_UNCOLLAPSE_NOT_COLLAPSED",
276
- "NO_FILL_CANDIDATES"
289
+ "NO_FILL_CANDIDATES",
290
+ "CANT_RESIZE_COLLAPSED_FRAME"
277
291
  ])
278
292
 
279
293
  export type LayoutError = EnumLike<typeof LAYOUT_ERROR>
@@ -359,6 +373,12 @@ export type LayoutErrorsInfo = {
359
373
  }
360
374
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
361
375
  [LAYOUT_ERROR.NO_FILL_CANDIDATES]: {}
376
+ [LAYOUT_ERROR.CANT_RESIZE_COLLAPSED_FRAME]: {
377
+ frame: LayoutFrame
378
+ }
379
+ [LAYOUT_ERROR.REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES]: {
380
+ problemEdgeCoordinates: number[]
381
+ }
362
382
  }
363
383
 
364
384
  // todo rename to toOpposite
@@ -404,35 +424,187 @@ export const zFrameCreate = zLayoutFrame.partial({
404
424
  })
405
425
 
406
426
 
407
- export type LayoutShapeSquareProps
408
- = & {
409
- css: BaseSquareCss
410
- }
411
-
412
- export type LayoutEdgesProps
427
+ export type LayoutShapeRectProps
413
428
  = & {
414
- edges: Edge[]
415
- intersections: IntersectionEntry[]
416
- /** The owning window, needed so we can correctly scale coordinates. */
417
- win: LayoutWindow
418
- /** The active frame, used to render the active edges. Calculate it from the `frames` returned by `useFrames` composable because otherwise it will be the wrong size while dragging. */
419
- activeFrame?: LayoutFrame
420
- draggingEdge?: Edge
421
- draggingIntersection?: IntersectionEntry
429
+ css: BaseRectCss
422
430
  }
423
- & Partial<LayoutShapeSquareProps>
424
431
 
425
432
 
426
433
  export type LayoutChange<TInfo = never> = {
427
434
  modified: LayoutFrame[]
428
435
  created: LayoutFrame[]
429
436
  deleted: LayoutFrame[]
437
+ window?: Partial<LayoutWindow>
430
438
  info?: TInfo
431
439
  }
440
+ export type DragState = {
441
+ /** The current directions in the corresponding orientations that the user is dragging in. */
442
+ dragDirections: Record<Orientation, Direction | undefined>
443
+ /** The curren point (in scaled window coordinates) the user is dragging at. */
444
+ dragPoint?: Point
445
+ /** Whether the user is currently dragging and what type of drag. Is truthy string during all drag events. */
446
+ isDragging: boolean | "frame" | "edge"
447
+ /** Whether to show the moved frames while dragging. */
448
+ showDragging: boolean
449
+ /** The frame being dragged during a frame drag. Only set when isDragging is "frame". */
450
+ draggingFrameId?: FrameId
451
+ /**
452
+ * 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.
453
+ */
454
+ draggingEdges: Edge[]
455
+ /**
456
+ * The intersection that is currently being dragged.
457
+ */
458
+ draggingIntersection?: IntersectionEntry
459
+ /** The "visual" edges that can be displayed for dragging. See {@link getVisualEdges} */
460
+ visualEdges: Edge[]
461
+ /**
462
+ * The frames touching the currently dragged edges. Each entry corresponds to the frames touching the corresponding dragging edge.
463
+ *
464
+ * So you can use the index in draggingEdges to get the corresponding frames.
465
+ */
466
+ touchingFrames: Record<string, LayoutFrame>[]
467
+ /**
468
+ * Same as touchingFrames, but with the frames in an array.
469
+ */
470
+ touchingFramesArrays: LayoutFrame[][]
471
+ /**
472
+ * All the frames, with/without the currently dragged frames depending on if `showDragging` is true.
473
+ */
474
+ frames: Record<string, LayoutFrame>
475
+ /**
476
+ * The frame that is currently being hovered over (according to whether `dragPoint` in in the frame or not).
477
+ */
478
+ dragHoveredFrame: LayoutFrame | undefined
479
+ /**
480
+ * A list of corner intersections. Frames can also be dragged by these.
481
+ */
482
+ intersections: IntersectionEntry[]
483
+ /**
484
+ * Whether the drag was initiated from a point along the window edge.
485
+ */
486
+ isDraggingFromWindowEdge: boolean
487
+ win: LayoutWindow
488
+ }
432
489
 
433
- export type LayoutFrameProps
434
- = & {
435
- frame: LayoutFrame
436
- isActiveFrame: boolean
490
+
491
+ export interface ActionDragChangeResult {
492
+ /** Whether the drag should update the edges. Defaults to false. */
493
+ updateEdges?: boolean
494
+ /** Deco shapes produced by this action during this drag step. */
495
+ shapes: LayoutShape[]
496
+ /** Whether to show the moved edges while dragging. Defaults to true. */
497
+ showDragging?: boolean
498
+ }
499
+ export type DragChangeResult = Omit<ActionDragChangeResult, "shapes">
500
+
501
+ /**
502
+ * Called when the drag coordinates change (during any event).
503
+ *
504
+ * Should return `{ allowed: true/false, shapes: LayoutShape[] }` to control whether the action is allowed and edges update and what deco shapes to render.
505
+ *
506
+ * Note that the allowed return type only affect the `move` event but is also typed as `boolean` for other events for ease of use.
507
+ */
508
+ export type DragChangeHandler = <T extends "start" | "move" | "end">(
509
+ type: T,
510
+ e: T extends "end" ? PointerEvent | undefined : PointerEvent,
511
+ state: DragState,
512
+ forceRecalculateEdges: () => void,
513
+ cancel: (e: PointerEvent | KeyboardEvent | undefined, state: DragState) => void
514
+ ) => ActionDragChangeResult
515
+
516
+ /**
517
+ * A drag action describes when and how to handle a drag event.
518
+ *
519
+ * For example, there are the default split/close actions that can be triggered in certain situations. This could be when holding down a modifier or key, or some other condition (e.g. the user is dragging a specific edge).
520
+ *
521
+ * Each action should handle it's configuration and saving/caching any state it needs. See {@link SplitAction} and {@link CloseAction} for examples.
522
+ */
523
+ // eslint-disable-next-line @typescript-eslint/naming-convention
524
+ export interface IDragAction {
525
+ /** A unique name for your action. */
526
+ name: string
527
+ onDragChange: DragChangeHandler
528
+ /**
529
+ *
530
+ * Is called after `onDragChange("end")` with the same event. Might not be called if the request was cancelled.
531
+ *
532
+ * You should apply your action if possible and return whether it was applied.
533
+ *
534
+ * This is also a good place to reset your state.
535
+ */
536
+ onDragApply: (state: DragState, forceRecalculateEdges: () => void) => boolean
537
+ /**
538
+ * Should return true if it should handle the "request"/event (e.g. some modifier is being pressed => user is requesting x action).
539
+ *
540
+ * The user is not necessarily dragging at this point, though they might also change actions mid drag. So it does not necessarily mean the event is allowed.
541
+ *
542
+ * Here is where you should initiate your state. Don't allow the action by default unless it can always be allowed.
543
+ */
544
+ canHandleRequest(
545
+ e: KeyboardEvent | PointerEvent,
546
+ state: DragState,
547
+ forceRecalculateEdges: () => void
548
+ ): boolean
549
+ /**
550
+ * Called when a user cancels the drag action.
551
+ *
552
+ * You should reset your state here.
553
+ */
554
+ cancel(e: PointerEvent | KeyboardEvent | undefined, state: DragState): void
555
+ /**
556
+ * 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).
557
+ *
558
+ *
559
+ * Calls look like this usually, where this.state is the plugin state:
560
+ * ```ts
561
+ * DragActionHandler.debugState(this.name, "before", state, this.state, this.debug)
562
+ * ```
563
+ */
564
+ debug: boolean | string
565
+ /**
566
+ * The action handler will call this regardless of whether the action is active or not.
567
+ *
568
+ * Can be used by actions to return display hints.
569
+ *
570
+ * 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.
571
+ */
572
+ // 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
573
+ getTextHints(type: "start" | "move" | "end"): {
574
+ /** Hint texts to display regarding the action state/usage. Undefined means no hint. */
575
+ actions?: string[]
576
+ /** Error texts/hints to display when the action produces an error. */
577
+ errors?: string[]
437
578
  }
438
- & Partial<LayoutShapeSquareProps>
579
+ /**
580
+ * Called after visual edges are recalculated, once per edge. Action handlers can annotate edges with error info here ({@link Edge.error}).
581
+ *
582
+ * When `LayoutEdges` sees an error it adds the message as the title and the following classes:
583
+ * - `data-error` attribute with the error message
584
+ * - class `drag-edge-errored` on the thick drag handle
585
+ * - class `edge-errored` on the thin visual edge
586
+ *
587
+ * Tailwind example to style errored edges:
588
+ * ```css
589
+ * [&_.drag-edge-errored]:cursor-not-allowed
590
+ * [&_.drag-edge-errored]:bg-red-500/30
591
+ * [&_.edge-errored]:bg-red-500/20
592
+ * ```
593
+ *
594
+ * Regarding the title, this is a temporary solution until lib is refactored to more composable components.
595
+ *
596
+ * NOTE: Annotation does NOT prevent dragging/events on edges.
597
+ */
598
+ annotateEdge?(edge: Edge, frames: LayoutFrame[]): void
599
+ }
600
+ export type EdgeDragStartData = { edge?: Edge, intersection?: IntersectionEntry }
601
+ export type FrameDragStartData = { frameId: FrameId }
602
+
603
+ // drag start overloads for triggering dragsj
604
+ export type DragStartFn = {
605
+ (e: PointerEvent, type: "edge", data: EdgeDragStartData): void
606
+ (e: PointerEvent, type: "frame", data: FrameDragStartData): void
607
+ }
608
+
609
+ export type * from "./vue.js"
610
+
@@ -0,0 +1,41 @@
1
+ import type { ComputedRef, InjectionKey, Ref } from "vue"
2
+
3
+ // eslint-disable-next-line no-restricted-imports
4
+ import type { Direction, DragState, Edge, FrameId, IntersectionEntry, LayoutFrame, LayoutShape, LayoutWindow, Orientation, Point } from "./index.js"
5
+
6
+ export type LayoutContext = ComputedRef<
7
+ & {
8
+ /** The owning window, needed so we can correctly scale coordinates. */
9
+ win: LayoutWindow
10
+ onFocus: (frameId: string) => void
11
+ shapes: LayoutShape[]
12
+ }>
13
+
14
+ export const layoutContextInjectionKey = Symbol.for("@witchcraft/layout:context") as InjectionKey<LayoutContext>
15
+
16
+ export interface UseFramesContext {
17
+ dragStart: {
18
+ (e: PointerEvent, type: "edge", data: { edge?: Edge, intersection?: IntersectionEntry }): void
19
+ (e: PointerEvent, type: "frame", data: { frameId: FrameId }): void
20
+ }
21
+ dragMove: (e: PointerEvent) => void
22
+ dragEnd: (e?: PointerEvent, options?: { apply?: boolean }) => void
23
+ cancel: () => void
24
+ dragDirections: Ref<Record<Orientation, Direction | undefined>>
25
+ dragPoint: Ref<Point | undefined>
26
+ isDragging: Ref<false | "frame" | "edge">
27
+ draggingEdges: Ref<Edge[]>
28
+ draggingIntersection: Ref<IntersectionEntry | undefined>
29
+ visualEdges: Ref<Edge[]>
30
+ touchingFrames: Ref<Record<string, LayoutFrame>[]>
31
+ touchingFramesArrays: Ref<LayoutFrame[][]>
32
+ frames: ComputedRef<Record<string, LayoutFrame>>
33
+ dragHoveredFrame: ComputedRef<LayoutFrame | undefined>
34
+ intersections: ComputedRef<IntersectionEntry[]>
35
+ isDraggingFromWindowEdge: Ref<boolean>
36
+ forceRecalculateEdges: () => void
37
+ state: ComputedRef<DragState>
38
+ frameDragFrameId: Ref<FrameId | undefined>
39
+ }
40
+
41
+ export const dragContextInjectionKey = Symbol.for("@witchcraft/layout:dragContext") as InjectionKey<UseFramesContext>
@@ -0,0 +1,8 @@
1
+ import type { BaseRectCss } from "../types/index.js"
2
+
3
+ /**
4
+ * Converts a css object to a key that can be used in a vue loop.
5
+ */
6
+ export function cssToKey(css: BaseRectCss): string {
7
+ return `${css.x},${css.y},${css.width},${css.height},${css.translate}`
8
+ }
@@ -1,26 +0,0 @@
1
- <template>
2
- <template
3
- v-for="(shape, i) of shapes"
4
- :key="`${shape.type}-${i}`"
5
- >
6
- <LayoutShapeSquare
7
- v-if="shape.type === 'square'"
8
- :css="getShapeSquareCss(shape.data, `var(--layoutEdgeWidth,2px)`)"
9
- v-bind="shape.attrs ?? {}"
10
- />
11
- <LayoutShapeSquare
12
- v-else-if="shape.type === 'edge'"
13
- :css="getVisualEdgeCss(shape.data, { padLongAxis: 'var(--layoutEdgeWidth,2px)' })"
14
- v-bind="shape.attrs ?? {}"
15
- />
16
- </template>
17
- </template>
18
-
19
- <script setup>
20
- import LayoutShapeSquare from "./LayoutShapeSquare.vue";
21
- import { getShapeSquareCss } from "../helpers/getShapeSquareCss";
22
- import { getVisualEdgeCss } from "../helpers/getVisualEdgeCss";
23
- defineProps({
24
- shapes: { type: Array, required: true }
25
- });
26
- </script>
@@ -1,17 +0,0 @@
1
- import type { Edge, IntersectionEntry, LayoutEdgesProps } from "../types/index.js";
2
- declare const _default: import("vue").DefineComponent<LayoutEdgesProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
3
- dragStart: (e: PointerEvent, type: "edge", opts: {
4
- edge?: Edge;
5
- intersection?: IntersectionEntry;
6
- }) => any;
7
- }, string, import("vue").PublicProps, Readonly<LayoutEdgesProps> & Readonly<{
8
- onDragStart?: ((e: PointerEvent, type: "edge", opts: {
9
- edge?: Edge;
10
- intersection?: IntersectionEntry;
11
- }) => any) | undefined;
12
- }>, {
13
- activeFrame: import("../types/index.js").LayoutFrame;
14
- draggingEdge: Edge;
15
- draggingIntersection: IntersectionEntry;
16
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
- export default _default;