@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 +30 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -0
- package/dist/runtime/components/LayoutWindow.d.vue.ts +14 -6
- package/dist/runtime/components/LayoutWindow.vue.d.ts +14 -6
- package/dist/runtime/composables/useFrames.d.ts +14 -6
- package/dist/runtime/composables/useFrames.js +8 -6
- package/dist/runtime/demo/tailwind.css +1 -1
- package/dist/runtime/move/ActionHandler.d.ts +1 -1
- package/dist/runtime/move/CloseAction.js +2 -2
- package/dist/runtime/move/FrameDragAction.js +1 -1
- package/dist/runtime/move/SplitAction.js +3 -3
- package/dist/runtime/types/index.d.ts +15 -4
- package/dist/runtime/types/vue.d.ts +1 -1
- package/package.json +1 -1
- package/src/module.ts +1 -0
- package/src/runtime/composables/useFrames.ts +43 -16
- package/src/runtime/demo/tailwind.css +4 -0
- package/src/runtime/move/ActionHandler.ts +1 -1
- package/src/runtime/move/CloseAction.ts +2 -2
- package/src/runtime/move/FrameDragAction.ts +1 -1
- package/src/runtime/move/SplitAction.ts +3 -3
- package/src/runtime/types/index.ts +25 -4
- package/src/runtime/types/vue.ts +2 -1
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
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"
|
|
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?:
|
|
354
|
-
|
|
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"
|
|
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?:
|
|
354
|
-
|
|
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"
|
|
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?:
|
|
8
|
-
|
|
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
|
|
120
|
+
e?.preventDefault();
|
|
121
121
|
window.addEventListener(moveEvent, onMove, { signal: controller.signal });
|
|
122
122
|
window.addEventListener(endEvent, moveEnd, { signal: controller.signal });
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
/**
|
|
1854
|
-
|
|
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
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 {
|
|
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<
|
|
141
|
-
|
|
142
|
-
|
|
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?:
|
|
175
|
+
context?: { type: TContextType } & TContextContext
|
|
153
176
|
} = {}
|
|
154
|
-
):
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
/**
|
|
452
|
-
|
|
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. */
|
package/src/runtime/types/vue.ts
CHANGED
|
@@ -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
|
+
|