@forgeax/app-shell 0.4.0 → 0.6.1
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 +15 -0
- package/dist/react.d.ts +10 -1
- package/dist/react.js +80 -1
- package/dist/resize.css +47 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -38,9 +38,11 @@ React entry point:
|
|
|
38
38
|
|
|
39
39
|
```tsx
|
|
40
40
|
import {
|
|
41
|
+
ResizeHandle,
|
|
41
42
|
ShellSlot,
|
|
42
43
|
SlotDebugOverlay,
|
|
43
44
|
isSlotDebugEnabled,
|
|
45
|
+
useLocalSize,
|
|
44
46
|
} from '@forgeax/app-shell/react';
|
|
45
47
|
|
|
46
48
|
export function ProductShell() {
|
|
@@ -52,6 +54,19 @@ export function ProductShell() {
|
|
|
52
54
|
}
|
|
53
55
|
```
|
|
54
56
|
|
|
57
|
+
Generic persisted sizing and pointer-capture handles use the same public entry:
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import '@forgeax/app-shell/resize.css';
|
|
61
|
+
import { ResizeHandle, useLocalSize } from '@forgeax/app-shell/react';
|
|
62
|
+
|
|
63
|
+
const [width, setWidth] = useLocalSize('product.sidebar.width', 280, 180, 640);
|
|
64
|
+
|
|
65
|
+
<aside style={{ width }}>
|
|
66
|
+
<ResizeHandle orientation="col" onDrag={(delta) => setWidth((value) => value + delta)} />
|
|
67
|
+
</aside>
|
|
68
|
+
```
|
|
69
|
+
|
|
55
70
|
## Ownership boundary
|
|
56
71
|
|
|
57
72
|
| Package | Owns |
|
package/dist/react.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
1
2
|
import { ReactElement, HTMLAttributes } from 'react';
|
|
2
3
|
|
|
3
4
|
/** Stable FNV-1a color bucket for a shell slot name. */
|
|
@@ -7,6 +8,14 @@ declare function isSlotDebugEnabled(search?: string): boolean;
|
|
|
7
8
|
/** Dev-time visualization derived only from live `data-fx-slot` markers. */
|
|
8
9
|
declare function SlotDebugOverlay(): ReactElement;
|
|
9
10
|
|
|
11
|
+
declare function useLocalSize(key: string, initial: number, min: number, max: number): readonly [number, (next: number | ((previous: number) => number)) => void];
|
|
12
|
+
interface ResizeHandleProps {
|
|
13
|
+
orientation: 'col' | 'row';
|
|
14
|
+
onDrag: (delta: number) => void;
|
|
15
|
+
title?: string;
|
|
16
|
+
}
|
|
17
|
+
declare function ResizeHandle({ orientation, onDrag, title }: ResizeHandleProps): react.JSX.Element;
|
|
18
|
+
|
|
10
19
|
type ShellSlotElement = 'aside' | 'div' | 'main' | 'section';
|
|
11
20
|
interface ShellSlotProps extends HTMLAttributes<HTMLElement> {
|
|
12
21
|
as?: ShellSlotElement;
|
|
@@ -15,4 +24,4 @@ interface ShellSlotProps extends HTMLAttributes<HTMLElement> {
|
|
|
15
24
|
/** Structural shell marker that preserves the caller's semantic element. */
|
|
16
25
|
declare function ShellSlot({ as, name, ...props }: ShellSlotProps): ReactElement;
|
|
17
26
|
|
|
18
|
-
export { ShellSlot, type ShellSlotElement, type ShellSlotProps, SlotDebugOverlay, hashSlotHue, isSlotDebugEnabled };
|
|
27
|
+
export { ResizeHandle, type ResizeHandleProps, ShellSlot, type ShellSlotElement, type ShellSlotProps, SlotDebugOverlay, hashSlotHue, isSlotDebugEnabled, useLocalSize };
|
package/dist/react.js
CHANGED
|
@@ -162,6 +162,83 @@ function SlotDebugOverlay() {
|
|
|
162
162
|
);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// src/resize.tsx
|
|
166
|
+
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
167
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
168
|
+
function useLocalSize(key, initial, min, max) {
|
|
169
|
+
const clamp = (value2) => Math.min(max, Math.max(min, value2));
|
|
170
|
+
const [value, setValueRaw] = useState2(() => {
|
|
171
|
+
if (typeof window === "undefined") return initial;
|
|
172
|
+
try {
|
|
173
|
+
const raw = window.localStorage.getItem(key);
|
|
174
|
+
if (!raw) return initial;
|
|
175
|
+
const persisted = Number(raw);
|
|
176
|
+
if (!Number.isFinite(persisted)) return initial;
|
|
177
|
+
return clamp(persisted);
|
|
178
|
+
} catch {
|
|
179
|
+
return initial;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
useEffect2(() => {
|
|
183
|
+
try {
|
|
184
|
+
window.localStorage.setItem(key, String(value));
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
}, [key, value]);
|
|
188
|
+
const setValue = (next) => {
|
|
189
|
+
setValueRaw((previous) => clamp(
|
|
190
|
+
typeof next === "function" ? next(previous) : next
|
|
191
|
+
));
|
|
192
|
+
};
|
|
193
|
+
return [value, setValue];
|
|
194
|
+
}
|
|
195
|
+
function ResizeHandle({ orientation, onDrag, title }) {
|
|
196
|
+
const startRef = useRef(null);
|
|
197
|
+
const resetGlobalDragStyles = () => {
|
|
198
|
+
document.body.style.cursor = "";
|
|
199
|
+
document.body.style.userSelect = "";
|
|
200
|
+
};
|
|
201
|
+
useEffect2(() => () => {
|
|
202
|
+
if (startRef.current) resetGlobalDragStyles();
|
|
203
|
+
}, []);
|
|
204
|
+
const onPointerDown = (event) => {
|
|
205
|
+
event.preventDefault();
|
|
206
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
207
|
+
startRef.current = { x: event.clientX, y: event.clientY };
|
|
208
|
+
document.body.style.cursor = orientation === "col" ? "col-resize" : "row-resize";
|
|
209
|
+
document.body.style.userSelect = "none";
|
|
210
|
+
};
|
|
211
|
+
const onPointerMove = (event) => {
|
|
212
|
+
if (!startRef.current) return;
|
|
213
|
+
const deltaX = event.clientX - startRef.current.x;
|
|
214
|
+
const deltaY = event.clientY - startRef.current.y;
|
|
215
|
+
startRef.current = { x: event.clientX, y: event.clientY };
|
|
216
|
+
onDrag(orientation === "col" ? deltaX : deltaY);
|
|
217
|
+
};
|
|
218
|
+
const finish = (event) => {
|
|
219
|
+
if (!startRef.current) return;
|
|
220
|
+
startRef.current = null;
|
|
221
|
+
try {
|
|
222
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
resetGlobalDragStyles();
|
|
226
|
+
};
|
|
227
|
+
return /* @__PURE__ */ jsx2(
|
|
228
|
+
"div",
|
|
229
|
+
{
|
|
230
|
+
className: `resize-handle resize-handle-${orientation}`,
|
|
231
|
+
onPointerDown,
|
|
232
|
+
onPointerMove,
|
|
233
|
+
onPointerUp: finish,
|
|
234
|
+
onPointerCancel: finish,
|
|
235
|
+
title,
|
|
236
|
+
role: "separator",
|
|
237
|
+
"aria-orientation": orientation === "col" ? "vertical" : "horizontal"
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
165
242
|
// src/react.tsx
|
|
166
243
|
function ShellSlot({
|
|
167
244
|
as = "div",
|
|
@@ -171,8 +248,10 @@ function ShellSlot({
|
|
|
171
248
|
return createElement(as, { ...props, "data-fx-slot": name });
|
|
172
249
|
}
|
|
173
250
|
export {
|
|
251
|
+
ResizeHandle,
|
|
174
252
|
ShellSlot,
|
|
175
253
|
SlotDebugOverlay,
|
|
176
254
|
hashSlotHue,
|
|
177
|
-
isSlotDebugEnabled
|
|
255
|
+
isSlotDebugEnabled,
|
|
256
|
+
useLocalSize
|
|
178
257
|
};
|
package/dist/resize.css
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
.resize-handle {
|
|
2
|
+
flex-shrink: 0;
|
|
3
|
+
position: relative;
|
|
4
|
+
background: transparent;
|
|
5
|
+
z-index: 5;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.resize-handle::after {
|
|
9
|
+
content: "";
|
|
10
|
+
position: absolute;
|
|
11
|
+
background: var(--color-border-default);
|
|
12
|
+
transition: background 150ms;
|
|
13
|
+
pointer-events: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.resize-handle-col {
|
|
17
|
+
width: 4px;
|
|
18
|
+
cursor: col-resize;
|
|
19
|
+
margin: 0 -2px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.resize-handle-col::after {
|
|
23
|
+
top: 0;
|
|
24
|
+
bottom: 0;
|
|
25
|
+
left: 50%;
|
|
26
|
+
width: 1px;
|
|
27
|
+
transform: translateX(-50%);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.resize-handle-row {
|
|
31
|
+
height: 4px;
|
|
32
|
+
cursor: row-resize;
|
|
33
|
+
margin: -2px 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.resize-handle-row::after {
|
|
37
|
+
left: 0;
|
|
38
|
+
right: 0;
|
|
39
|
+
top: 50%;
|
|
40
|
+
height: 1px;
|
|
41
|
+
transform: translateY(-50%);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.resize-handle:hover::after,
|
|
45
|
+
.resize-handle:active::after {
|
|
46
|
+
background: var(--color-border-strong);
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Generic Dock, Panel, Window, and Slot composition primitives",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"sideEffects":
|
|
7
|
+
"sideEffects": ["./dist/resize.css"],
|
|
8
8
|
"files": ["dist"],
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -20,12 +20,13 @@
|
|
|
20
20
|
"./react": {
|
|
21
21
|
"types": "./dist/react.d.ts",
|
|
22
22
|
"import": "./dist/react.js"
|
|
23
|
-
}
|
|
23
|
+
},
|
|
24
|
+
"./resize.css": "./dist/resize.css"
|
|
24
25
|
},
|
|
25
26
|
"scripts": {
|
|
26
27
|
"typecheck": "tsc --noEmit",
|
|
27
28
|
"test": "bun test test",
|
|
28
|
-
"build": "tsup src/index.ts src/dock.ts src/react.tsx --format esm --dts --outDir dist",
|
|
29
|
+
"build": "tsup src/index.ts src/dock.ts src/react.tsx --format esm --dts --outDir dist && bun run scripts/copy-resize-css.ts",
|
|
29
30
|
"check": "bun run typecheck && bun run test && bun run build",
|
|
30
31
|
"release:preflight": "bun run scripts/release-preflight.ts"
|
|
31
32
|
},
|