@particle-academy/react-fancy 5.59.0 → 5.60.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/CHANGELOG.md +54 -0
- package/dist/{chunk-2GFM5WD4.cjs → chunk-AGO4AA3K.cjs} +57 -9
- package/dist/chunk-AGO4AA3K.cjs.map +1 -0
- package/dist/chunk-CEFK54WP.cjs +186 -0
- package/dist/chunk-CEFK54WP.cjs.map +1 -0
- package/dist/chunk-GDF6XTHC.js +184 -0
- package/dist/chunk-GDF6XTHC.js.map +1 -0
- package/dist/{chunk-ZAX72LXL.js → chunk-JEHV7PYP.js} +58 -10
- package/dist/chunk-JEHV7PYP.js.map +1 -0
- package/dist/{chunk-6XX6OHAP.cjs → chunk-M77BB2QI.cjs} +3 -3
- package/dist/{chunk-6XX6OHAP.cjs.map → chunk-M77BB2QI.cjs.map} +1 -1
- package/dist/{chunk-YBRQTZKV.js → chunk-ZGQEKDG6.js} +3 -3
- package/dist/{chunk-YBRQTZKV.js.map → chunk-ZGQEKDG6.js.map} +1 -1
- package/dist/file-upload.cjs +3 -3
- package/dist/file-upload.d.cts +7 -1
- package/dist/file-upload.d.ts +7 -1
- package/dist/file-upload.js +1 -1
- package/dist/index.cjs +42 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/inputs.cjs +7 -7
- package/dist/inputs.js +3 -3
- package/dist/json-editor.cjs +13 -13
- package/dist/json-editor.js +2 -2
- package/dist/resizable-panels.cjs +13 -0
- package/dist/resizable-panels.cjs.map +1 -0
- package/dist/resizable-panels.d.cts +68 -0
- package/dist/resizable-panels.d.ts +68 -0
- package/dist/resizable-panels.js +4 -0
- package/dist/resizable-panels.js.map +1 -0
- package/docs/FileUpload.md +3 -0
- package/docs/ResizablePanels.md +81 -0
- package/package.json +11 -1
- package/dist/chunk-2GFM5WD4.cjs.map +0 -1
- package/dist/chunk-ZAX72LXL.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.60.0] - 2026-10-23
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`ResizablePanels`: two panels with an accessible, draggable separator.**
|
|
19
|
+
Follows the W3C window-splitter pattern. Requested by a consumer (Maipa).
|
|
20
|
+
**Nothing to do:** it is a new component; import it from the barrel or from
|
|
21
|
+
`@particle-academy/react-fancy/resizable-panels`.
|
|
22
|
+
- `value` is the primary (first) panel's size as a **percentage** (0-100),
|
|
23
|
+
with `onValueChange`, or `defaultValue` (50) uncontrolled. Clamped to
|
|
24
|
+
`min` (10) / `max` (90), which are themselves clamped to 0-100.
|
|
25
|
+
- `onValueCommit` fires once per gesture (pointer release or cancel, key
|
|
26
|
+
release) and only if the value changed, so a host can persist a per-user
|
|
27
|
+
size without writing on every frame. The host owns persistence.
|
|
28
|
+
- `orientation` names the panel layout: `"horizontal"` (side by side, a
|
|
29
|
+
vertical separator moved with ArrowLeft / ArrowRight) or `"vertical"`
|
|
30
|
+
(stacked, ArrowUp / ArrowDown). `step` (5), Shift + Arrow `largeStep` (20),
|
|
31
|
+
Home / End to `min` / `max`.
|
|
32
|
+
- The separator is `role="separator"` with `aria-valuenow/min/max`,
|
|
33
|
+
`aria-orientation`, `aria-controls` (the primary panel), `aria-label`
|
|
34
|
+
(`label` prop) and a tab stop with a `--fancy-focus` ring. Pointer and touch
|
|
35
|
+
drags use pointer capture. `disabled` freezes it.
|
|
36
|
+
- Handles: `data-react-fancy-resizable-panels` (with `data-orientation`,
|
|
37
|
+
`data-dragging`, `data-disabled`), `-panel="first" | "second"`,
|
|
38
|
+
`-separator`. New exported types `ResizablePanelsProps`,
|
|
39
|
+
`ResizablePanelsOrientation`.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **`FileUpload.Dropzone` is reachable by keyboard.** The zone was a clickable
|
|
44
|
+
div around a hidden file input with no tab stop, role or key handler, so a
|
|
45
|
+
keyboard or switch user could not open the file picker at all. Found by a
|
|
46
|
+
consumer (Maipa) on 5.59.0. **Nothing to do:** the default zone looks the
|
|
47
|
+
same, and `value` / `onChange`, `accept`, `multiple`, `maxFiles`, `maxSize`
|
|
48
|
+
and drag-and-drop are unchanged.
|
|
49
|
+
- The zone now holds a native `<button type="button">`
|
|
50
|
+
(`data-react-fancy-file-upload-browse`); Tab reaches it and Enter / Space
|
|
51
|
+
open the picker. On the default zone it wraps the icon and copy, so the
|
|
52
|
+
visible text is its name; with custom `children` it is visually hidden and
|
|
53
|
+
named "Browse files". While it has keyboard focus the zone draws a solid
|
|
54
|
+
2px `--fancy-focus` ring at full opacity with a 2px offset (gap painted
|
|
55
|
+
with `--fancy-surface`), so it meets WCAG 2.2's 3:1 for focus indicators
|
|
56
|
+
in light and dark. Hover is unchanged.
|
|
57
|
+
- New `browseLabel` prop on `FileUpload.Dropzone` overrides the button's
|
|
58
|
+
accessible name.
|
|
59
|
+
- `disabled` disables the button (not focusable, not activatable) and sets
|
|
60
|
+
`aria-disabled="true"` on the zone. The zone also carries `data-disabled`
|
|
61
|
+
and `data-drag-over`.
|
|
62
|
+
- The hidden input has an `id` the button names in `aria-controls`, an
|
|
63
|
+
`aria-label`, and `tabIndex={-1}`.
|
|
64
|
+
- The default copy is now a `<span>` inside the button rather than a `<p>`;
|
|
65
|
+
a stylesheet that targeted `[data-react-fancy-file-upload-dropzone] p`
|
|
66
|
+
should target the span instead.
|
|
67
|
+
|
|
14
68
|
## [5.59.0] - 2026-10-23
|
|
15
69
|
|
|
16
70
|
### Added
|
|
@@ -15,13 +15,19 @@ function useFileUpload() {
|
|
|
15
15
|
}
|
|
16
16
|
return ctx;
|
|
17
17
|
}
|
|
18
|
+
var DEFAULT_BROWSE_LABEL = "Browse files";
|
|
19
|
+
var DEFAULT_COPY = "Drop files here or click to browse";
|
|
18
20
|
function FileUploadDropzone({
|
|
19
21
|
children,
|
|
20
|
-
className
|
|
22
|
+
className,
|
|
23
|
+
browseLabel
|
|
21
24
|
}) {
|
|
22
25
|
const { addFiles, disabled, accept, multiple } = useFileUpload();
|
|
23
26
|
const [dragOver, setDragOver] = react.useState(false);
|
|
24
27
|
const inputRef = react.useRef(null);
|
|
28
|
+
const inputId = react.useId();
|
|
29
|
+
const custom = children !== void 0 && children !== null;
|
|
30
|
+
const inputLabel = browseLabel ?? (custom ? DEFAULT_BROWSE_LABEL : DEFAULT_COPY);
|
|
25
31
|
const handleDrop = react.useCallback(
|
|
26
32
|
(e) => {
|
|
27
33
|
e.preventDefault();
|
|
@@ -36,16 +42,31 @@ function FileUploadDropzone({
|
|
|
36
42
|
setDragOver(true);
|
|
37
43
|
}, []);
|
|
38
44
|
const handleDragLeave = react.useCallback(() => setDragOver(false), []);
|
|
45
|
+
const handleClick = react.useCallback(
|
|
46
|
+
(e) => {
|
|
47
|
+
if (disabled) return;
|
|
48
|
+
if (e.target === inputRef.current) return;
|
|
49
|
+
inputRef.current?.click();
|
|
50
|
+
},
|
|
51
|
+
[disabled]
|
|
52
|
+
);
|
|
39
53
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
40
54
|
"div",
|
|
41
55
|
{
|
|
42
56
|
"data-react-fancy-file-upload-dropzone": "",
|
|
57
|
+
"data-drag-over": dragOver ? "" : void 0,
|
|
58
|
+
"data-disabled": disabled ? "" : void 0,
|
|
59
|
+
"aria-disabled": disabled || void 0,
|
|
43
60
|
onDrop: handleDrop,
|
|
44
61
|
onDragOver: handleDragOver,
|
|
45
62
|
onDragLeave: handleDragLeave,
|
|
46
|
-
onClick:
|
|
63
|
+
onClick: handleClick,
|
|
47
64
|
className: chunkZU5Q77OT_cjs.cn(
|
|
48
|
-
"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-
|
|
65
|
+
"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-[color,background-color,border-color,box-shadow]",
|
|
66
|
+
// Full opacity on purpose: a translucent 2px ring over a dashed border
|
|
67
|
+
// falls under WCAG 2.2's 3:1 for a focus indicator. The offset gap is
|
|
68
|
+
// painted with the surface token so it matches the page in dark mode.
|
|
69
|
+
"has-focus-visible:ring-2 has-focus-visible:ring-offset-2 has-focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))] has-focus-visible:ring-offset-[color:var(--fancy-surface,var(--color-white))] dark:has-focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))] dark:has-focus-visible:ring-offset-[color:var(--fancy-surface-dark,var(--color-zinc-900))]",
|
|
49
70
|
dragOver ? "border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950" : "border-[color:var(--fancy-border-strong,var(--color-zinc-300))] hover:border-zinc-400 dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:hover:border-zinc-500",
|
|
50
71
|
disabled && "cursor-not-allowed opacity-50",
|
|
51
72
|
className
|
|
@@ -55,8 +76,11 @@ function FileUploadDropzone({
|
|
|
55
76
|
"input",
|
|
56
77
|
{
|
|
57
78
|
ref: inputRef,
|
|
79
|
+
id: inputId,
|
|
58
80
|
type: "file",
|
|
59
81
|
className: "hidden",
|
|
82
|
+
tabIndex: -1,
|
|
83
|
+
"aria-label": inputLabel,
|
|
60
84
|
onChange: (e) => {
|
|
61
85
|
if (e.target.files) addFiles(e.target.files);
|
|
62
86
|
e.target.value = "";
|
|
@@ -66,10 +90,34 @@ function FileUploadDropzone({
|
|
|
66
90
|
disabled
|
|
67
91
|
}
|
|
68
92
|
),
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
72
|
-
|
|
93
|
+
custom ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
94
|
+
children,
|
|
95
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
96
|
+
"button",
|
|
97
|
+
{
|
|
98
|
+
type: "button",
|
|
99
|
+
"data-react-fancy-file-upload-browse": "",
|
|
100
|
+
"aria-controls": inputId,
|
|
101
|
+
disabled,
|
|
102
|
+
className: "sr-only",
|
|
103
|
+
children: browseLabel ?? DEFAULT_BROWSE_LABEL
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
107
|
+
"button",
|
|
108
|
+
{
|
|
109
|
+
type: "button",
|
|
110
|
+
"data-react-fancy-file-upload-browse": "",
|
|
111
|
+
"aria-controls": inputId,
|
|
112
|
+
"aria-label": browseLabel,
|
|
113
|
+
disabled,
|
|
114
|
+
className: "flex cursor-pointer flex-col items-center rounded-md outline-none disabled:cursor-not-allowed",
|
|
115
|
+
children: [
|
|
116
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Upload, { size: 32, strokeWidth: 1.5, "aria-hidden": "true", className: "mb-2 text-zinc-400" }),
|
|
117
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-zinc-500", children: DEFAULT_COPY })
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
)
|
|
73
121
|
]
|
|
74
122
|
}
|
|
75
123
|
);
|
|
@@ -212,5 +260,5 @@ var FileUpload = Object.assign(FileUploadRoot, {
|
|
|
212
260
|
|
|
213
261
|
exports.FileUpload = FileUpload;
|
|
214
262
|
exports.useFileUpload = useFileUpload;
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
216
|
-
//# sourceMappingURL=chunk-
|
|
263
|
+
//# sourceMappingURL=chunk-AGO4AA3K.cjs.map
|
|
264
|
+
//# sourceMappingURL=chunk-AGO4AA3K.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/FileUpload/FileUpload.context.ts","../src/components/FileUpload/FileUploadDropzone.tsx","../src/components/FileUpload/FileUploadList.tsx","../src/components/FileUpload/matchesAccept.ts","../src/components/FileUpload/FileUpload.tsx"],"names":["createContext","useContext","useState","useRef","useId","useCallback","jsxs","cn","jsx","Fragment","Upload","useMemo","useEffect","File","X"],"mappings":"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","file":"chunk-AGO4AA3K.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { FileUploadContextValue } from \"./FileUpload.types\";\n\nexport const FileUploadContext =\n createContext<FileUploadContextValue | null>(null);\n\nexport function useFileUpload(): FileUploadContextValue {\n const ctx = useContext(FileUploadContext);\n if (!ctx) {\n throw new Error(\n \"FileUpload compound components must be used within <FileUpload>\",\n );\n }\n return ctx;\n}\n","import { useCallback, useId, useRef, useState } from \"react\";\nimport { Upload } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadDropzoneProps } from \"./FileUpload.types\";\n\n/** Accessible name of the browse trigger when the zone has custom children. */\nconst DEFAULT_BROWSE_LABEL = \"Browse files\";\n\n/** The default zone copy; it doubles as the trigger's accessible name. */\nconst DEFAULT_COPY = \"Drop files here or click to browse\";\n\n/*\n * Keyboard access (after 5.59.0). Until then the zone was a clickable div around a\n * display:none input: no tab stop, no role, no key handler, so a keyboard or\n * switch user could not open the picker at all.\n *\n * The trigger is a REAL <button type=\"button\">, not a div with role=\"button\":\n * focus, Enter and Space (Space on keyup, cancelled by moving off) then come\n * from the platform instead of a key handler that has to get those right.\n *\n * - Default content: the button wraps the icon and the copy, so the visible\n * text IS its accessible name and the look is unchanged.\n * - Custom children: they may contain their own interactive content, which a\n * button must not wrap, so the button is visually hidden beside them and the\n * ZONE draws the focus ring (has-focus-visible) — the focus indicator is\n * never invisible.\n *\n * A click anywhere on the zone still opens the picker, as before; a click on\n * the button bubbles to the zone and is handled there once.\n */\nexport function FileUploadDropzone({\n children,\n className,\n browseLabel,\n}: FileUploadDropzoneProps) {\n const { addFiles, disabled, accept, multiple } = useFileUpload();\n const [dragOver, setDragOver] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const custom = children !== undefined && children !== null;\n const inputLabel = browseLabel ?? (custom ? DEFAULT_BROWSE_LABEL : DEFAULT_COPY);\n\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(false);\n if (disabled) return;\n addFiles(e.dataTransfer.files);\n },\n [addFiles, disabled],\n );\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n setDragOver(true);\n }, []);\n\n const handleDragLeave = useCallback(() => setDragOver(false), []);\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n if (disabled) return;\n // The input's own click bubbles back up here; re-entering would ask for a\n // second picker.\n if (e.target === inputRef.current) return;\n inputRef.current?.click();\n },\n [disabled],\n );\n\n return (\n <div\n data-react-fancy-file-upload-dropzone=\"\"\n data-drag-over={dragOver ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n aria-disabled={disabled || undefined}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onClick={handleClick}\n className={cn(\n \"flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-[color,background-color,border-color,box-shadow]\",\n // Full opacity on purpose: a translucent 2px ring over a dashed border\n // falls under WCAG 2.2's 3:1 for a focus indicator. The offset gap is\n // painted with the surface token so it matches the page in dark mode.\n \"has-focus-visible:ring-2 has-focus-visible:ring-offset-2 has-focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))] has-focus-visible:ring-offset-[color:var(--fancy-surface,var(--color-white))] dark:has-focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))] dark:has-focus-visible:ring-offset-[color:var(--fancy-surface-dark,var(--color-zinc-900))]\",\n dragOver\n ? \"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950\"\n : \"border-[color:var(--fancy-border-strong,var(--color-zinc-300))] hover:border-zinc-400 dark:border-[color:var(--fancy-border-strong-dark,var(--color-zinc-700))] dark:hover:border-zinc-500\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n >\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n className=\"hidden\"\n tabIndex={-1}\n aria-label={inputLabel}\n onChange={(e) => {\n if (e.target.files) addFiles(e.target.files);\n\n // Reset, or choosing the SAME file again fires no `change` event at\n // all and the gesture is simply dead — \"remove it, then re-add it\"\n // and \"retry the import that just failed\" both stop working. The\n // component never sees those files to reject them; it is not asked.\n e.target.value = \"\";\n }}\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n />\n {custom ? (\n <>\n {children}\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n disabled={disabled}\n className=\"sr-only\"\n >\n {browseLabel ?? DEFAULT_BROWSE_LABEL}\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n data-react-fancy-file-upload-browse=\"\"\n aria-controls={inputId}\n aria-label={browseLabel}\n disabled={disabled}\n className=\"flex cursor-pointer flex-col items-center rounded-md outline-none disabled:cursor-not-allowed\"\n >\n <Upload size={32} strokeWidth={1.5} aria-hidden=\"true\" className=\"mb-2 text-zinc-400\" />\n <span className=\"text-sm text-zinc-500\">{DEFAULT_COPY}</span>\n </button>\n )}\n </div>\n );\n}\n\nFileUploadDropzone.displayName = \"FileUploadDropzone\";\n","import { useEffect, useMemo } from \"react\";\nimport { File, X } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { useFileUpload } from \"./FileUpload.context\";\nimport type { FileUploadListProps } from \"./FileUpload.types\";\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction getExtension(name: string): string {\n const parts = name.split(\".\");\n return parts.length > 1 ? parts.pop()!.toUpperCase() : \"\";\n}\n\nfunction ThumbnailItem({ file, index, onRemove }: { file: File; index: number; onRemove: (i: number) => void }) {\n const isImage = file.type.startsWith(\"image/\");\n const objectUrl = useMemo(() => (isImage ? URL.createObjectURL(file) : null), [file, isImage]);\n\n useEffect(() => {\n return () => {\n if (objectUrl) URL.revokeObjectURL(objectUrl);\n };\n }, [objectUrl]);\n\n return (\n <div className=\"group relative flex w-20 flex-col items-center gap-1\">\n <div className=\"relative h-20 w-20 overflow-hidden rounded-lg border border-[color:var(--fancy-border,var(--color-zinc-200))] bg-zinc-50 dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))] dark:bg-zinc-800\">\n {isImage && objectUrl ? (\n <img src={objectUrl} alt={file.name} className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full w-full flex-col items-center justify-center gap-1\">\n <File size={20} className=\"text-zinc-400\" />\n <span className=\"text-[10px] font-medium text-zinc-500\">{getExtension(file.name)}</span>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => onRemove(index)}\n className=\"absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-white opacity-0 transition-opacity hover:bg-black/80 group-hover:opacity-100\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={10} />\n </button>\n </div>\n <span className=\"w-full truncate text-center text-xs text-zinc-500\">{file.name}</span>\n </div>\n );\n}\n\nexport function FileUploadList({ thumbnail, className }: FileUploadListProps) {\n const { files, removeFile } = useFileUpload();\n\n if (files.length === 0) return null;\n\n if (thumbnail) {\n return (\n <div data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 flex flex-wrap gap-3\", className)}>\n {files.map((file, i) => (\n <ThumbnailItem key={`${file.name}-${i}`} file={file} index={i} onRemove={removeFile} />\n ))}\n </div>\n );\n }\n\n return (\n <ul data-react-fancy-file-upload-list=\"\" className={cn(\"mt-3 space-y-2\", className)}>\n {files.map((file, i) => (\n <li\n key={`${file.name}-${i}`}\n className=\"flex items-center gap-3 rounded-lg border border-[color:var(--fancy-border,var(--color-zinc-200))] px-3 py-2 text-sm dark:border-[color:var(--fancy-border-dark,var(--color-zinc-700))]\"\n >\n <File size={16} className=\"shrink-0 text-zinc-400\" />\n <span className=\"flex-1 truncate\">{file.name}</span>\n <span className=\"shrink-0 text-xs text-zinc-400\">\n {formatSize(file.size)}\n </span>\n <button\n type=\"button\"\n onClick={() => removeFile(i)}\n className=\"shrink-0 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300\"\n aria-label={`Remove ${file.name}`}\n >\n <X size={14} />\n </button>\n </li>\n ))}\n </ul>\n );\n}\n\nFileUploadList.displayName = \"FileUploadList\";\n","/**\n * Does a file satisfy an `accept` string?\n *\n * The browser enforces `accept` in the FILE PICKER only. A drag-and-drop never\n * opens a picker, so on the drop path this is the only thing standing between an\n * image-only dropzone and a dropped executable. Forwarding the attribute to the\n * hidden input is half the job; this is the other half.\n *\n * Follows the HTML `accept` grammar: a comma-separated list of extensions\n * (`.csv`), exact MIME types (`text/plain`) and type wildcards (`image/*`).\n * Matching is case-insensitive, because a drop reports whatever the OS says and\n * `TEXT/PLAIN` is a real thing to receive.\n *\n * A dropped file often carries an EMPTY `type` — the OS may not know it and\n * `File.type` is a hint, never a guarantee. That is why extension rules are\n * checked against the name and not the type: an `accept=\".csv\"` zone has to work\n * for a .csv whose type is `\"\"`, which is the common case rather than the edge.\n */\nexport function matchesAccept(file: File, accept: string): boolean {\n const rules = accept\n .split(\",\")\n .map((rule) => rule.trim().toLowerCase())\n .filter(Boolean);\n\n // An accept string that lists nothing constrains nothing. Failing closed here\n // would turn `accept=\"\"` into a dropzone that silently swallows every file.\n if (rules.length === 0) return true;\n\n const type = file.type.toLowerCase();\n const name = file.name.toLowerCase();\n\n return rules.some((rule) => {\n if (rule.startsWith(\".\")) return name.endsWith(rule);\n if (rule.endsWith(\"/*\")) return type.startsWith(rule.slice(0, -1));\n return type === rule;\n });\n}\n","import { useCallback, useMemo, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { FileUploadContext } from \"./FileUpload.context\";\nimport { FileUploadDropzone } from \"./FileUploadDropzone\";\nimport { FileUploadList } from \"./FileUploadList\";\nimport { matchesAccept } from \"./matchesAccept\";\nimport type { FileUploadProps } from \"./FileUpload.types\";\n\nfunction FileUploadRoot({\n children,\n value,\n onChange,\n accept,\n multiple = true,\n maxFiles,\n maxSize,\n disabled = false,\n className,\n}: FileUploadProps) {\n const [internalFiles, setInternalFiles] = useState<File[]>([]);\n const files = value ?? internalFiles;\n\n const addFiles = useCallback(\n (newFiles: FileList | File[]) => {\n let toAdd = Array.from(newFiles);\n\n // `accept` is enforced HERE, not only on the input: the input's attribute\n // governs the picker, and a drop never opens one.\n if (accept) {\n toAdd = toAdd.filter((f) => matchesAccept(f, accept));\n }\n\n if (maxSize) {\n toAdd = toAdd.filter((f) => f.size <= maxSize);\n }\n\n /*\n * `multiple={false}` REPLACES; `maxFiles` refuses the overflow.\n *\n * These used to be the same rule — a cap of one, first-wins — on the\n * reasoning that the two props must not disagree about what a single-file\n * upload means. But they are different statements:\n *\n * - `maxFiles={n}` is a QUOTA. Dropping five files into a three-file\n * field should keep three, not the last three.\n * - `multiple={false}` is a FIELD SHAPE: this input holds one file. Its\n * analogue is a radio group, where choosing again MOVES the selection.\n *\n * Under the old rule a single-file field could never change its mind: the\n * second choice computed `[old, new].slice(0, 1)` and discarded the new\n * file in silence. The YouGene estate worked around it by remounting the\n * component with a `key`, which is the shape of a bug rather than a\n * preference.\n */\n if (multiple === false) {\n /*\n * The FIRST of the incoming batch, not the last.\n *\n * Two separate questions hide here, and only one of them was the bug:\n *\n * - ACROSS gestures — choose a file, then choose another. The second\n * must win, or the field cannot change its mind. That is the fix.\n * - WITHIN one gesture — drop two files at once onto a single-file\n * field. Which one wins is arbitrary, and taking the first is what\n * has always shipped; there is no replacement intent in a single\n * ambiguous drop.\n *\n * Taking `slice(-1)` would have answered both with \"the last\", quietly\n * changing the second. The existing drop test caught it.\n */\n const replacement = toAdd.slice(0, 1);\n // Nothing survived the accept/size filters — leave the field alone\n // rather than clearing a file the user never asked to remove.\n const next = replacement.length > 0 ? replacement : files;\n\n if (value === undefined) setInternalFiles(next);\n onChange?.(next);\n\n return;\n }\n\n const updated = [...files, ...toAdd];\n const limited = maxFiles ? updated.slice(0, maxFiles) : updated;\n\n if (value === undefined) setInternalFiles(limited);\n onChange?.(limited);\n },\n [accept, files, maxFiles, maxSize, multiple, onChange, value],\n );\n\n const removeFile = useCallback(\n (index: number) => {\n const updated = files.filter((_, i) => i !== index);\n if (value === undefined) setInternalFiles(updated);\n onChange?.(updated);\n },\n [files, onChange, value],\n );\n\n const ctx = useMemo(\n () => ({ files, addFiles, removeFile, disabled, accept, multiple }),\n [files, addFiles, removeFile, disabled, accept, multiple],\n );\n\n return (\n <FileUploadContext.Provider value={ctx}>\n <div data-react-fancy-file-upload=\"\" className={cn(className)}>{children}</div>\n </FileUploadContext.Provider>\n );\n}\n\nexport const FileUpload = Object.assign(FileUploadRoot, {\n Dropzone: FileUploadDropzone,\n List: FileUploadList,\n});\n"]}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
function clamp(n, lo, hi) {
|
|
8
|
+
return Math.min(hi, Math.max(lo, n));
|
|
9
|
+
}
|
|
10
|
+
function tidy(n) {
|
|
11
|
+
return Math.round(n * 100) / 100;
|
|
12
|
+
}
|
|
13
|
+
var SEPARATOR_BASE = "relative shrink-0 touch-none select-none outline-none transition-colors bg-[color:var(--fancy-border,var(--color-zinc-200))] dark:bg-[color:var(--fancy-border-dark,var(--color-zinc-700))] focus-visible:bg-[color:var(--fancy-focus,var(--color-blue-500))] dark:focus-visible:bg-[color:var(--fancy-focus-dark,var(--color-blue-400))] focus-visible:ring-2 focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))]/40 dark:focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))]/40";
|
|
14
|
+
var SEPARATOR_ACTIVE = "hover:bg-[color:var(--fancy-focus,var(--color-blue-500))] dark:hover:bg-[color:var(--fancy-focus-dark,var(--color-blue-400))]";
|
|
15
|
+
var ResizablePanels = react.forwardRef(
|
|
16
|
+
({
|
|
17
|
+
children,
|
|
18
|
+
value,
|
|
19
|
+
defaultValue = 50,
|
|
20
|
+
onValueChange,
|
|
21
|
+
onValueCommit,
|
|
22
|
+
min = 10,
|
|
23
|
+
max = 90,
|
|
24
|
+
orientation = "horizontal",
|
|
25
|
+
step = 5,
|
|
26
|
+
largeStep = 20,
|
|
27
|
+
label = "Resize panels",
|
|
28
|
+
disabled = false,
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}, forwardedRef) => {
|
|
32
|
+
const lo = clamp(min, 0, 100);
|
|
33
|
+
const hi = Math.max(lo, clamp(max, 0, 100));
|
|
34
|
+
const [internal, setInternal] = react.useState(defaultValue);
|
|
35
|
+
const controlled = value !== void 0;
|
|
36
|
+
const current = tidy(clamp(controlled ? value : internal, lo, hi));
|
|
37
|
+
const latest = react.useRef(current);
|
|
38
|
+
latest.current = current;
|
|
39
|
+
const rootRef = react.useRef(null);
|
|
40
|
+
const setRefs = react.useCallback(
|
|
41
|
+
(node) => {
|
|
42
|
+
rootRef.current = node;
|
|
43
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
44
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
45
|
+
},
|
|
46
|
+
[forwardedRef]
|
|
47
|
+
);
|
|
48
|
+
const firstId = react.useId();
|
|
49
|
+
const horizontal = orientation === "horizontal";
|
|
50
|
+
const request = react.useCallback(
|
|
51
|
+
(next) => {
|
|
52
|
+
const v = tidy(clamp(next, lo, hi));
|
|
53
|
+
if (v === latest.current) return;
|
|
54
|
+
latest.current = v;
|
|
55
|
+
if (!controlled) setInternal(v);
|
|
56
|
+
onValueChange?.(v);
|
|
57
|
+
},
|
|
58
|
+
[controlled, hi, lo, onValueChange]
|
|
59
|
+
);
|
|
60
|
+
const keyChanged = react.useRef(false);
|
|
61
|
+
const onKeyDown = (e) => {
|
|
62
|
+
if (disabled) return;
|
|
63
|
+
const dec = horizontal ? "ArrowLeft" : "ArrowUp";
|
|
64
|
+
const inc = horizontal ? "ArrowRight" : "ArrowDown";
|
|
65
|
+
const by = e.shiftKey ? largeStep : step;
|
|
66
|
+
let next;
|
|
67
|
+
if (e.key === dec) next = latest.current - by;
|
|
68
|
+
else if (e.key === inc) next = latest.current + by;
|
|
69
|
+
else if (e.key === "Home") next = lo;
|
|
70
|
+
else if (e.key === "End") next = hi;
|
|
71
|
+
else return;
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
const before = latest.current;
|
|
74
|
+
request(next);
|
|
75
|
+
if (latest.current !== before) keyChanged.current = true;
|
|
76
|
+
};
|
|
77
|
+
const onKeyUp = () => {
|
|
78
|
+
if (!keyChanged.current) return;
|
|
79
|
+
keyChanged.current = false;
|
|
80
|
+
onValueCommit?.(latest.current);
|
|
81
|
+
};
|
|
82
|
+
const drag = react.useRef(null);
|
|
83
|
+
const [dragging, setDragging] = react.useState(false);
|
|
84
|
+
const valueAt = (clientX, clientY) => {
|
|
85
|
+
const rect = rootRef.current?.getBoundingClientRect();
|
|
86
|
+
if (!rect) return null;
|
|
87
|
+
const size = horizontal ? rect.width : rect.height;
|
|
88
|
+
if (!size) return null;
|
|
89
|
+
const offset = horizontal ? clientX - rect.left : clientY - rect.top;
|
|
90
|
+
return offset / size * 100;
|
|
91
|
+
};
|
|
92
|
+
const onPointerDown = (e) => {
|
|
93
|
+
if (disabled || e.button !== 0) return;
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
96
|
+
drag.current = { pointerId: e.pointerId, start: latest.current };
|
|
97
|
+
setDragging(true);
|
|
98
|
+
};
|
|
99
|
+
const onPointerMove = (e) => {
|
|
100
|
+
if (!drag.current || drag.current.pointerId !== e.pointerId) return;
|
|
101
|
+
const v = valueAt(e.clientX, e.clientY);
|
|
102
|
+
if (v !== null) request(v);
|
|
103
|
+
};
|
|
104
|
+
const endDrag = (e) => {
|
|
105
|
+
const d = drag.current;
|
|
106
|
+
if (!d || d.pointerId !== e.pointerId) return;
|
|
107
|
+
drag.current = null;
|
|
108
|
+
setDragging(false);
|
|
109
|
+
if (e.currentTarget.hasPointerCapture?.(e.pointerId)) {
|
|
110
|
+
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
111
|
+
}
|
|
112
|
+
if (latest.current !== d.start) onValueCommit?.(latest.current);
|
|
113
|
+
};
|
|
114
|
+
const items = react.Children.toArray(children);
|
|
115
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
116
|
+
"div",
|
|
117
|
+
{
|
|
118
|
+
ref: setRefs,
|
|
119
|
+
...props,
|
|
120
|
+
"data-react-fancy-resizable-panels": "",
|
|
121
|
+
"data-orientation": orientation,
|
|
122
|
+
"data-dragging": dragging ? "" : void 0,
|
|
123
|
+
"data-disabled": disabled ? "" : void 0,
|
|
124
|
+
className: chunkZU5Q77OT_cjs.cn(
|
|
125
|
+
"flex h-full w-full min-h-0 min-w-0 overflow-hidden",
|
|
126
|
+
horizontal ? "flex-row" : "flex-col",
|
|
127
|
+
dragging && (horizontal ? "cursor-col-resize select-none" : "cursor-row-resize select-none"),
|
|
128
|
+
className
|
|
129
|
+
),
|
|
130
|
+
children: [
|
|
131
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
132
|
+
"div",
|
|
133
|
+
{
|
|
134
|
+
id: firstId,
|
|
135
|
+
"data-react-fancy-resizable-panels-panel": "first",
|
|
136
|
+
className: "min-h-0 min-w-0 overflow-auto",
|
|
137
|
+
style: { flexBasis: `${current}%`, flexGrow: 0, flexShrink: 0 },
|
|
138
|
+
children: items[0]
|
|
139
|
+
}
|
|
140
|
+
),
|
|
141
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
142
|
+
"div",
|
|
143
|
+
{
|
|
144
|
+
role: "separator",
|
|
145
|
+
"data-react-fancy-resizable-panels-separator": "",
|
|
146
|
+
"aria-orientation": horizontal ? "vertical" : "horizontal",
|
|
147
|
+
"aria-valuenow": current,
|
|
148
|
+
"aria-valuemin": lo,
|
|
149
|
+
"aria-valuemax": hi,
|
|
150
|
+
"aria-controls": firstId,
|
|
151
|
+
"aria-label": label,
|
|
152
|
+
"aria-disabled": disabled || void 0,
|
|
153
|
+
tabIndex: disabled ? -1 : 0,
|
|
154
|
+
onKeyDown,
|
|
155
|
+
onKeyUp,
|
|
156
|
+
onPointerDown,
|
|
157
|
+
onPointerMove,
|
|
158
|
+
onPointerUp: endDrag,
|
|
159
|
+
onPointerCancel: endDrag,
|
|
160
|
+
onLostPointerCapture: endDrag,
|
|
161
|
+
className: chunkZU5Q77OT_cjs.cn(
|
|
162
|
+
SEPARATOR_BASE,
|
|
163
|
+
// A 1px line with a 12px hit area, so it is easy to grab by touch.
|
|
164
|
+
horizontal ? "w-px before:absolute before:inset-y-0 before:-left-1.5 before:-right-1.5" : "h-px before:absolute before:inset-x-0 before:-top-1.5 before:-bottom-1.5",
|
|
165
|
+
disabled ? "cursor-default opacity-50" : chunkZU5Q77OT_cjs.cn(SEPARATOR_ACTIVE, horizontal ? "cursor-col-resize" : "cursor-row-resize")
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
170
|
+
"div",
|
|
171
|
+
{
|
|
172
|
+
"data-react-fancy-resizable-panels-panel": "second",
|
|
173
|
+
className: "min-h-0 min-w-0 flex-1 overflow-auto",
|
|
174
|
+
children: items[1]
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
ResizablePanels.displayName = "ResizablePanels";
|
|
183
|
+
|
|
184
|
+
exports.ResizablePanels = ResizablePanels;
|
|
185
|
+
//# sourceMappingURL=chunk-CEFK54WP.cjs.map
|
|
186
|
+
//# sourceMappingURL=chunk-CEFK54WP.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ResizablePanels/ResizablePanels.tsx"],"names":["forwardRef","useState","useRef","useCallback","useId","Children","jsxs","cn","jsx"],"mappings":"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","file":"chunk-CEFK54WP.cjs","sourcesContent":["import { Children, forwardRef, useCallback, useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent, PointerEvent } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport type { ResizablePanelsProps } from \"./ResizablePanels.types\";\n\nfunction clamp(n: number, lo: number, hi: number): number {\n return Math.min(hi, Math.max(lo, n));\n}\n\n/** Two decimals: precise enough for a pixel on a 4K row, tidy in JSON. */\nfunction tidy(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\nconst SEPARATOR_BASE =\n \"relative shrink-0 touch-none select-none outline-none transition-colors \" +\n \"bg-[color:var(--fancy-border,var(--color-zinc-200))] dark:bg-[color:var(--fancy-border-dark,var(--color-zinc-700))] \" +\n \"focus-visible:bg-[color:var(--fancy-focus,var(--color-blue-500))] dark:focus-visible:bg-[color:var(--fancy-focus-dark,var(--color-blue-400))] \" +\n \"focus-visible:ring-2 focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))]/40 dark:focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))]/40\";\n\nconst SEPARATOR_ACTIVE =\n \"hover:bg-[color:var(--fancy-focus,var(--color-blue-500))] dark:hover:bg-[color:var(--fancy-focus-dark,var(--color-blue-400))]\";\n\n/**\n * Two panels and a draggable, keyboard-operable separator between them — the\n * W3C window-splitter pattern\n * (https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/).\n *\n * `value` is the PRIMARY (first) panel's share of the container, as a\n * percentage. The separator is `role=\"separator\"` with `aria-valuenow/min/max`,\n * `aria-controls` pointing at the primary panel, and a tab stop. Arrow keys\n * along the drag axis move it by `step`, Shift + Arrow by `largeStep`, Home /\n * End jump to `min` / `max`. Pointer and touch drags use pointer capture, so a\n * fast drag that leaves the bar keeps resizing.\n *\n * `onValueChange` fires continuously; `onValueCommit` fires once per gesture\n * (pointer release, key release) so a host can persist a per-user size without\n * writing on every frame. The host owns persistence.\n */\nexport const ResizablePanels = forwardRef<HTMLDivElement, ResizablePanelsProps>(\n (\n {\n children,\n value,\n defaultValue = 50,\n onValueChange,\n onValueCommit,\n min = 10,\n max = 90,\n orientation = \"horizontal\",\n step = 5,\n largeStep = 20,\n label = \"Resize panels\",\n disabled = false,\n className,\n ...props\n },\n forwardedRef,\n ) => {\n // Never past the container, whatever min/max say.\n const lo = clamp(min, 0, 100);\n const hi = Math.max(lo, clamp(max, 0, 100));\n\n const [internal, setInternal] = useState(defaultValue);\n const controlled = value !== undefined;\n const current = tidy(clamp(controlled ? value : internal, lo, hi));\n\n /*\n * The value most recently ASKED FOR, re-synced to what is rendered on every\n * render. Several key repeats can land before React re-renders; reading the\n * render-time value would make each of them compute from the same start.\n */\n const latest = useRef(current);\n latest.current = current;\n\n const rootRef = useRef<HTMLDivElement | null>(null);\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n rootRef.current = node;\n if (typeof forwardedRef === \"function\") forwardedRef(node);\n else if (forwardedRef) forwardedRef.current = node;\n },\n [forwardedRef],\n );\n\n const firstId = useId();\n const horizontal = orientation === \"horizontal\";\n\n const request = useCallback(\n (next: number) => {\n const v = tidy(clamp(next, lo, hi));\n if (v === latest.current) return;\n latest.current = v;\n if (!controlled) setInternal(v);\n onValueChange?.(v);\n },\n [controlled, hi, lo, onValueChange],\n );\n\n // --- keyboard -----------------------------------------------------------\n\n const keyChanged = useRef(false);\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n const dec = horizontal ? \"ArrowLeft\" : \"ArrowUp\";\n const inc = horizontal ? \"ArrowRight\" : \"ArrowDown\";\n const by = e.shiftKey ? largeStep : step;\n let next: number;\n if (e.key === dec) next = latest.current - by;\n else if (e.key === inc) next = latest.current + by;\n else if (e.key === \"Home\") next = lo;\n else if (e.key === \"End\") next = hi;\n else return;\n e.preventDefault();\n const before = latest.current;\n request(next);\n if (latest.current !== before) keyChanged.current = true;\n };\n\n const onKeyUp = () => {\n if (!keyChanged.current) return;\n keyChanged.current = false;\n onValueCommit?.(latest.current);\n };\n\n // --- pointer ------------------------------------------------------------\n\n const drag = useRef<{ pointerId: number; start: number } | null>(null);\n const [dragging, setDragging] = useState(false);\n\n const valueAt = (clientX: number, clientY: number): number | null => {\n const rect = rootRef.current?.getBoundingClientRect();\n if (!rect) return null;\n const size = horizontal ? rect.width : rect.height;\n if (!size) return null;\n const offset = horizontal ? clientX - rect.left : clientY - rect.top;\n return (offset / size) * 100;\n };\n\n const onPointerDown = (e: PointerEvent<HTMLDivElement>) => {\n if (disabled || e.button !== 0) return;\n e.preventDefault();\n e.currentTarget.setPointerCapture?.(e.pointerId);\n drag.current = { pointerId: e.pointerId, start: latest.current };\n setDragging(true);\n };\n\n const onPointerMove = (e: PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== e.pointerId) return;\n const v = valueAt(e.clientX, e.clientY);\n if (v !== null) request(v);\n };\n\n const endDrag = (e: PointerEvent<HTMLDivElement>) => {\n const d = drag.current;\n if (!d || d.pointerId !== e.pointerId) return;\n drag.current = null;\n setDragging(false);\n if (e.currentTarget.hasPointerCapture?.(e.pointerId)) {\n e.currentTarget.releasePointerCapture?.(e.pointerId);\n }\n if (latest.current !== d.start) onValueCommit?.(latest.current);\n };\n\n const items = Children.toArray(children);\n\n return (\n <div\n ref={setRefs}\n {...props}\n data-react-fancy-resizable-panels=\"\"\n data-orientation={orientation}\n data-dragging={dragging ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n className={cn(\n \"flex h-full w-full min-h-0 min-w-0 overflow-hidden\",\n horizontal ? \"flex-row\" : \"flex-col\",\n dragging && (horizontal ? \"cursor-col-resize select-none\" : \"cursor-row-resize select-none\"),\n className,\n )}\n >\n <div\n id={firstId}\n data-react-fancy-resizable-panels-panel=\"first\"\n className=\"min-h-0 min-w-0 overflow-auto\"\n style={{ flexBasis: `${current}%`, flexGrow: 0, flexShrink: 0 }}\n >\n {items[0]}\n </div>\n <div\n role=\"separator\"\n data-react-fancy-resizable-panels-separator=\"\"\n aria-orientation={horizontal ? \"vertical\" : \"horizontal\"}\n aria-valuenow={current}\n aria-valuemin={lo}\n aria-valuemax={hi}\n aria-controls={firstId}\n aria-label={label}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n onKeyDown={onKeyDown}\n onKeyUp={onKeyUp}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onLostPointerCapture={endDrag}\n className={cn(\n SEPARATOR_BASE,\n // A 1px line with a 12px hit area, so it is easy to grab by touch.\n horizontal\n ? \"w-px before:absolute before:inset-y-0 before:-left-1.5 before:-right-1.5\"\n : \"h-px before:absolute before:inset-x-0 before:-top-1.5 before:-bottom-1.5\",\n disabled\n ? \"cursor-default opacity-50\"\n : cn(SEPARATOR_ACTIVE, horizontal ? \"cursor-col-resize\" : \"cursor-row-resize\"),\n )}\n />\n <div\n data-react-fancy-resizable-panels-panel=\"second\"\n className=\"min-h-0 min-w-0 flex-1 overflow-auto\"\n >\n {items[1]}\n </div>\n </div>\n );\n },\n);\n\nResizablePanels.displayName = \"ResizablePanels\";\n"]}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { cn } from './chunk-MMW2JGKZ.js';
|
|
2
|
+
import { forwardRef, useState, useRef, useCallback, useId, Children } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
function clamp(n, lo, hi) {
|
|
6
|
+
return Math.min(hi, Math.max(lo, n));
|
|
7
|
+
}
|
|
8
|
+
function tidy(n) {
|
|
9
|
+
return Math.round(n * 100) / 100;
|
|
10
|
+
}
|
|
11
|
+
var SEPARATOR_BASE = "relative shrink-0 touch-none select-none outline-none transition-colors bg-[color:var(--fancy-border,var(--color-zinc-200))] dark:bg-[color:var(--fancy-border-dark,var(--color-zinc-700))] focus-visible:bg-[color:var(--fancy-focus,var(--color-blue-500))] dark:focus-visible:bg-[color:var(--fancy-focus-dark,var(--color-blue-400))] focus-visible:ring-2 focus-visible:ring-[color:var(--fancy-focus,var(--color-blue-500))]/40 dark:focus-visible:ring-[color:var(--fancy-focus-dark,var(--color-blue-400))]/40";
|
|
12
|
+
var SEPARATOR_ACTIVE = "hover:bg-[color:var(--fancy-focus,var(--color-blue-500))] dark:hover:bg-[color:var(--fancy-focus-dark,var(--color-blue-400))]";
|
|
13
|
+
var ResizablePanels = forwardRef(
|
|
14
|
+
({
|
|
15
|
+
children,
|
|
16
|
+
value,
|
|
17
|
+
defaultValue = 50,
|
|
18
|
+
onValueChange,
|
|
19
|
+
onValueCommit,
|
|
20
|
+
min = 10,
|
|
21
|
+
max = 90,
|
|
22
|
+
orientation = "horizontal",
|
|
23
|
+
step = 5,
|
|
24
|
+
largeStep = 20,
|
|
25
|
+
label = "Resize panels",
|
|
26
|
+
disabled = false,
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
}, forwardedRef) => {
|
|
30
|
+
const lo = clamp(min, 0, 100);
|
|
31
|
+
const hi = Math.max(lo, clamp(max, 0, 100));
|
|
32
|
+
const [internal, setInternal] = useState(defaultValue);
|
|
33
|
+
const controlled = value !== void 0;
|
|
34
|
+
const current = tidy(clamp(controlled ? value : internal, lo, hi));
|
|
35
|
+
const latest = useRef(current);
|
|
36
|
+
latest.current = current;
|
|
37
|
+
const rootRef = useRef(null);
|
|
38
|
+
const setRefs = useCallback(
|
|
39
|
+
(node) => {
|
|
40
|
+
rootRef.current = node;
|
|
41
|
+
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
42
|
+
else if (forwardedRef) forwardedRef.current = node;
|
|
43
|
+
},
|
|
44
|
+
[forwardedRef]
|
|
45
|
+
);
|
|
46
|
+
const firstId = useId();
|
|
47
|
+
const horizontal = orientation === "horizontal";
|
|
48
|
+
const request = useCallback(
|
|
49
|
+
(next) => {
|
|
50
|
+
const v = tidy(clamp(next, lo, hi));
|
|
51
|
+
if (v === latest.current) return;
|
|
52
|
+
latest.current = v;
|
|
53
|
+
if (!controlled) setInternal(v);
|
|
54
|
+
onValueChange?.(v);
|
|
55
|
+
},
|
|
56
|
+
[controlled, hi, lo, onValueChange]
|
|
57
|
+
);
|
|
58
|
+
const keyChanged = useRef(false);
|
|
59
|
+
const onKeyDown = (e) => {
|
|
60
|
+
if (disabled) return;
|
|
61
|
+
const dec = horizontal ? "ArrowLeft" : "ArrowUp";
|
|
62
|
+
const inc = horizontal ? "ArrowRight" : "ArrowDown";
|
|
63
|
+
const by = e.shiftKey ? largeStep : step;
|
|
64
|
+
let next;
|
|
65
|
+
if (e.key === dec) next = latest.current - by;
|
|
66
|
+
else if (e.key === inc) next = latest.current + by;
|
|
67
|
+
else if (e.key === "Home") next = lo;
|
|
68
|
+
else if (e.key === "End") next = hi;
|
|
69
|
+
else return;
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
const before = latest.current;
|
|
72
|
+
request(next);
|
|
73
|
+
if (latest.current !== before) keyChanged.current = true;
|
|
74
|
+
};
|
|
75
|
+
const onKeyUp = () => {
|
|
76
|
+
if (!keyChanged.current) return;
|
|
77
|
+
keyChanged.current = false;
|
|
78
|
+
onValueCommit?.(latest.current);
|
|
79
|
+
};
|
|
80
|
+
const drag = useRef(null);
|
|
81
|
+
const [dragging, setDragging] = useState(false);
|
|
82
|
+
const valueAt = (clientX, clientY) => {
|
|
83
|
+
const rect = rootRef.current?.getBoundingClientRect();
|
|
84
|
+
if (!rect) return null;
|
|
85
|
+
const size = horizontal ? rect.width : rect.height;
|
|
86
|
+
if (!size) return null;
|
|
87
|
+
const offset = horizontal ? clientX - rect.left : clientY - rect.top;
|
|
88
|
+
return offset / size * 100;
|
|
89
|
+
};
|
|
90
|
+
const onPointerDown = (e) => {
|
|
91
|
+
if (disabled || e.button !== 0) return;
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
94
|
+
drag.current = { pointerId: e.pointerId, start: latest.current };
|
|
95
|
+
setDragging(true);
|
|
96
|
+
};
|
|
97
|
+
const onPointerMove = (e) => {
|
|
98
|
+
if (!drag.current || drag.current.pointerId !== e.pointerId) return;
|
|
99
|
+
const v = valueAt(e.clientX, e.clientY);
|
|
100
|
+
if (v !== null) request(v);
|
|
101
|
+
};
|
|
102
|
+
const endDrag = (e) => {
|
|
103
|
+
const d = drag.current;
|
|
104
|
+
if (!d || d.pointerId !== e.pointerId) return;
|
|
105
|
+
drag.current = null;
|
|
106
|
+
setDragging(false);
|
|
107
|
+
if (e.currentTarget.hasPointerCapture?.(e.pointerId)) {
|
|
108
|
+
e.currentTarget.releasePointerCapture?.(e.pointerId);
|
|
109
|
+
}
|
|
110
|
+
if (latest.current !== d.start) onValueCommit?.(latest.current);
|
|
111
|
+
};
|
|
112
|
+
const items = Children.toArray(children);
|
|
113
|
+
return /* @__PURE__ */ jsxs(
|
|
114
|
+
"div",
|
|
115
|
+
{
|
|
116
|
+
ref: setRefs,
|
|
117
|
+
...props,
|
|
118
|
+
"data-react-fancy-resizable-panels": "",
|
|
119
|
+
"data-orientation": orientation,
|
|
120
|
+
"data-dragging": dragging ? "" : void 0,
|
|
121
|
+
"data-disabled": disabled ? "" : void 0,
|
|
122
|
+
className: cn(
|
|
123
|
+
"flex h-full w-full min-h-0 min-w-0 overflow-hidden",
|
|
124
|
+
horizontal ? "flex-row" : "flex-col",
|
|
125
|
+
dragging && (horizontal ? "cursor-col-resize select-none" : "cursor-row-resize select-none"),
|
|
126
|
+
className
|
|
127
|
+
),
|
|
128
|
+
children: [
|
|
129
|
+
/* @__PURE__ */ jsx(
|
|
130
|
+
"div",
|
|
131
|
+
{
|
|
132
|
+
id: firstId,
|
|
133
|
+
"data-react-fancy-resizable-panels-panel": "first",
|
|
134
|
+
className: "min-h-0 min-w-0 overflow-auto",
|
|
135
|
+
style: { flexBasis: `${current}%`, flexGrow: 0, flexShrink: 0 },
|
|
136
|
+
children: items[0]
|
|
137
|
+
}
|
|
138
|
+
),
|
|
139
|
+
/* @__PURE__ */ jsx(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
role: "separator",
|
|
143
|
+
"data-react-fancy-resizable-panels-separator": "",
|
|
144
|
+
"aria-orientation": horizontal ? "vertical" : "horizontal",
|
|
145
|
+
"aria-valuenow": current,
|
|
146
|
+
"aria-valuemin": lo,
|
|
147
|
+
"aria-valuemax": hi,
|
|
148
|
+
"aria-controls": firstId,
|
|
149
|
+
"aria-label": label,
|
|
150
|
+
"aria-disabled": disabled || void 0,
|
|
151
|
+
tabIndex: disabled ? -1 : 0,
|
|
152
|
+
onKeyDown,
|
|
153
|
+
onKeyUp,
|
|
154
|
+
onPointerDown,
|
|
155
|
+
onPointerMove,
|
|
156
|
+
onPointerUp: endDrag,
|
|
157
|
+
onPointerCancel: endDrag,
|
|
158
|
+
onLostPointerCapture: endDrag,
|
|
159
|
+
className: cn(
|
|
160
|
+
SEPARATOR_BASE,
|
|
161
|
+
// A 1px line with a 12px hit area, so it is easy to grab by touch.
|
|
162
|
+
horizontal ? "w-px before:absolute before:inset-y-0 before:-left-1.5 before:-right-1.5" : "h-px before:absolute before:inset-x-0 before:-top-1.5 before:-bottom-1.5",
|
|
163
|
+
disabled ? "cursor-default opacity-50" : cn(SEPARATOR_ACTIVE, horizontal ? "cursor-col-resize" : "cursor-row-resize")
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
),
|
|
167
|
+
/* @__PURE__ */ jsx(
|
|
168
|
+
"div",
|
|
169
|
+
{
|
|
170
|
+
"data-react-fancy-resizable-panels-panel": "second",
|
|
171
|
+
className: "min-h-0 min-w-0 flex-1 overflow-auto",
|
|
172
|
+
children: items[1]
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
ResizablePanels.displayName = "ResizablePanels";
|
|
181
|
+
|
|
182
|
+
export { ResizablePanels };
|
|
183
|
+
//# sourceMappingURL=chunk-GDF6XTHC.js.map
|
|
184
|
+
//# sourceMappingURL=chunk-GDF6XTHC.js.map
|