@witchcraft/layout 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,6 +79,36 @@ declare module "@witchcraft/layout" {
79
79
  ```
80
80
  Note that `zFrameId` and `zWinId` are different as they also support constants (`zFrameIdConstants` and `zWindowIdConstants`).
81
81
 
82
+ #### Extending Move Types
83
+
84
+ You can also extend the move types. This is usually needed for creating custom actions.
85
+
86
+ Unlike frame types, this uses a pattern where you can extend as many times as you want so long as the key is prefixed with `LayoutMove` and the type satisfies `{ type: string, context: any, resolve: any, moveType: "edge" | "frame" | "other" }`.
87
+
88
+
89
+ ```ts
90
+
91
+ declare module "@witchcraft/layout" {
92
+ interface Register {
93
+ LayoutMovePicker: {
94
+ // used to discriminate the type
95
+ type: "picker",
96
+ // any other props to add to the context parameter of moveStart
97
+ // context ends up typed as { type: "picker", someState: string }
98
+ context: {
99
+ someState: string
100
+ }
101
+ // promise resolve value, for typing the return value of moveStart
102
+ resolve: string
103
+ // for typing the type parameter of moveStart so user picks supported types
104
+ moveType: "other",
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ `moveStart`'s context parameter will now be type checked as `{someState: string, type: "picker"}` and the return value will be `Promise<string>`.
111
+
82
112
  ## Basics
83
113
 
84
114
  A layout is very simple, and looks like this:
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@witchcraft/layout",
3
3
  "configKey": "witchcraftLayout",
4
- "version": "0.5.5",
4
+ "version": "0.5.7",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -17,6 +17,7 @@ const module$1 = defineNuxtModule({
17
17
  addTemplate({
18
18
  filename: "witchcraft-layout.css",
19
19
  write: true,
20
+ // keep in sync with demo tailwind.css's relevant section
20
21
  getContents: () => [
21
22
  `@source "${resolve("runtime/components")}";`,
22
23
  // the drag actions handlers and utils they use add some styles
@@ -44,7 +44,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
44
44
  y: number;
45
45
  } | undefined;
46
46
  moveDistance: number;
47
- isMoving: false | "edge" | "frame";
47
+ isMoving: false | "edge" | "frame" | "other";
48
48
  showMoving: boolean;
49
49
  movingFrameId: string | undefined;
50
50
  movingEdges: {
@@ -347,11 +347,19 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
347
347
  getUpdateWindowSizeInfo: typeof getUpdateWindowSizeInfo;
348
348
  actionHandler: ActionHandler<any, any>;
349
349
  moveContext: {
350
- moveStart: <T extends "edge" | "frame">(e: PointerEvent, type: T, data: T extends "edge" ? import("../types/index.js").EdgeMoveStartData : import("../types/index.js").FrameMoveStartData, { moveEvent, endEvent, context }?: {
350
+ moveStart: <T extends "edge" | "frame" | "other", TContextType extends keyof import("../types/index.js").ExtendedMoveTypes, TContext extends import("../types/index.js").ExtendedMoveTypes[TContextType], TContextReturn extends TContext extends {
351
+ resolve: infer R;
352
+ } ? R : never, TContextMoveType extends TContext extends {
353
+ moveType: infer M;
354
+ } ? M : never, TContextContext extends TContext extends {
355
+ context: infer C;
356
+ } ? C : never>(e: PointerEvent | undefined, type: TContextMoveType extends never ? T : TContextMoveType, data: T extends "edge" ? import("../types/index.js").EdgeMoveStartData : T extends "frame" ? import("../types/index.js").FrameMoveStartData : undefined, { moveEvent, endEvent, context }?: {
351
357
  moveEvent?: string;
352
358
  endEvent?: string;
353
- context?: Record<string, unknown>;
354
- }) => Promise<any>;
359
+ context?: {
360
+ type: TContextType;
361
+ } & TContextContext;
362
+ }) => TContext extends never ? Promise<void> : Promise<TContextReturn>;
355
363
  moveEnd: (e?: PointerEvent, { apply }?: {
356
364
  apply?: boolean;
357
365
  }) => void;
@@ -371,7 +379,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
371
379
  x: number;
372
380
  y: number;
373
381
  } | undefined>;
374
- isMoving: import("vue").Ref<false | "edge" | "frame", false | "edge" | "frame">;
382
+ isMoving: import("vue").Ref<false | "edge" | "frame" | "other", false | "edge" | "frame" | "other">;
375
383
  movingEdges: import("vue").Ref<{
376
384
  startX: number;
377
385
  startY: number;
@@ -1096,7 +1104,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
1096
1104
  y: number;
1097
1105
  } | undefined;
1098
1106
  moveDistance: number;
1099
- isMoving: false | "edge" | "frame";
1107
+ isMoving: false | "edge" | "frame" | "other";
1100
1108
  showMoving: boolean;
1101
1109
  movingFrameId: string | undefined;
1102
1110
  movingEdges: {
@@ -44,7 +44,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
44
44
  y: number;
45
45
  } | undefined;
46
46
  moveDistance: number;
47
- isMoving: false | "edge" | "frame";
47
+ isMoving: false | "edge" | "frame" | "other";
48
48
  showMoving: boolean;
49
49
  movingFrameId: string | undefined;
50
50
  movingEdges: {
@@ -347,11 +347,19 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
347
347
  getUpdateWindowSizeInfo: typeof getUpdateWindowSizeInfo;
348
348
  actionHandler: ActionHandler<any, any>;
349
349
  moveContext: {
350
- moveStart: <T extends "edge" | "frame">(e: PointerEvent, type: T, data: T extends "edge" ? import("../types/index.js").EdgeMoveStartData : import("../types/index.js").FrameMoveStartData, { moveEvent, endEvent, context }?: {
350
+ moveStart: <T extends "edge" | "frame" | "other", TContextType extends keyof import("../types/index.js").ExtendedMoveTypes, TContext extends import("../types/index.js").ExtendedMoveTypes[TContextType], TContextReturn extends TContext extends {
351
+ resolve: infer R;
352
+ } ? R : never, TContextMoveType extends TContext extends {
353
+ moveType: infer M;
354
+ } ? M : never, TContextContext extends TContext extends {
355
+ context: infer C;
356
+ } ? C : never>(e: PointerEvent | undefined, type: TContextMoveType extends never ? T : TContextMoveType, data: T extends "edge" ? import("../types/index.js").EdgeMoveStartData : T extends "frame" ? import("../types/index.js").FrameMoveStartData : undefined, { moveEvent, endEvent, context }?: {
351
357
  moveEvent?: string;
352
358
  endEvent?: string;
353
- context?: Record<string, unknown>;
354
- }) => Promise<any>;
359
+ context?: {
360
+ type: TContextType;
361
+ } & TContextContext;
362
+ }) => TContext extends never ? Promise<void> : Promise<TContextReturn>;
355
363
  moveEnd: (e?: PointerEvent, { apply }?: {
356
364
  apply?: boolean;
357
365
  }) => void;
@@ -371,7 +379,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
371
379
  x: number;
372
380
  y: number;
373
381
  } | undefined>;
374
- isMoving: import("vue").Ref<false | "edge" | "frame", false | "edge" | "frame">;
382
+ isMoving: import("vue").Ref<false | "edge" | "frame" | "other", false | "edge" | "frame" | "other">;
375
383
  movingEdges: import("vue").Ref<{
376
384
  startX: number;
377
385
  startY: number;
@@ -1096,7 +1104,7 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps,
1096
1104
  y: number;
1097
1105
  } | undefined;
1098
1106
  moveDistance: number;
1099
- isMoving: false | "edge" | "frame";
1107
+ isMoving: false | "edge" | "frame" | "other";
1100
1108
  showMoving: boolean;
1101
1109
  movingFrameId: string | undefined;
1102
1110
  movingEdges: {
@@ -1,11 +1,19 @@
1
1
  import type { Ref } from "vue";
2
- import type { EdgeMoveStartData, FrameMoveStartData, IActionHandler, IntersectionEntry, LayoutFrame, LayoutWindow } from "../types/index.js";
2
+ import type { EdgeMoveStartData, ExtendedMoveTypes, FrameMoveStartData, IActionHandler, IntersectionEntry, LayoutFrame, LayoutWindow } from "../types/index.js";
3
3
  export declare function useFrames(win: Ref<LayoutWindow>, handler: IActionHandler): {
4
- moveStart: <T extends "edge" | "frame">(e: PointerEvent, type: T, data: T extends "edge" ? EdgeMoveStartData : FrameMoveStartData, { moveEvent, endEvent, context }?: {
4
+ moveStart: <T extends "edge" | "frame" | "other", TContextType extends keyof ExtendedMoveTypes, TContext extends ExtendedMoveTypes[TContextType], TContextReturn extends TContext extends {
5
+ resolve: infer R;
6
+ } ? R : never, TContextMoveType extends TContext extends {
7
+ moveType: infer M;
8
+ } ? M : never, TContextContext extends TContext extends {
9
+ context: infer C;
10
+ } ? C : never>(e: PointerEvent | undefined, type: TContextMoveType extends never ? T : TContextMoveType, data: T extends "edge" ? EdgeMoveStartData : T extends "frame" ? FrameMoveStartData : undefined, { moveEvent, endEvent, context }?: {
5
11
  moveEvent?: string;
6
12
  endEvent?: string;
7
- context?: Record<string, unknown>;
8
- }) => Promise<any>;
13
+ context?: {
14
+ type: TContextType;
15
+ } & TContextContext;
16
+ }) => TContext extends never ? Promise<void> : Promise<TContextReturn>;
9
17
  moveEnd: (e?: PointerEvent, { apply }?: {
10
18
  apply?: boolean;
11
19
  }) => void;
@@ -25,7 +33,7 @@ export declare function useFrames(win: Ref<LayoutWindow>, handler: IActionHandle
25
33
  x: number;
26
34
  y: number;
27
35
  } | undefined>;
28
- isMoving: Ref<false | "edge" | "frame", false | "edge" | "frame">;
36
+ isMoving: Ref<false | "edge" | "frame" | "other", false | "edge" | "frame" | "other">;
29
37
  movingEdges: Ref<{
30
38
  startX: number;
31
39
  startY: number;
@@ -750,7 +758,7 @@ export declare function useFrames(win: Ref<LayoutWindow>, handler: IActionHandle
750
758
  y: number;
751
759
  } | undefined;
752
760
  moveDistance: number;
753
- isMoving: false | "edge" | "frame";
761
+ isMoving: false | "edge" | "frame" | "other";
754
762
  showMoving: boolean;
755
763
  movingFrameId: string | undefined;
756
764
  movingEdges: {
@@ -117,19 +117,21 @@ export function useFrames(win, handler) {
117
117
  });
118
118
  controller = new AbortController();
119
119
  controller.signal.addEventListener("abort", () => resetState());
120
- e.preventDefault();
120
+ e?.preventDefault();
121
121
  window.addEventListener(moveEvent, onMove, { signal: controller.signal });
122
122
  window.addEventListener(endEvent, moveEnd, { signal: controller.signal });
123
- const point = toWindowCoord(win.value, e);
124
- movePoint.value = point;
125
- moveStartPoint = { ...point };
126
- moveDirStore.update(point);
123
+ if (e) {
124
+ const point = toWindowCoord(win.value, e);
125
+ movePoint.value = point;
126
+ moveStartPoint = { ...point };
127
+ moveDirStore.update(point);
128
+ }
127
129
  eventContext.value = context;
128
130
  isMoving.value = type;
129
131
  showMoving.value = true;
130
132
  if (type === "frame") {
131
133
  movingFrameId.value = data.frameId;
132
- } else {
134
+ } else if (type === "edge") {
133
135
  const { edge, intersection } = data;
134
136
  movingIntersection.value = intersection;
135
137
  movingEdges.value = edge ? [edge] : [
@@ -1 +1 @@
1
- @import "tailwindcss";@import "../../../playground/.nuxt/witchcraft-ui.css";@import "../../../playground/.nuxt/witchcraft-layout.css";@source "../../../src/runtime/demo";button{cursor:pointer}
1
+ @import "tailwindcss";@import "../../../playground/.nuxt/witchcraft-ui.css";@import "../../../playground/.nuxt/witchcraft-layout.css";@source "../../../src/runtime/demo";@source "../../../src/runtime/move";@source "../../../src/runtime/components";@source "../../../src/runtime/utils/createSplitDecoShapes";button{cursor:pointer}
@@ -44,7 +44,7 @@ export declare class ActionHandler<TRawActions extends IAction[], TActions exten
44
44
  */
45
45
  defaultOnMoveChange?: IAction["onMoveChange"]);
46
46
  eventHandler(e: KeyboardEvent | PointerEvent, state: MoveState, forceRecalculateEdges: () => void): undefined;
47
- onMoveChange<T extends "start" | "move" | "end">(type: T, e: T extends "end" ? PointerEvent | undefined : PointerEvent, state: MoveState, forceRecalculateEdges: () => void, cancel: (e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void, resolve: T extends "end" ? undefined : ((opts: ActionResolve) => void)): MoveChangeResult;
47
+ onMoveChange<T extends "start" | "move" | "end">(type: T, e: T extends "end" | "start" ? PointerEvent | undefined : PointerEvent, state: MoveState, forceRecalculateEdges: () => void, cancel: (e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void, resolve: T extends "end" ? undefined : ((opts: ActionResolve) => void)): MoveChangeResult;
48
48
  onMoveApply(state: MoveState, forceRecalculateEdges: () => void): ActionHandlerApplyResult;
49
49
  onMoveEnded(): void;
50
50
  cancel(e: PointerEvent | KeyboardEvent | undefined, state: MoveState): void;
@@ -99,7 +99,7 @@ export class CloseAction {
99
99
  return false;
100
100
  }
101
101
  onMoveChange(type, _e, state) {
102
- if (state.moveDistance <= this.minDragDistance) {
102
+ if (type === "end" || state.moveDistance <= this.minDragDistance) {
103
103
  return { updateEdges: false, shapes: [] };
104
104
  }
105
105
  const {
@@ -150,7 +150,7 @@ export class CloseAction {
150
150
  const decos = this._getDecos(state);
151
151
  this.state.lastReturn = {
152
152
  updateEdges: !isMovingFromWindowEdge,
153
- shapes: type === "end" ? [] : decos.flatMap((_) => _.shapes)
153
+ shapes: decos.flatMap((_) => _.shapes)
154
154
  };
155
155
  return this.state.lastReturn;
156
156
  }
@@ -94,7 +94,7 @@ export class FrameDragAction {
94
94
  return false;
95
95
  }
96
96
  onMoveChange(type, _e, state) {
97
- if (state.moveDistance <= this.minDragDistance) {
97
+ if (type === "start" || state.moveDistance <= this.minDragDistance) {
98
98
  return { updateEdges: false, shapes: [], showMoving: false };
99
99
  }
100
100
  const { win, movingFrameId, moveHoveredFrame } = state;
@@ -120,7 +120,7 @@ export class SplitAction {
120
120
  }
121
121
  onMoveChange(type, _e, state) {
122
122
  const { moveHoveredFrame, moveDistance } = state;
123
- if (moveDistance <= this.minDragDistance) {
123
+ if (type === "end" || moveDistance <= this.minDragDistance) {
124
124
  return { updateEdges: false, shapes: [], showMoving: false };
125
125
  }
126
126
  let ok = false;
@@ -145,7 +145,7 @@ export class SplitAction {
145
145
  };
146
146
  this.state.lastReturn = {
147
147
  updateEdges: true,
148
- shapes: type === "end" ? [] : [errorDeco],
148
+ shapes: [errorDeco],
149
149
  showMoving: false
150
150
  };
151
151
  return this.state.lastReturn;
@@ -153,7 +153,7 @@ export class SplitAction {
153
153
  }
154
154
  this.state.lastReturn = {
155
155
  updateEdges: false,
156
- shapes: type === "end" ? [] : decos.flatMap((_) => _.shapes),
156
+ shapes: decos.flatMap((_) => _.shapes),
157
157
  showMoving: false
158
158
  };
159
159
  return this.state.lastReturn;
@@ -1,4 +1,5 @@
1
1
  import type { EnumLike } from "@alanscodelog/utils";
2
+ import type { Flatten, OrToAnd } from "@alanscodelog/utils/types";
2
3
  import { z } from "zod";
3
4
  import { KnownError } from "../utils/KnownError.js";
4
5
  export declare const zUuid: z.ZodUUID;
@@ -1843,6 +1844,12 @@ export type LayoutChange<TInfo = never> = {
1843
1844
  window?: Partial<LayoutWindow>;
1844
1845
  info?: TInfo;
1845
1846
  };
1847
+ export type ExtendedMoveTypes = Flatten<OrToAnd<keyof Register extends `LayoutMove${infer T}` ? Register extends Record<`LayoutMove${T}`, infer U> ? U extends {
1848
+ type: string;
1849
+ context: any;
1850
+ resolve: any;
1851
+ moveType: "edge" | "frame" | "other";
1852
+ } ? Record<U["type"], U> : never : never : never>>;
1846
1853
  export type MoveState = {
1847
1854
  /** The current directions in the corresponding orientations that the user is dragging in. */
1848
1855
  moveDirections: Record<Orientation, Direction | undefined>;
@@ -1850,8 +1857,12 @@ export type MoveState = {
1850
1857
  movePoint?: Point;
1851
1858
  /** Cumulative pixel distance the user has dragged from the initial click point. 0 on start, increases on each move. */
1852
1859
  moveDistance: number;
1853
- /** Whether the user is currently dragging and what type of drag. Is truthy string during all drag events. */
1854
- isMoving: boolean | "frame" | "edge";
1860
+ /**
1861
+ * Whether the user is currently moving and what type of move. Is truthy string during all move events. The actions decide what they need to set it to as "frame" and "edge" have custom behavior.
1862
+ *
1863
+ * Other is for actions that do things other than drag frames/edges which include built in behavior (e.g. frames get a frame dragging indicator at the cursor)
1864
+ */
1865
+ isMoving: boolean | "frame" | "edge" | "other";
1855
1866
  /** Whether to show the moved frames while dragging. */
1856
1867
  showMoving: boolean;
1857
1868
  /** The frame being dragged during a frame drag. Only set when isMoving is "frame". */
@@ -1926,7 +1937,7 @@ export interface IAction {
1926
1937
  *
1927
1938
  * See also {@link ActionHandler.onMoveChange} to understand it's lifecycle as it is the extended version of.
1928
1939
  */
1929
- onMoveChange: <T extends "start" | "move" | "end">(type: T, e: T extends "end" ? PointerEvent | undefined : PointerEvent, state: MoveState, forceRecalculateEdges: () => void,
1940
+ onMoveChange: <T extends "start" | "move" | "end">(type: T, e: T extends "end" | "start" ? PointerEvent | undefined : PointerEvent, state: MoveState, forceRecalculateEdges: () => void,
1930
1941
  /** Calls moveEnd with updateEdges: false. This can technically be called from "end", it should still work. */
1931
1942
  cancel: (e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void,
1932
1943
  /** Saves result to resolve moveStart promise, updates edges if you passed updateEdges then aborts the handler (only onMoveEnded will fire after) . Not available during "end" event. It's designed for resolving from other external evente (e.g. key events). */
@@ -2028,7 +2039,7 @@ export interface IActionHandler {
2028
2039
  * - onMoveApply(...) (IF moveEnd was called with apply: true, otherwise this is skipped)
2029
2040
  * - onMoveEnded() // do cleanup here, always called at the very end
2030
2041
  */
2031
- onMoveChange<T extends "start" | "move" | "end">(type: T, e: T extends "end" ? PointerEvent | undefined : PointerEvent, state: MoveState, forceRecalculateEdges: () => void,
2042
+ onMoveChange<T extends "start" | "move" | "end">(type: T, e: T extends "end" | "start" ? PointerEvent | undefined : PointerEvent, state: MoveState, forceRecalculateEdges: () => void,
2032
2043
  /** Calls moveEnd with apply: false. This can technically be called from "end", it should still work. */
2033
2044
  cancel: (e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void,
2034
2045
  /** Saves result to resolve moveStart promise, updates edges if you passed updateEdges then aborts the handler (only onMoveEnded will fire after) . Not available during "end" event. It's designed for resolving from other external evente (e.g. key events). */
@@ -33,7 +33,7 @@ export interface UseFramesContext {
33
33
  cancel: () => void;
34
34
  moveDirections: Ref<Record<Orientation, Direction | undefined>>;
35
35
  movePoint: Ref<Point | undefined>;
36
- isMoving: Ref<false | "frame" | "edge">;
36
+ isMoving: Ref<false | "frame" | "edge" | "other">;
37
37
  movingEdges: Ref<Edge[]>;
38
38
  movingIntersection: Ref<IntersectionEntry | undefined>;
39
39
  visualEdges: Ref<Edge[]>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@witchcraft/layout",
3
3
  "description": "Headless layout manager.",
4
- "version": "0.5.5",
4
+ "version": "0.5.7",
5
5
  "main": "./dist/runtime/index.js",
6
6
  "type": "module",
7
7
  "sideEffects": false,
package/src/module.ts CHANGED
@@ -35,6 +35,7 @@ export default defineNuxtModule<ModuleOptions>({
35
35
  addTemplate({
36
36
  filename: "witchcraft-layout.css",
37
37
  write: true,
38
+ // keep in sync with demo tailwind.css's relevant section
38
39
  getContents: () => [
39
40
  `@source "${resolve("runtime/components")}";`,
40
41
  // the drag actions handlers and utils they use add some styles
@@ -12,7 +12,23 @@ import { toWindowCoord } from "../helpers/toWindowCoord.js"
12
12
  import { findFramesTouchingEdge } from "../layout/findFramesTouchingEdge.js"
13
13
  import { isPointInRect } from "../layout/isPointInRect.js"
14
14
  import { MoveDirectionStore } from "../move/MoveDirectionStore.js"
15
- import type { ActionResolve, Direction, Edge, EdgeMoveStartData, FrameId, FrameMoveStartData, IActionHandler, IntersectionEntry, LayoutFrame, LayoutWindow, MoveState, Orientation, Point } from "../types/index.js"
15
+ import type {
16
+ ActionResolve,
17
+ Direction,
18
+ Edge,
19
+ EdgeMoveStartData,
20
+ ExtendedMoveTypes,
21
+ FrameId,
22
+ FrameMoveStartData,
23
+ IActionHandler,
24
+ IntersectionEntry,
25
+ LayoutFrame,
26
+ LayoutWindow,
27
+ MoveState,
28
+ Orientation,
29
+ Point
30
+ } from "../types/index.js"
31
+
16
32
 
17
33
  export function useFrames(
18
34
  win: Ref<LayoutWindow>,
@@ -34,7 +50,7 @@ export function useFrames(
34
50
 
35
51
  const touchingFramesArrays = computed(() => touchingFrames.value.map(entry => Object.values(entry)))
36
52
 
37
- const isMoving = ref<false | "frame" | "edge">(false)
53
+ const isMoving = ref<false | "frame" | "edge" | "other">(false)
38
54
  const showMoving = ref(false)
39
55
  const movePoint = ref<Point | undefined>()
40
56
 
@@ -137,11 +153,18 @@ export function useFrames(
137
153
  handler.onMoveEnded()
138
154
  }
139
155
 
140
- function moveStart<T extends "edge" | "frame">(
141
- e: PointerEvent,
142
- type: T,
156
+ function moveStart<
157
+ T extends "edge" | "frame" | "other",
158
+ TContextType extends keyof ExtendedMoveTypes,
159
+ TContext extends ExtendedMoveTypes[TContextType],
160
+ TContextReturn extends TContext extends { resolve: infer R } ? R : never,
161
+ TContextMoveType extends TContext extends { moveType: infer M } ? M : never,
162
+ TContextContext extends TContext extends { context: infer C } ? C : never
163
+ >(
164
+ e: PointerEvent | undefined,
165
+ type: TContextMoveType extends never ? T : TContextMoveType,
143
166
  // someday this will work
144
- data: T extends "edge" ? EdgeMoveStartData : FrameMoveStartData,
167
+ data: T extends "edge" ? EdgeMoveStartData : T extends "frame" ? FrameMoveStartData : undefined,
145
168
  {
146
169
  moveEvent = "pointermove",
147
170
  endEvent = "pointerup",
@@ -149,9 +172,11 @@ export function useFrames(
149
172
  }: {
150
173
  moveEvent?: string
151
174
  endEvent?: string
152
- context?: Record<string, unknown>
175
+ context?: { type: TContextType } & TContextContext
153
176
  } = {}
154
- ): Promise<any> {
177
+ ): TContext extends never
178
+ ? Promise<void>
179
+ : Promise<TContextReturn> {
155
180
  // if already dragging, abort previous (resetState resolves the old promise)
156
181
  if (controller) {
157
182
  controller.abort()
@@ -164,22 +189,24 @@ export function useFrames(
164
189
  controller = new AbortController()
165
190
  controller.signal.addEventListener("abort", () => resetState())
166
191
 
167
- e.preventDefault()
192
+ e?.preventDefault()
168
193
  window.addEventListener(moveEvent, onMove as EventListener, { signal: controller.signal })
169
194
  window.addEventListener(endEvent, moveEnd as EventListener, { signal: controller.signal })
170
195
 
171
- const point = toWindowCoord(win.value, e)
172
- movePoint.value = point
173
- moveStartPoint = { ...point }
174
- moveDirStore.update(point)
196
+ if (e) {
197
+ const point = toWindowCoord(win.value, e)
198
+ movePoint.value = point
199
+ moveStartPoint = { ...point }
200
+ moveDirStore.update(point)
201
+ }
175
202
  eventContext.value = context
176
203
 
177
- isMoving.value = type
204
+ isMoving.value = type as any
178
205
  showMoving.value = true
179
206
 
180
207
  if (type === "frame") {
181
208
  movingFrameId.value = (data as FrameMoveStartData).frameId
182
- } else {
209
+ } else if (type === "edge") {
183
210
  const { edge, intersection } = data as EdgeMoveStartData
184
211
  movingIntersection.value = intersection
185
212
 
@@ -216,7 +243,7 @@ export function useFrames(
216
243
 
217
244
  const res = handler.onMoveChange("start", e, state.value, forceRecalculateEdges, cancel, resolve)
218
245
  showMoving.value = res.showMoving ?? true
219
- return promise
246
+ return promise as any
220
247
  }
221
248
 
222
249
  function onMove(e: PointerEvent): void {
@@ -5,6 +5,10 @@
5
5
 
6
6
  /* Only needed for the dev playground: the demo App.vue lives outside the Vite project root, so Tailwind's auto-scan doesn't find the [&.deco-...] classes it defines. */
7
7
  @source "../../../src/runtime/demo";
8
+ /* Only needed for the non-nuxt demo, makes sure classes are added even if .nuxt/witchcraft-layout.css is not generated or is outdated. Keep in sync with nuxt module list. */
9
+ @source "../../../src/runtime/move";
10
+ @source "../../../src/runtime/components";
11
+ @source "../../../src/runtime/utils/createSplitDecoShapes";
8
12
 
9
13
  button {
10
14
  cursor: pointer;
@@ -123,7 +123,7 @@ export class ActionHandler<
123
123
 
124
124
  onMoveChange<T extends "start" | "move" | "end">(
125
125
  type: T,
126
- e: T extends "end" ? PointerEvent | undefined : PointerEvent,
126
+ e: T extends "end" | "start" ? PointerEvent | undefined : PointerEvent,
127
127
  state: MoveState,
128
128
  forceRecalculateEdges: () => void,
129
129
  cancel: (e: PointerEvent | KeyboardEvent | undefined, state: MoveState) => void,
@@ -155,7 +155,7 @@ export class CloseAction implements IAction {
155
155
  _e: PointerEvent | undefined,
156
156
  state: MoveState
157
157
  ): ActionChangeResult {
158
- if (state.moveDistance <= this.minDragDistance) {
158
+ if (type === "end" || state.moveDistance <= this.minDragDistance) {
159
159
  return { updateEdges: false, shapes: [] }
160
160
  }
161
161
  const {
@@ -207,7 +207,7 @@ export class CloseAction implements IAction {
207
207
  const decos = this._getDecos(state)
208
208
  this.state.lastReturn = {
209
209
  updateEdges: !isMovingFromWindowEdge,
210
- shapes: type === "end" ? [] : decos.flatMap(_ => _.shapes)
210
+ shapes: decos.flatMap(_ => _.shapes)
211
211
  }
212
212
  return this.state.lastReturn
213
213
  }
@@ -155,7 +155,7 @@ export class FrameDragAction implements IAction {
155
155
  _e: PointerEvent | undefined,
156
156
  state: MoveState
157
157
  ): ActionChangeResult {
158
- if (state.moveDistance <= this.minDragDistance) {
158
+ if (type === "start" || state.moveDistance <= this.minDragDistance) {
159
159
  return { updateEdges: false, shapes: [], showMoving: false }
160
160
  }
161
161
  const { win, movingFrameId, moveHoveredFrame } = state
@@ -179,7 +179,7 @@ export class SplitAction implements IAction {
179
179
  state: MoveState
180
180
  ): ActionChangeResult {
181
181
  const { moveHoveredFrame, moveDistance } = state
182
- if (moveDistance <= this.minDragDistance) {
182
+ if (type === "end" || moveDistance <= this.minDragDistance) {
183
183
  return { updateEdges: false, shapes: [], showMoving: false }
184
184
  }
185
185
  let ok = false
@@ -205,7 +205,7 @@ export class SplitAction implements IAction {
205
205
  }
206
206
  this.state.lastReturn = {
207
207
  updateEdges: true,
208
- shapes: type === "end" ? [] : [errorDeco],
208
+ shapes: [errorDeco],
209
209
  showMoving: false
210
210
  }
211
211
  return this.state.lastReturn
@@ -213,7 +213,7 @@ export class SplitAction implements IAction {
213
213
  }
214
214
  this.state.lastReturn = {
215
215
  updateEdges: false,
216
- shapes: type === "end" ? [] : decos.flatMap(_ => _.shapes),
216
+ shapes: decos.flatMap(_ => _.shapes),
217
217
  showMoving: false
218
218
  }
219
219
  return this.state.lastReturn
@@ -1,5 +1,6 @@
1
1
  import type { EnumLike } from "@alanscodelog/utils"
2
2
  import { enumFromArray } from "@alanscodelog/utils/enumFromArray"
3
+ import type { Flatten, OrToAnd } from "@alanscodelog/utils/types"
3
4
  import { z } from "zod"
4
5
 
5
6
  import { KnownError } from "../utils/KnownError.js"
@@ -441,6 +442,22 @@ export type LayoutChange<TInfo = never> = {
441
442
  window?: Partial<LayoutWindow>
442
443
  info?: TInfo
443
444
  }
445
+
446
+
447
+ // #todo we also need to change how the normal types are extended to this pattern
448
+ export type ExtendedMoveTypes = Flatten<OrToAnd<keyof Register extends `LayoutMove${infer T}`
449
+ ? Register extends Record<`LayoutMove${T}`, infer U>
450
+ ? U extends {
451
+ type: string
452
+ context: any
453
+ resolve: any
454
+ moveType: "edge" | "frame" | "other"
455
+ }
456
+ ? Record<U["type"], U>
457
+ : never
458
+ : never
459
+ : never>>
460
+
444
461
  export type MoveState = {
445
462
  /** The current directions in the corresponding orientations that the user is dragging in. */
446
463
  moveDirections: Record<Orientation, Direction | undefined>
@@ -448,8 +465,12 @@ export type MoveState = {
448
465
  movePoint?: Point
449
466
  /** Cumulative pixel distance the user has dragged from the initial click point. 0 on start, increases on each move. */
450
467
  moveDistance: number
451
- /** Whether the user is currently dragging and what type of drag. Is truthy string during all drag events. */
452
- isMoving: boolean | "frame" | "edge"
468
+ /**
469
+ * Whether the user is currently moving and what type of move. Is truthy string during all move events. The actions decide what they need to set it to as "frame" and "edge" have custom behavior.
470
+ *
471
+ * Other is for actions that do things other than drag frames/edges which include built in behavior (e.g. frames get a frame dragging indicator at the cursor)
472
+ */
473
+ isMoving: boolean | "frame" | "edge" | "other"
453
474
  /** Whether to show the moved frames while dragging. */
454
475
  showMoving: boolean
455
476
  /** The frame being dragged during a frame drag. Only set when isMoving is "frame". */
@@ -531,7 +552,7 @@ export interface IAction {
531
552
  */
532
553
  onMoveChange: <T extends "start" | "move" | "end">(
533
554
  type: T,
534
- e: T extends "end" ? PointerEvent | undefined : PointerEvent,
555
+ e: T extends "end" | "start" ? PointerEvent | undefined : PointerEvent,
535
556
  state: MoveState,
536
557
  forceRecalculateEdges: () => void,
537
558
  /** Calls moveEnd with updateEdges: false. This can technically be called from "end", it should still work. */
@@ -644,7 +665,7 @@ export interface IActionHandler {
644
665
  */
645
666
  onMoveChange<T extends "start" | "move" | "end">(
646
667
  type: T,
647
- e: T extends "end" ? PointerEvent | undefined : PointerEvent,
668
+ e: T extends "end" | "start" ? PointerEvent | undefined : PointerEvent,
648
669
  state: MoveState,
649
670
  forceRecalculateEdges: () => void,
650
671
  /** Calls moveEnd with apply: false. This can technically be called from "end", it should still work. */
@@ -25,7 +25,7 @@ export interface UseFramesContext {
25
25
  cancel: () => void
26
26
  moveDirections: Ref<Record<Orientation, Direction | undefined>>
27
27
  movePoint: Ref<Point | undefined>
28
- isMoving: Ref<false | "frame" | "edge">
28
+ isMoving: Ref<false | "frame" | "edge" | "other">
29
29
  movingEdges: Ref<Edge[]>
30
30
  movingIntersection: Ref<IntersectionEntry | undefined>
31
31
  visualEdges: Ref<Edge[]>
@@ -41,3 +41,4 @@ export interface UseFramesContext {
41
41
  }
42
42
 
43
43
  export const moveContextInjectionKey = Symbol.for("@witchcraft/layout:moveContext") as InjectionKey<UseFramesContext>
44
+