@gridui/react-utils 0.0.0-stage → 0.0.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.
- package/README.md +48 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +7 -0
- package/dist/pointer-drag.d.ts +15 -0
- package/dist/use-pointer-drag.d.ts +9 -0
- package/docs/catalog.json +33 -0
- package/package.json +43 -3
package/README.md
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
-
#
|
|
1
|
+
# React utils
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@gridui/react-utils` owns React interaction lifecycle hooks shared by independent
|
|
4
|
+
component families. It depends on React and the React-free `@gridui/interaction`
|
|
5
|
+
helpers. It has no stylesheet or Theme dependency.
|
|
6
|
+
|
|
7
|
+
## Pointer drag
|
|
8
|
+
|
|
9
|
+
`usePointerDrag<Data>(onCancel)` returns a stable `PointerDrag<Data>` controller.
|
|
10
|
+
Each hook instance owns at most one session. Call its methods from events or
|
|
11
|
+
effects; session changes do not trigger renders. Keep visible state in the owner.
|
|
12
|
+
|
|
13
|
+
- `start(event, data)` accepts a primary, left-button pointer when no session is
|
|
14
|
+
active. It captures that pointer on `event.currentTarget`, records caller data,
|
|
15
|
+
and subscribes to Escape and blur on the element's owning window. It returns
|
|
16
|
+
whether it started; a native capture error propagates without creating a session.
|
|
17
|
+
- `get(pointerId?)` returns the active caller data, or `null` for an absent or
|
|
18
|
+
different pointer. Treat the data as the drag's starting snapshot.
|
|
19
|
+
- `finish(pointerId)` releases resources and returns that pointer's data without
|
|
20
|
+
invoking `onCancel`. The owner decides what to commit.
|
|
21
|
+
- `cancel(pointerId?)` releases resources and invokes the latest committed
|
|
22
|
+
`onCancel(data)` once. Omitting the ID cancels whichever session is active.
|
|
23
|
+
|
|
24
|
+
Wire `pointercancel` and `lostpointercapture` to `cancel(event.pointerId)`.
|
|
25
|
+
Escape, window blur, and effect disconnection cancel automatically, including
|
|
26
|
+
React Activity hiding retained content. Cleanup clears ownership before releasing
|
|
27
|
+
capture and notifying the owner, so capture-loss events cannot cancel twice.
|
|
28
|
+
The next drag can start after effects reconnect. IME-owned and already handled
|
|
29
|
+
Escape events are ignored; active Escape is claimed before modal dismissal.
|
|
30
|
+
|
|
31
|
+
The owner handles focus, preventDefault, touch-action styles, geometry, bounds,
|
|
32
|
+
controlled state, preview, commit, and rollback. `Resizable` rolls back continuous
|
|
33
|
+
size notifications on cancel; `BottomSheet` discards its preview and commits snap
|
|
34
|
+
points only after `finish`. There is no shared cache, persistence, or product state.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { usePointerDrag } from "@gridui/react-utils";
|
|
38
|
+
|
|
39
|
+
// Inside a component, where preview is caller-owned React state:
|
|
40
|
+
const drag = usePointerDrag<{ startY: number }>(() => setPreview(null));
|
|
41
|
+
// Pointer down: drag.start(event, { startY: event.clientY });
|
|
42
|
+
// Pointer move: const start = drag.get(event.pointerId);
|
|
43
|
+
// Pointer up: if (drag.finish(event.pointerId)) commitPreview();
|
|
44
|
+
// Pointer cancel / lost capture: drag.cancel(event.pointerId);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Release history
|
|
48
|
+
|
|
49
|
+
Package-owned records live in [.changeset/history](https://github.com/sudo-ui/GridUI-React-Monorepo/tree/main/packages-core/react-utils/.changeset/history).
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// src/pointer-drag.ts
|
|
2
|
+
import { listenForDragCancellation } from "@gridui/interaction";
|
|
3
|
+
var createPointerDrag = (onCancel) => {
|
|
4
|
+
let active = null;
|
|
5
|
+
const get = (pointerId) => {
|
|
6
|
+
return active && (pointerId === void 0 || active.pointerId === pointerId) ? active.data : null;
|
|
7
|
+
};
|
|
8
|
+
const detach = (pointerId) => {
|
|
9
|
+
if (!active || pointerId !== void 0 && active.pointerId !== pointerId) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const session = active;
|
|
13
|
+
active = null;
|
|
14
|
+
session.cleanup();
|
|
15
|
+
if (session.element.hasPointerCapture(session.pointerId)) {
|
|
16
|
+
session.element.releasePointerCapture(session.pointerId);
|
|
17
|
+
}
|
|
18
|
+
return session.data;
|
|
19
|
+
};
|
|
20
|
+
const cancel = (pointerId) => {
|
|
21
|
+
const data = detach(pointerId);
|
|
22
|
+
if (data) {
|
|
23
|
+
onCancel(data);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
get,
|
|
28
|
+
cancel,
|
|
29
|
+
finish: (pointerId) => {
|
|
30
|
+
return detach(pointerId);
|
|
31
|
+
},
|
|
32
|
+
start: (event, data) => {
|
|
33
|
+
if (active || event.button !== 0 || !event.isPrimary) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const element = event.currentTarget;
|
|
37
|
+
element.setPointerCapture(event.pointerId);
|
|
38
|
+
active = {
|
|
39
|
+
pointerId: event.pointerId,
|
|
40
|
+
element,
|
|
41
|
+
data,
|
|
42
|
+
cleanup: listenForDragCancellation(element, () => {
|
|
43
|
+
cancel();
|
|
44
|
+
})
|
|
45
|
+
};
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// src/use-pointer-drag.ts
|
|
52
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
53
|
+
var usePointerDrag = (onCancel) => {
|
|
54
|
+
const cancelRef = useRef(onCancel);
|
|
55
|
+
useLayoutEffect(() => {
|
|
56
|
+
cancelRef.current = onCancel;
|
|
57
|
+
});
|
|
58
|
+
const [drag] = useState(() => {
|
|
59
|
+
return createPointerDrag((data) => {
|
|
60
|
+
cancelRef.current(data);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
return () => {
|
|
65
|
+
drag.cancel();
|
|
66
|
+
};
|
|
67
|
+
}, [drag]);
|
|
68
|
+
return drag;
|
|
69
|
+
};
|
|
70
|
+
export {
|
|
71
|
+
usePointerDrag
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pointer-drag.ts", "../src/use-pointer-drag.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * @author WMXPY\n * @package ReactUtils\n * @namespace Root\n * @description Pointer Drag\n */\n\nimport { listenForDragCancellation } from \"@gridui/interaction\";\nimport type { PointerEvent } from \"react\";\n\nexport type PointerDragStart = Pick<PointerEvent<HTMLElement>, \"button\" | \"isPrimary\" | \"pointerId\" | \"currentTarget\">;\n\nexport type PointerDrag<Data extends object> = {\n readonly start: (event: PointerDragStart, data: Data) => boolean;\n readonly get: (pointerId?: number) => Data | null;\n readonly finish: (pointerId: number) => Data | null;\n readonly cancel: (pointerId?: number) => void;\n};\n\ntype Session<Data> = {\n readonly pointerId: number;\n readonly element: HTMLElement;\n readonly data: Data;\n readonly cleanup: () => void;\n};\n\nexport const createPointerDrag = <Data extends object>(onCancel: (data: Data) => void): PointerDrag<Data> => {\n let active: Session<Data> | null = null;\n const get = (pointerId?: number) => {\n return active && (pointerId === undefined || active.pointerId === pointerId) ? active.data : null;\n };\n const detach = (pointerId?: number) => {\n if (!active || (pointerId !== undefined && active.pointerId !== pointerId)) {\n return null;\n }\n const session = active;\n active = null;\n session.cleanup();\n if (session.element.hasPointerCapture(session.pointerId)) {\n session.element.releasePointerCapture(session.pointerId);\n }\n return session.data;\n };\n const cancel = (pointerId?: number) => {\n const data = detach(pointerId);\n if (data) {\n onCancel(data);\n }\n };\n return {\n get,\n cancel,\n finish: (pointerId) => {\n return detach(pointerId);\n },\n start: (event, data) => {\n if (active || event.button !== 0 || !event.isPrimary) {\n return false;\n }\n const element = event.currentTarget;\n element.setPointerCapture(event.pointerId);\n active = {\n pointerId: event.pointerId,\n element,\n data,\n cleanup: listenForDragCancellation(element, () => {\n cancel();\n }),\n };\n return true;\n },\n };\n};\n", "/**\n * @author WMXPY\n * @package ReactUtils\n * @namespace Root\n * @description Use Pointer Drag\n */\n\nimport { createPointerDrag, type PointerDrag } from \"./pointer-drag\";\nimport { useEffect, useLayoutEffect, useRef, useState } from \"react\";\n\n/** Own one pointer session; the caller owns geometry, rendering, commit and rollback. */\nexport const usePointerDrag = <Data extends object>(onCancel: (data: Data) => void): PointerDrag<Data> => {\n const cancelRef = useRef(onCancel);\n useLayoutEffect(() => {\n cancelRef.current = onCancel;\n });\n const [drag] = useState(() => {\n return createPointerDrag<Data>((data) => {\n cancelRef.current(data);\n });\n });\n useEffect(() => {\n return () => {\n drag.cancel();\n };\n }, [drag]);\n return drag;\n};\n"],
|
|
5
|
+
"mappings": ";AAOA,SAAS,iCAAiC;AAmBnC,IAAM,oBAAoB,CAAsB,aAAsD;AACzG,MAAI,SAA+B;AACnC,QAAM,MAAM,CAAC,cAAuB;AAChC,WAAO,WAAW,cAAc,UAAa,OAAO,cAAc,aAAa,OAAO,OAAO;AAAA,EACjG;AACA,QAAM,SAAS,CAAC,cAAuB;AACnC,QAAI,CAAC,UAAW,cAAc,UAAa,OAAO,cAAc,WAAY;AACxE,aAAO;AAAA,IACX;AACA,UAAM,UAAU;AAChB,aAAS;AACT,YAAQ,QAAQ;AAChB,QAAI,QAAQ,QAAQ,kBAAkB,QAAQ,SAAS,GAAG;AACtD,cAAQ,QAAQ,sBAAsB,QAAQ,SAAS;AAAA,IAC3D;AACA,WAAO,QAAQ;AAAA,EACnB;AACA,QAAM,SAAS,CAAC,cAAuB;AACnC,UAAM,OAAO,OAAO,SAAS;AAC7B,QAAI,MAAM;AACN,eAAS,IAAI;AAAA,IACjB;AAAA,EACJ;AACA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA,QAAQ,CAAC,cAAc;AACnB,aAAO,OAAO,SAAS;AAAA,IAC3B;AAAA,IACA,OAAO,CAAC,OAAO,SAAS;AACpB,UAAI,UAAU,MAAM,WAAW,KAAK,CAAC,MAAM,WAAW;AAClD,eAAO;AAAA,MACX;AACA,YAAM,UAAU,MAAM;AACtB,cAAQ,kBAAkB,MAAM,SAAS;AACzC,eAAS;AAAA,QACL,WAAW,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA,SAAS,0BAA0B,SAAS,MAAM;AAC9C,iBAAO;AAAA,QACX,CAAC;AAAA,MACL;AACA,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;;;AChEA,SAAS,WAAW,iBAAiB,QAAQ,gBAAgB;AAGtD,IAAM,iBAAiB,CAAsB,aAAsD;AACtG,QAAM,YAAY,OAAO,QAAQ;AACjC,kBAAgB,MAAM;AAClB,cAAU,UAAU;AAAA,EACxB,CAAC;AACD,QAAM,CAAC,IAAI,IAAI,SAAS,MAAM;AAC1B,WAAO,kBAAwB,CAAC,SAAS;AACrC,gBAAU,QAAQ,IAAI;AAAA,IAC1B,CAAC;AAAA,EACL,CAAC;AACD,YAAU,MAAM;AACZ,WAAO,MAAM;AACT,WAAK,OAAO;AAAA,IAChB;AAAA,EACJ,GAAG,CAAC,IAAI,CAAC;AACT,SAAO;AACX;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author WMXPY
|
|
3
|
+
* @package ReactUtils
|
|
4
|
+
* @namespace Root
|
|
5
|
+
* @description Pointer Drag
|
|
6
|
+
*/
|
|
7
|
+
import type { PointerEvent } from "react";
|
|
8
|
+
export type PointerDragStart = Pick<PointerEvent<HTMLElement>, "button" | "isPrimary" | "pointerId" | "currentTarget">;
|
|
9
|
+
export type PointerDrag<Data extends object> = {
|
|
10
|
+
readonly start: (event: PointerDragStart, data: Data) => boolean;
|
|
11
|
+
readonly get: (pointerId?: number) => Data | null;
|
|
12
|
+
readonly finish: (pointerId: number) => Data | null;
|
|
13
|
+
readonly cancel: (pointerId?: number) => void;
|
|
14
|
+
};
|
|
15
|
+
export declare const createPointerDrag: <Data extends object>(onCancel: (data: Data) => void) => PointerDrag<Data>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author WMXPY
|
|
3
|
+
* @package ReactUtils
|
|
4
|
+
* @namespace Root
|
|
5
|
+
* @description Use Pointer Drag
|
|
6
|
+
*/
|
|
7
|
+
import { type PointerDrag } from "./pointer-drag";
|
|
8
|
+
/** Own one pointer session; the caller owns geometry, rendering, commit and rollback. */
|
|
9
|
+
export declare const usePointerDrag: <Data extends object>(onCancel: (data: Data) => void) => PointerDrag<Data>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "@gridui/react-utils",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"category": "core",
|
|
6
|
+
"description": "Shared React interaction lifecycle hooks",
|
|
7
|
+
"usage": "`@gridui/react-utils` owns React interaction lifecycle hooks shared by independent component families. It depends on React and the React-free `@gridui/interaction` helpers. It has no stylesheet or Theme dependency.",
|
|
8
|
+
"documentation": [
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"exports": [
|
|
12
|
+
{
|
|
13
|
+
"name": "PointerDrag",
|
|
14
|
+
"kind": "type",
|
|
15
|
+
"importPath": "@gridui/react-utils"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "PointerDragStart",
|
|
19
|
+
"kind": "type",
|
|
20
|
+
"importPath": "@gridui/react-utils"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "usePointerDrag",
|
|
24
|
+
"kind": "function",
|
|
25
|
+
"importPath": "@gridui/react-utils"
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"styles": [],
|
|
29
|
+
"examples": [
|
|
30
|
+
"https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/react-utils/stories/getting-started.mdx",
|
|
31
|
+
"https://github.com/sudo-ui/GridUI-React-Monorepo/blob/main/packages-core/react-utils/stories/pointer-drag-lifecycle.mdx"
|
|
32
|
+
]
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gridui/react-utils",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Shared React interaction lifecycle hooks",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"docs"
|
|
11
|
+
],
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@gridui/interaction": "^1.2.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@gridui-internal/configuration": "0.0.0-internal",
|
|
27
|
+
"@jest/globals": "30.5.2",
|
|
28
|
+
"@types/jest": "30.0.0",
|
|
29
|
+
"@types/node": "26.6.6",
|
|
30
|
+
"@types/react": "19.3.0",
|
|
31
|
+
"@types/react-dom": "19.3.0",
|
|
32
|
+
"jest": "30.5.2",
|
|
33
|
+
"react": "19.3.1",
|
|
34
|
+
"react-dom": "19.3.1",
|
|
35
|
+
"typescript": "6.0.3"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": "^19.2.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "node ../../scripts/compilation/package.ts",
|
|
42
|
+
"compile": "tsc --project tsconfig.json",
|
|
43
|
+
"test": "jest",
|
|
44
|
+
"coverage": "jest --coverage"
|
|
45
|
+
}
|
|
6
46
|
}
|