@witchcraft/layout 0.3.1 → 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 +62 -70
  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,13 +1,24 @@
1
1
  import { enumFromArray } from "@alanscodelog/utils/enumFromArray";
2
2
  import { z } from "zod";
3
- export * from "../drag/types.js";
4
- import { settings } from "../settings.js";
3
+ import { KnownError } from "../utils/KnownError.js";
5
4
  export const zUuid = z.uuid();
6
5
  export const zWindowIdConstants = z.enum(["ACTIVE"]);
7
6
  export const zWinId = z.uuid().or(zWindowIdConstants);
8
7
  export const zFrameIdConstants = z.enum(["ACTIVE"]);
9
8
  export const zFrameId = z.uuid().or(zFrameIdConstants);
10
- export const zScaledIntPercentage = z.number().int().min(0).max(settings.maxInt).nonnegative();
9
+ import { settings } from "../settings.js";
10
+ export const zScaledIntPercentage = z.number().int().min(0).nonnegative().superRefine((val, ctx) => {
11
+ if (val > settings.maxInt) {
12
+ ctx.addIssue({
13
+ code: "too_big",
14
+ type: "number",
15
+ origin: "number",
16
+ maximum: settings.maxInt,
17
+ inclusive: true,
18
+ message: `Value ${val} must be less than or equal to ${settings.maxInt}`
19
+ });
20
+ }
21
+ });
11
22
  const zPx = z.number().int().nonnegative();
12
23
  export const zPos = z.object({
13
24
  x: zScaledIntPercentage,
@@ -33,15 +44,16 @@ export const zEdge = z.object({
33
44
  startX: zScaledIntPercentage,
34
45
  startY: zScaledIntPercentage,
35
46
  endX: zScaledIntPercentage,
36
- endY: zScaledIntPercentage
47
+ endY: zScaledIntPercentage,
48
+ error: z.instanceof(KnownError).or(z.object({ code: z.string(), message: z.string() })).optional()
37
49
  });
38
50
  export const zDirection = z.enum(["up", "down", "left", "right"]);
39
51
  export const zExtendedDirection = z.union([zDirection, z.enum(["horizontal", "vertical"])]);
40
52
  export const zSide = z.enum(["top", "bottom", "left", "right"]);
41
53
  export const zOrientation = z.enum(["horizontal", "vertical"]);
42
54
  export const zExtendedSide = z.union([zSide, zOrientation]);
43
- export const zBaseSquare = zSize.extend(zPos.shape);
44
- export const zLayoutFrame = zBaseSquare.extend({
55
+ export const zBaseRect = zSize.extend(zPos.shape);
56
+ export const zLayoutFrame = zBaseRect.extend({
45
57
  id: z.uuid(),
46
58
  docked: zSide.optional(),
47
59
  collapsed: z.number().min(1).optional()
@@ -88,7 +100,7 @@ export const zInitializedLayout = zLayout.required({
88
100
  return zLayoutWindow.safeParse(layout.windows[layout.activeWindow]).success;
89
101
  }, { message: "An initialized layout's active window must exist and be valid." });
90
102
  const zLayoutShape = z.discriminatedUnion("type", [
91
- z.object({ type: z.literal("square"), data: zBaseSquare, attrs: z.record(z.string(), z.string()).optional() }),
103
+ z.object({ type: z.literal("rect"), data: zBaseRect, attrs: z.record(z.string(), z.string()).optional() }),
92
104
  z.object({ type: z.literal("edge"), data: zEdge, attrs: z.record(z.string(), z.string()).optional() })
93
105
  ]);
94
106
  const zBaseDeco = z.object({
@@ -134,12 +146,14 @@ export const LAYOUT_ERROR = enumFromArray([
134
146
  "CANT_SPLIT_DOCKED_FRAME",
135
147
  "NO_SPACE_TO_REDISTRIBUTE",
136
148
  "REDISTRIBUTE_OUT_OF_BOUNDS",
149
+ "REDISTRIBUTE_WOULD_RESULT_IN_INVALID_FRAMES",
137
150
  "FRAME_ALREADY_DOCKED_ON_SIDE",
138
151
  "CANT_LEAVE_NO_UNDOCKED_FRAMES",
139
152
  "CANT_UNDOCK_COLLAPSED_FRAME",
140
153
  "CANT_COLLAPSE_NOT_DOCKED",
141
154
  "CANT_UNCOLLAPSE_NOT_COLLAPSED",
142
- "NO_FILL_CANDIDATES"
155
+ "NO_FILL_CANDIDATES",
156
+ "CANT_RESIZE_COLLAPSED_FRAME"
143
157
  ]);
144
158
  export const zWindowCreate = zLayoutWindowLoose.partial({ id: true, pxWidth: true, pxHeight: true, pxX: true, pxY: true }).extend({ frames: zLayoutWindow.shape.frames.optional() });
145
159
  export const zLayoutCreate = baseLayout.loose().extend({
@@ -0,0 +1,41 @@
1
+ import type { ComputedRef, InjectionKey, Ref } from "vue";
2
+ import type { Direction, DragState, Edge, FrameId, IntersectionEntry, LayoutFrame, LayoutShape, LayoutWindow, Orientation, Point } from "./index.js";
3
+ export type LayoutContext = ComputedRef<{
4
+ /** The owning window, needed so we can correctly scale coordinates. */
5
+ win: LayoutWindow;
6
+ onFocus: (frameId: string) => void;
7
+ shapes: LayoutShape[];
8
+ }>;
9
+ export declare const layoutContextInjectionKey: InjectionKey<LayoutContext>;
10
+ export interface UseFramesContext {
11
+ dragStart: {
12
+ (e: PointerEvent, type: "edge", data: {
13
+ edge?: Edge;
14
+ intersection?: IntersectionEntry;
15
+ }): void;
16
+ (e: PointerEvent, type: "frame", data: {
17
+ frameId: FrameId;
18
+ }): void;
19
+ };
20
+ dragMove: (e: PointerEvent) => void;
21
+ dragEnd: (e?: PointerEvent, options?: {
22
+ apply?: boolean;
23
+ }) => void;
24
+ cancel: () => void;
25
+ dragDirections: Ref<Record<Orientation, Direction | undefined>>;
26
+ dragPoint: Ref<Point | undefined>;
27
+ isDragging: Ref<false | "frame" | "edge">;
28
+ draggingEdges: Ref<Edge[]>;
29
+ draggingIntersection: Ref<IntersectionEntry | undefined>;
30
+ visualEdges: Ref<Edge[]>;
31
+ touchingFrames: Ref<Record<string, LayoutFrame>[]>;
32
+ touchingFramesArrays: Ref<LayoutFrame[][]>;
33
+ frames: ComputedRef<Record<string, LayoutFrame>>;
34
+ dragHoveredFrame: ComputedRef<LayoutFrame | undefined>;
35
+ intersections: ComputedRef<IntersectionEntry[]>;
36
+ isDraggingFromWindowEdge: Ref<boolean>;
37
+ forceRecalculateEdges: () => void;
38
+ state: ComputedRef<DragState>;
39
+ frameDragFrameId: Ref<FrameId | undefined>;
40
+ }
41
+ export declare const dragContextInjectionKey: InjectionKey<UseFramesContext>;
@@ -1 +1,2 @@
1
+ export const layoutContextInjectionKey = Symbol.for("@witchcraft/layout:context");
1
2
  export const dragContextInjectionKey = Symbol.for("@witchcraft/layout:dragContext");
@@ -0,0 +1,5 @@
1
+ import type { BaseRectCss } from "../types/index.js";
2
+ /**
3
+ * Converts a css object to a key that can be used in a vue loop.
4
+ */
5
+ export declare function cssToKey(css: BaseRectCss): string;
@@ -0,0 +1,3 @@
1
+ export function cssToKey(css) {
2
+ return `${css.x},${css.y},${css.width},${css.height},${css.translate}`;
3
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@witchcraft/layout",
3
3
  "description": "Headless layout manager.",
4
- "version": "0.3.1",
4
+ "version": "0.4.0",
5
5
  "main": "./dist/runtime/index.js",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -28,13 +28,12 @@
28
28
  }
29
29
  },
30
30
  "dependencies": {
31
- "@alanscodelog/utils": "^6.2.0",
32
- "@iconify/json": "^2.2.474",
33
- "es-toolkit": "^1.46.1"
31
+ "@alanscodelog/utils": "^6.3.0",
32
+ "@iconify/json": "^2.2.474"
34
33
  },
35
34
  "peerDependencies": {
36
- "@witchcraft/nuxt-utils": "^0.3.6",
37
- "@witchcraft/ui": "^0.5.0"
35
+ "@witchcraft/nuxt-utils": "^0.3.10",
36
+ "@witchcraft/ui": "^0.5.9"
38
37
  },
39
38
  "peerDependenciesMeta": {
40
39
  "@witchcraft/ui": {
@@ -60,8 +59,8 @@
60
59
  "@types/node": "^24.12.4",
61
60
  "@vitejs/plugin-vue": "^6.0.7",
62
61
  "@vitest/coverage-v8": "^4.1.0",
63
- "@witchcraft/nuxt-utils": "^0.3.6",
64
- "@witchcraft/ui": "^0.5.0",
62
+ "@witchcraft/nuxt-utils": "^0.3.10",
63
+ "@witchcraft/ui": "^0.5.9",
65
64
  "concurrently": "^9.2.1",
66
65
  "cross-env": "^10.1.0",
67
66
  "fast-glob": "^3.3.3",
@@ -128,10 +127,10 @@
128
127
  "build:only": "nuxt-module-build build",
129
128
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
130
129
  "dev:playground": "nuxi dev playground",
131
- "//dev:vite": "check everything is working when using vite",
132
- "dev:vite": "vite --mode development --config vite.dev.config.ts",
130
+ "//dev:vite:vue": "check everything is working when using vite (Vue demo)",
131
+ "dev:vite:vue": "vite --mode development --config vite.dev.vue.config.ts",
133
132
  "//dev": "The module needs to be build, not stubbed, for vite's dev to work.",
134
- "dev": "pnpm dev:vite",
133
+ "dev": "pnpm dev:vite:vue",
135
134
  "lint:eslint": "eslint \"{src,tests,bin}/**/*.{cjs,js,ts}\" \"*.{cjs,js,ts}\" --max-warnings=0 --report-unused-disable-directives",
136
135
  "lint:commits": "commitlint --from-last-tag --to HEAD --verbose",
137
136
  "lint:imports": "echo disabled madge --circular --extensions ts ./src",
@@ -139,7 +138,7 @@
139
138
  "lint:types": "vue-tsc --noEmit",
140
139
  "coverage": "vitest --coverage",
141
140
  "coverage:dev": "vitest --watch --coverage",
142
- "test": "vitest",
141
+ "test": "vitest --config vite.dev.vue.config.ts",
143
142
  "doc": "typedoc",
144
143
  "doc:watch": "onchange -i \"src/**/*.ts\" \"typedoc.config.js\" -- pnpm doc",
145
144
  "doc:serve": "http-server docs --port=5001",
package/src/module.ts CHANGED
@@ -35,7 +35,12 @@ export default defineNuxtModule<ModuleOptions>({
35
35
  addTemplate({
36
36
  filename: "witchcraft-layout.css",
37
37
  write: true,
38
- getContents: () => `@source "${resolve("runtime/components")}";`
38
+ getContents: () => [
39
+ `@source "${resolve("runtime/components")}";`,
40
+ // the drag actions handlers and utils they use add some styles
41
+ `@source "${resolve("runtime/drag")}";`,
42
+ `@source "${resolve("runtime/utils/createSplitDecoShapes")}";`
43
+ ].join("\n")
39
44
  })
40
45
 
41
46
  nuxt.options.alias["#witchcraft-layout"] = resolve("runtime")
@@ -8,7 +8,7 @@
8
8
  </template>
9
9
  <script lang="ts" setup>
10
10
  import { inject, onBeforeUnmount } from "vue"
11
- import { dragContextInjectionKey } from "../drag/types.js";
11
+ import { dragContextInjectionKey } from "../types/vue.js"
12
12
 
13
13
  const props = defineProps<{
14
14
  frameId: string
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <template
3
+ v-for="(shape, i) of ctx.shapes.filter(_ => _.type === 'edge')"
4
+ :key="cssToKey(shape.data)"
5
+ >
6
+
7
+ <slot
8
+ name="edge"
9
+ v-bind="{
10
+ shape,
11
+ css: getVisualEdgeCss(shape.data, { padLongAxis: 'var(--layoutEdgeWidth,2px)' })
12
+ }"
13
+ >
14
+ <LayoutShapeRect
15
+ :css="getVisualEdgeCss(shape.data, { padLongAxis: 'var(--layoutEdgeWidth,2px)' })"
16
+ :class="twMerge(shape.attrs?.class, ($attrs as any).class)"
17
+ v-bind="{...shape.attrs, ...$attrs, class: undefined }"
18
+
19
+ />
20
+ </slot>
21
+ </template>
22
+ </template>
23
+ <script lang="ts" setup>
24
+ import LayoutShapeRect from "./LayoutShapeRect.vue"
25
+
26
+ import { getVisualEdgeCss } from "../helpers/getVisualEdgeCss"
27
+ import { layoutContextInjectionKey } from "../types/vue.js"
28
+ import { useAttrs, inject } from "vue"
29
+ import {cssToKey} from "../utils/cssToKey.js"
30
+ import { twMerge } from "@witchcraft/ui/utils/twMerge"
31
+
32
+ const $attrs = useAttrs()
33
+
34
+ const ctx = inject(layoutContextInjectionKey, undefined)
35
+ if (!ctx) throw new Error("LayoutEdges must be used within a LayoutWindow")
36
+
37
+
38
+ </script>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <template
3
+ v-for="(shape) of ctx.shapes.filter(_ => _.type === 'rect')"
4
+ :key="cssToKey(shape.data)"
5
+ >
6
+ <slot
7
+ name="rect"
8
+ v-bind="{
9
+ shape,
10
+ css: getShapeRectCss(shape.data, `var(--layoutEdgeWidth,2px)`)
11
+ }"
12
+ >
13
+ <LayoutShapeRect
14
+ v-if="shape.type === 'rect'"
15
+ :css="getShapeRectCss(shape.data, `var(--layoutEdgeWidth,2px)`)"
16
+ :class="twMerge(`bg-neutral-500/20`,shape.attrs?.class, ($attrs as any).class)"
17
+ v-bind="{...shape.attrs, ...$attrs, class: undefined }"
18
+ />
19
+ </slot>
20
+
21
+ </template>
22
+ </template>
23
+ <script lang="ts" setup>
24
+ import LayoutShapeRect from "./LayoutShapeRect.vue"
25
+
26
+ import { getShapeRectCss } from "../helpers/getShapeRectCss"
27
+ import { layoutContextInjectionKey } from "../types/vue.js"
28
+ import { useAttrs, inject } from "vue"
29
+ import { cssToKey } from "../utils/cssToKey.js"
30
+ import { twMerge } from "@witchcraft/ui/utils/twMerge"
31
+
32
+ const $attrs = useAttrs()
33
+
34
+ defineOptions({
35
+ inheritAttrs: false
36
+ })
37
+
38
+
39
+ const ctx = inject(layoutContextInjectionKey, undefined)
40
+ if (!ctx) throw new Error("LayoutEdges must be used within a LayoutWindow")
41
+
42
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <LayoutShapeRect
3
+ :css="css"
4
+ :class="twMerge(`
5
+ drag-edge-grabbed
6
+ z-20
7
+ bg-accent-500
8
+ cursor-pointer
9
+ [&.request-split]:hidden
10
+ `,
11
+ ($attrs as any).class
12
+ )"
13
+ v-bind="{ ...$attrs, class: undefined }"
14
+ v-for="css, i of cssDragEdge"
15
+ :key="cssToKey(css)"
16
+ />
17
+ </template>
18
+ <script lang="ts" setup>
19
+ import { dragContextInjectionKey, layoutContextInjectionKey } from "../types/vue.js"
20
+ import { twMerge } from "@witchcraft/ui/utils/twMerge"
21
+ import { computed, inject, useAttrs } from "vue"
22
+
23
+ import LayoutShapeRect from "./LayoutShapeRect.vue"
24
+ import { getVisualEdgesCss } from "../helpers/getVisualEdgesCss.js"
25
+ import { cssToKey } from "../utils/cssToKey.js"
26
+
27
+
28
+ const $attrs = useAttrs()
29
+
30
+ defineOptions({
31
+ inheritAttrs: false
32
+ })
33
+
34
+ const ctx = inject(layoutContextInjectionKey, undefined)
35
+ if (!ctx) throw new Error("LayoutEdges must be used within a LayoutWindow")
36
+
37
+ const dragCtx = inject(dragContextInjectionKey, undefined)
38
+ if (!dragCtx) throw new Error("LayoutEdges must be used within a LayoutWindow")
39
+
40
+ const draggingEdge = computed(() => dragCtx.draggingEdges.value.length === 1 ? dragCtx.draggingEdges.value[0] : undefined)
41
+
42
+ const cssDragEdge = computed(() => {
43
+ if (!draggingEdge.value) return []
44
+ return getVisualEdgesCss([draggingEdge.value], {
45
+ edgeWidth: `var(--layoutEdgeWidth, 2px)`,
46
+ padLongAxis: `(var(--layoutEdgeWidth, 2px) + var(--layoutExtraDragEdgePadding, 0px))`
47
+ })
48
+ })
49
+
50
+ </script>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <LayoutShapeRect
3
+ :css="css"
4
+ :title="edge.error?.message"
5
+ :class="twMerge(
6
+ `
7
+ drag-edge
8
+ z-20
9
+ hover:cursor-pointer
10
+
11
+ [&.request-split]:hidden
12
+ `,
13
+ edge.error && `
14
+ drag-edge-errored
15
+ hover:cursor-not-allowed
16
+ `,
17
+ ($attrs as any).class
18
+ )"
19
+ v-bind="{ ...$attrs, class: undefined }"
20
+ @pointerdown="onPointerDown?.($event, 'edge', { edge })"
21
+ />
22
+ </template>
23
+
24
+ <script lang="ts" setup>
25
+ import { layoutContextInjectionKey } from "../types/vue.js"
26
+ import { twMerge } from "@witchcraft/ui/utils/twMerge"
27
+ import { useAttrs, inject } from "vue"
28
+
29
+ import LayoutShapeRect from "./LayoutShapeRect.vue"
30
+ import { type EdgeDragStartData } from "../types/index.js"
31
+ import { dragContextInjectionKey } from "../types/vue.js"
32
+ import type { Edge, EdgeCss } from "../types/index.js"
33
+
34
+ const $attrs = useAttrs()
35
+
36
+ defineOptions({
37
+ inheritAttrs: false
38
+ })
39
+
40
+ const props = defineProps<{
41
+ edge: Edge
42
+ css: EdgeCss
43
+ onPointerDown?: (e: PointerEvent, type: "edge", data: EdgeDragStartData) => void
44
+ }>()
45
+
46
+ const ctx = inject(layoutContextInjectionKey, undefined)
47
+ if (!ctx) throw new Error("LayoutEdges must be used within a LayoutWindow")
48
+
49
+ const dragCtx = inject(dragContextInjectionKey, undefined)
50
+ if (!dragCtx) throw new Error("LayoutEdges must be used within a LayoutWindow")
51
+
52
+ </script>
@@ -0,0 +1,45 @@
1
+ <template>
2
+ <LayoutShapeRect
3
+ :class="twMerge(
4
+ `
5
+ visible-drag-edge
6
+ pointer-events-none
7
+ [.drag-edge:hover+&]:bg-accent-500/60
8
+ `,
9
+ edge.error && `
10
+ visible-drag-edge-errored
11
+ [.drag-edge:hover+&]:bg-red-500/50
12
+ [.drag-edge:hover+&]:cursor-not-allowed
13
+
14
+ `,
15
+ ($attrs as any).class
16
+ )"
17
+ :css="css"
18
+ v-bind="{ ...$attrs, class: undefined }"
19
+ />
20
+ </template>
21
+
22
+ <script lang="ts" setup>
23
+ import { twMerge } from "@witchcraft/ui/utils/twMerge"
24
+ import { useAttrs, inject } from "vue"
25
+
26
+ import LayoutShapeRect from "./LayoutShapeRect.vue"
27
+ import { type Edge, type EdgeCss } from "../types/index.js"
28
+ import { layoutContextInjectionKey } from "../types/vue.js"
29
+
30
+
31
+ const $attrs = useAttrs()
32
+
33
+ defineOptions({
34
+ inheritAttrs: false
35
+ })
36
+
37
+ defineProps<{
38
+ edge: Edge
39
+ css: EdgeCss
40
+ }>()
41
+
42
+ const ctx = inject(layoutContextInjectionKey, undefined)
43
+ if (!ctx) throw new Error("LayoutEdges must be used within a LayoutWindow")
44
+
45
+ </script>
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <template
3
+ v-for="css, i in cssDragEdges"
4
+ :key="cssToKey(css.handle)"
5
+ >
6
+ <slot name="handle" v-bind="{
7
+ css: css.handle,
8
+ edge: visualEdges[i],
9
+ onPointerDown: dragCtx.dragStart
10
+ }">
11
+ <LayoutDragEdgeHandle
12
+ :css="css.handle"
13
+ :edge="visualEdges[i]"
14
+ @pointerdown="dragCtx.dragStart"
15
+ />
16
+ </slot>
17
+ <slot name="indicator" v-bind="{
18
+ css: css.visible,
19
+ edge: visualEdges[i],
20
+ }">
21
+ <LayoutDragEdgeVisible
22
+ :edge="visualEdges[i]"
23
+ :css="css.visible"
24
+ />
25
+ </slot>
26
+ </template>
27
+ </template>
28
+ <script lang="ts" setup>
29
+ import { layoutContextInjectionKey } from "../types/vue.js"
30
+
31
+ import { computed, inject } from "vue"
32
+
33
+ import LayoutDragEdgeHandle from "./LayoutDragEdgeHandle.vue"
34
+ import LayoutDragEdgeVisible from "./LayoutDragEdgeVisible.vue"
35
+ import { getVisualEdgesCss } from "../helpers/getVisualEdgesCss.js"
36
+ import { isEdgeEqual } from "../helpers/isEdgeEqual.js"
37
+ import { toRef } from "vue"
38
+ import {cssToKey} from "../utils/cssToKey.js"
39
+ import { type EdgeCss } from "../types/index.js"
40
+ import { dragContextInjectionKey } from "../types/vue.js"
41
+
42
+ defineOptions({
43
+ inheritAttrs: false
44
+ })
45
+
46
+ const ctx = inject(layoutContextInjectionKey, undefined)
47
+ if (!ctx) throw new Error("LayoutEdges must be used wivisible a LayoutWindow")
48
+
49
+ const dragCtx = inject(dragContextInjectionKey, undefined)
50
+ if (!dragCtx) throw new Error("LayoutEdges must be used within a LayoutWindow")
51
+
52
+
53
+ const visualEdges = toRef(dragCtx, "visualEdges")
54
+
55
+ const draggingEdge = computed(() => dragCtx.draggingEdges.value.length === 1 ? dragCtx.draggingEdges.value[0] : undefined)
56
+
57
+ const cssDragEdges = computed(() => {
58
+ const handleEdges = getVisualEdgesCss(
59
+ draggingEdge.value
60
+ ? dragCtx.visualEdges.value.filter(_ => !isEdgeEqual(draggingEdge.value!, _))
61
+ : dragCtx.visualEdges.value,
62
+ {
63
+ edgeWidth: `var(--layoutHandleWidth, 8px)`,
64
+ padLongAxis: `var(--layoutEdgeWidth, 2px)`
65
+ }
66
+ )
67
+ const visibleEdges = getVisualEdgesCss(dragCtx.visualEdges.value, {
68
+ edgeWidth: `var(--layoutEdgeWidth, 2px)`,
69
+ padLongAxis: `(var(--layoutEdgeWidth, 2px) + var(--layoutExtraDragEdgePadding, 0px))`
70
+ })
71
+ const edges: { visible: EdgeCss, handle: EdgeCss }[] = []
72
+ for (let i = 0; i < handleEdges.length; i++) {
73
+ edges.push({ visible: visibleEdges[i], handle: handleEdges[i] })
74
+ }
75
+ return edges
76
+ })
77
+
78
+ </script>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <LayoutShapeRect
3
+ v-if="activeFrame"
4
+ :class="twMerge(`
5
+ active-frame-edge
6
+ pointer-events-none
7
+ z-0
8
+ border-blue-500
9
+ border
10
+ rounded-md
11
+ `,
12
+ ($attrs as any).class
13
+ )"
14
+ v-bind="{ ...$attrs, class: undefined }"
15
+ :css="getShapeRectCss(activeFrame, `var(--layoutEdgeWidth,2px)`)"
16
+ />
17
+ </template>
18
+
19
+ <script lang="ts" setup>
20
+ import { dragContextInjectionKey, layoutContextInjectionKey } from "../types/vue.js"
21
+ import { computed, inject, useAttrs } from "vue"
22
+ import { twMerge } from "@witchcraft/ui/utils/twMerge"
23
+
24
+ import LayoutShapeRect from "./LayoutShapeRect.vue"
25
+ import { getShapeRectCss } from "../helpers/getShapeRectCss.js"
26
+
27
+
28
+ const $attrs = useAttrs()
29
+
30
+ defineOptions({
31
+ inheritAttrs: false
32
+ })
33
+
34
+ const ctx = inject(layoutContextInjectionKey, undefined)
35
+ if (!ctx) throw new Error("LayoutEdges must be used within a LayoutWindow")
36
+
37
+ const dragCtx = inject(dragContextInjectionKey, undefined)
38
+ if (!dragCtx) throw new Error("LayoutEdges must be used within a LayoutWindow")
39
+
40
+
41
+ const activeFrame = computed(() => ctx.value.win.activeFrame ? dragCtx.frames.value[ctx.value.win.activeFrame] : undefined)
42
+ </script>
43
+
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <!-- overflow-hidden is just in case, if the frame's css is not properly set to h-full, overflow-auto, or the border/padding are too large, we can still get overflows -->
3
- <LayoutShapeSquare
3
+ <LayoutShapeRect
4
4
  tabindex="0"
5
- :css="getShapeSquareCss(frame)"
5
+ :css="getShapeRectCss(frame)"
6
6
  :class="twMerge(`
7
7
  frame
8
8
  p-[var(--layoutEdgeWidth,_2px)]
@@ -11,38 +11,45 @@
11
11
  ($attrs as any).class
12
12
  )"
13
13
  v-bind="{ ...$attrs, class: undefined }"
14
- @focus="emit('focus')"
14
+ @focus="ctx.onFocus(frame.id); emit('focus');"
15
+ v-for="frame in frames"
16
+ :key="frame.id"
15
17
  >
16
- <slot>
18
+ <slot :name="`frame-${frame.id}`" v-bind="{frame}">
17
19
  <div class="p-2 text-xs bg-neutral-500">
18
20
  {{ debugFrame(frame) }}
19
21
  </div>
20
22
  <div>No slot found for `frame-{{ frame.id }}`. </div>
21
23
  </slot>
22
- </LayoutShapeSquare>
24
+ </LayoutShapeRect>
23
25
  </template>
24
26
 
25
27
  <script lang="ts" setup>
28
+ import { dragContextInjectionKey, layoutContextInjectionKey } from "../types/vue.js"
26
29
  import { twMerge } from "@witchcraft/ui/utils/twMerge"
27
- import { useAttrs } from "vue"
30
+ import { useAttrs, inject, toRef } from "vue"
28
31
 
29
32
  const $attrs = useAttrs()
30
33
 
31
- defineOptions({
32
- inheritAttrs: false
33
- })
34
+ import LayoutShapeRect from "./LayoutShapeRect.vue"
34
35
 
35
- import LayoutShapeSquare from "./LayoutShapeSquare.vue"
36
-
37
- import { getShapeSquareCss } from "../helpers/getShapeSquareCss"
36
+ import { getShapeRectCss } from "../helpers/getShapeRectCss"
38
37
  import { debugFrame } from "../layout/debugFrame.js"
39
- import type { LayoutFrameProps } from "../types/index.js"
40
38
 
39
+ defineOptions({
40
+ inheritAttrs: false
41
+ })
41
42
 
42
43
  const emit = defineEmits<{
43
- /** Documentation #todo */
44
+ /** Focus event is always "emitted" to the provider (so it can't be cancelled), but the component also emits it in case you want to listen to it. */
44
45
  (e: "focus"): void
45
46
  }>()
46
47
 
47
- /* const props = */defineProps<LayoutFrameProps>()
48
+ const ctx = inject(layoutContextInjectionKey, undefined)
49
+ if (!ctx) throw new Error("LayoutEdges must be used within a LayoutWindow")
50
+
51
+ const dragCtx = inject(dragContextInjectionKey, undefined)
52
+ if (!dragCtx) throw new Error("LayoutEdges must be used within a LayoutWindow")
53
+
54
+ const frames = toRef(dragCtx, "frames")
48
55
  </script>