panelui-web 0.9.0 → 0.10.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.
@@ -0,0 +1,306 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Workbench — the frame of an editor: a canvas with the work on it, a
5
+ * toolbar floating over it, an inspector beside it and a timeline under it.
6
+ *
7
+ * ```tsx
8
+ * <Workbench className="h-[640px]">
9
+ * <Workbench.Bar><Toolbar>…</Toolbar></Workbench.Bar>
10
+ * <Workbench.Canvas>
11
+ * <Workbench.Artboard aspect="16 / 9">…</Workbench.Artboard>
12
+ * </Workbench.Canvas>
13
+ * <Workbench.Inspector><Inspector>…</Inspector></Workbench.Inspector>
14
+ * <Workbench.Timeline><Timeline>…</Timeline></Workbench.Timeline>
15
+ * </Workbench>
16
+ * ```
17
+ *
18
+ * ## The work is in the middle
19
+ *
20
+ * Everything else stands round the canvas as a panel of its own, floating
21
+ * a few pixels clear of its neighbours, so the work reads as the thing on
22
+ * the desk and the tools as things beside it. The artboard sits centred on
23
+ * a field of dots at the largest size its shape fits.
24
+ *
25
+ * ## Focus clears the desk
26
+ *
27
+ * In focus the sidebar, the inspector and the timeline slide away and the
28
+ * canvas takes the room they leave. `\` toggles it from the keyboard, and
29
+ * `useWorkbench()` gives a button of your own the same switch.
30
+ *
31
+ * ## On a narrow screen the inspector becomes a sheet
32
+ *
33
+ * Below `compactBelow` pixels wide there is no room for a column beside the
34
+ * canvas. The inspector then opens as a sheet from the side, and
35
+ * `Workbench.InspectorTrigger` is the button that opens it. The width that
36
+ * counts is the workbench's own, not the window's, so it works in a pane.
37
+ */
38
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
39
+ import { tv } from 'tailwind-variants';
40
+ import { Sheet } from '../sheet/index.js';
41
+ const EASE = 'ease-[cubic-bezier(0.32,0.72,0,1)]';
42
+ const workbenchVariants = tv({
43
+ slots: {
44
+ root: [
45
+ 'group/workbench relative grid w-full min-w-0 overflow-hidden rounded-[26px] bg-muted/60 p-2 text-foreground',
46
+ 'ring-1 ring-foreground/[0.06] dark:bg-muted/40 dark:ring-foreground/10',
47
+ '[grid-template-areas:"sidebar_canvas_inspector""timeline_timeline_timeline"]',
48
+ 'grid-cols-[var(--wb-sidebar)_minmax(0,1fr)_var(--wb-inspector)] grid-rows-[minmax(0,1fr)_var(--wb-timeline)]',
49
+ // No grid gap: each panel carries its own 8px on the canvas side, so a
50
+ // column that closes takes its gap with it.
51
+ `transition-[grid-template-columns,grid-template-rows] duration-500 ${EASE} motion-reduce:transition-none`,
52
+ 'data-resizing:transition-none',
53
+ ],
54
+ sidebar: [
55
+ 'flex min-h-0 min-w-0 flex-col overflow-hidden pe-2 [grid-area:sidebar]',
56
+ `transition-[opacity,translate] duration-500 ${EASE} motion-reduce:transition-none`,
57
+ 'group-data-focus/workbench:-translate-x-4 group-data-focus/workbench:opacity-0',
58
+ ],
59
+ canvas: [
60
+ 'relative isolate flex min-h-0 min-w-0 items-center justify-center overflow-hidden rounded-[20px] px-6 pt-[4.5rem] pb-6 [grid-area:canvas] [container-type:size]',
61
+ // The top padding is the floating bar's room, so the work is centred in what is left.
62
+ 'bg-background ring-1 ring-foreground/[0.05] dark:bg-background/70',
63
+ 'bg-[radial-gradient(color-mix(in_oklab,var(--foreground)_13%,transparent)_1px,transparent_1.5px)] bg-size-[20px_20px] bg-center',
64
+ ],
65
+ artboard: [
66
+ 'relative overflow-hidden rounded-xl bg-background shadow-[0_24px_60px_-24px_rgb(0_0_0/0.45)] ring-1 ring-foreground/10',
67
+ 'aspect-(--wb-aspect) w-[min(100cqw,100cqh*var(--wb-ratio))]',
68
+ `transition-[width] duration-500 ${EASE} motion-reduce:transition-none`,
69
+ ],
70
+ bar: 'pointer-events-none z-10 mt-3 flex justify-center self-start justify-self-center [grid-area:canvas] *:pointer-events-auto',
71
+ inspector: [
72
+ 'flex min-h-0 min-w-0 justify-end overflow-hidden ps-2 [grid-area:inspector]',
73
+ `transition-[opacity,translate] duration-500 ${EASE} motion-reduce:transition-none`,
74
+ 'group-data-focus/workbench:translate-x-4 group-data-focus/workbench:opacity-0',
75
+ // The panel inside fills the column's height.
76
+ '[&_[data-slot=inspector]]:h-full [&_[data-slot=inspector-panel]]:h-full',
77
+ ],
78
+ timeline: [
79
+ 'relative flex min-h-0 min-w-0 flex-col overflow-hidden pt-2 [grid-area:timeline]',
80
+ `transition-[opacity,translate] duration-500 ${EASE} motion-reduce:transition-none`,
81
+ 'group-data-focus/workbench:translate-y-4 group-data-focus/workbench:opacity-0',
82
+ '[&>[data-slot=timeline]]:min-h-0 [&>[data-slot=timeline]]:flex-1',
83
+ ],
84
+ resize: [
85
+ 'absolute inset-x-0 top-0 z-20 flex h-2 cursor-row-resize items-center justify-center outline-none',
86
+ "after:h-1 after:w-10 after:rounded-full after:bg-foreground/20 after:transition-[background-color,width] after:duration-150 after:content-['']",
87
+ 'hover:after:w-16 hover:after:bg-foreground/40 focus-visible:after:w-16 focus-visible:after:bg-ring data-active:after:w-16 data-active:after:bg-foreground/50',
88
+ ],
89
+ trigger: [
90
+ 'inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-xl text-muted-foreground outline-none',
91
+ 'transition-[color,background-color,scale] duration-150 hover:bg-foreground/[0.06] hover:text-foreground active:scale-[0.94]',
92
+ 'focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-[1.125rem] [&_svg]:shrink-0',
93
+ ],
94
+ // In the sheet, the sheet is the surface: the inspector's own card gives
95
+ // up its fill, ring and shadow rather than stand as a box in a box.
96
+ sheetBody: [
97
+ 'flex min-h-0 flex-1 px-2 pb-2 [&_[data-slot=inspector]]:h-full [&_[data-slot=inspector]]:w-full',
98
+ '[&_[data-slot=inspector-panel]]:h-full [&_[data-slot=inspector-panel]]:w-full [&_[data-slot=inspector-panel]]:bg-transparent',
99
+ '[&_[data-slot=inspector-panel]]:shadow-none [&_[data-slot=inspector-panel]]:ring-0',
100
+ ],
101
+ },
102
+ });
103
+ const WorkbenchContext = createContext(null);
104
+ function useWorkbenchContext(part) {
105
+ const context = useContext(WorkbenchContext);
106
+ if (!context)
107
+ throw new Error(`${part} must be used inside a <Workbench>.`);
108
+ return context;
109
+ }
110
+ /**
111
+ * The workbench's state, for controls of your own: a focus button in the
112
+ * toolbar, a button that opens the inspector on a narrow screen.
113
+ */
114
+ export function useWorkbench() {
115
+ const { focus, setFocus, compact, inspectorOpen, setInspectorOpen } = useWorkbenchContext('useWorkbench');
116
+ return { focus, setFocus, compact, inspectorOpen, setInspectorOpen };
117
+ }
118
+ /** The editor's frame. Give it a height, such as `h-[640px]` or `h-dvh`. */
119
+ function WorkbenchRoot({ focus: focusProp, defaultFocus = false, onFocusChange, focusShortcut = true, compactBelow = 760, inspectorWidth = 348, sidebarWidth = 240, defaultTimelineHeight = 232, className, style, children, ...props }) {
120
+ const slots = workbenchVariants();
121
+ const [innerFocus, setInnerFocus] = useState(defaultFocus);
122
+ const focus = focusProp ?? innerFocus;
123
+ const [compact, setCompact] = useState(false);
124
+ const [inspectorOpen, setInspectorOpen] = useState(false);
125
+ const [parts, setParts] = useState({ sidebar: false, inspector: false, timeline: false });
126
+ const [timelineHeight, setTimelineHeight] = useState(defaultTimelineHeight);
127
+ const [resizing, setResizing] = useState(false);
128
+ const root = useRef(null);
129
+ const live = useRef({ focus, onFocusChange, controlled: focusProp !== undefined });
130
+ live.current = { focus, onFocusChange, controlled: focusProp !== undefined };
131
+ const setFocus = useCallback((next) => {
132
+ if (!live.current.controlled)
133
+ setInnerFocus(next);
134
+ live.current.onFocusChange?.(next);
135
+ }, []);
136
+ // The workbench's own width decides, so it works in a pane as on a page.
137
+ useEffect(() => {
138
+ const box = root.current;
139
+ if (!box)
140
+ return;
141
+ const observer = new ResizeObserver(() => setCompact(box.clientWidth < compactBelow));
142
+ observer.observe(box);
143
+ return () => observer.disconnect();
144
+ }, [compactBelow]);
145
+ useEffect(() => {
146
+ if (!compact)
147
+ setInspectorOpen(false);
148
+ }, [compact]);
149
+ useEffect(() => {
150
+ if (!focusShortcut)
151
+ return;
152
+ const onKey = (event) => {
153
+ if (event.key !== '\\' || event.metaKey || event.ctrlKey || event.altKey || event.defaultPrevented)
154
+ return;
155
+ const target = event.target;
156
+ if (target instanceof Element && target.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"]')) {
157
+ return;
158
+ }
159
+ event.preventDefault();
160
+ setFocus(!live.current.focus);
161
+ };
162
+ document.addEventListener('keydown', onKey);
163
+ return () => document.removeEventListener('keydown', onKey);
164
+ }, [focusShortcut, setFocus]);
165
+ const mark = (part) => (present) => setParts((all) => (all[part] === present ? all : { ...all, [part]: present }));
166
+ const context = useMemo(() => ({
167
+ focus,
168
+ setFocus,
169
+ compact,
170
+ inspectorOpen,
171
+ setInspectorOpen,
172
+ hasSidebar: mark('sidebar'),
173
+ hasInspector: mark('inspector'),
174
+ hasTimeline: mark('timeline'),
175
+ timelineHeight,
176
+ setTimelineHeight,
177
+ setResizing,
178
+ rootHeight: () => root.current?.clientHeight ?? 0,
179
+ }), [focus, setFocus, compact, inspectorOpen, timelineHeight]);
180
+ const showSidebar = parts.sidebar && !focus && !compact;
181
+ const showInspector = parts.inspector && !focus && !compact;
182
+ const showTimeline = parts.timeline && !focus;
183
+ return (_jsx(WorkbenchContext.Provider, { value: context, children: _jsx("div", { ref: root, "data-slot": "workbench", "data-focus": focus ? '' : undefined, "data-compact": compact ? '' : undefined, "data-resizing": resizing ? '' : undefined, className: slots.root({ className }), style: {
184
+ ...style,
185
+ '--wb-sidebar': showSidebar ? `${sidebarWidth}px` : '0px',
186
+ '--wb-inspector': showInspector ? `${inspectorWidth}px` : '0px',
187
+ '--wb-timeline': showTimeline ? `${timelineHeight}px` : '0px',
188
+ }, ...props, children: children }) }));
189
+ }
190
+ WorkbenchRoot.displayName = 'Workbench';
191
+ /** Tells the workbench a part is in it, so its column or row gets room. */
192
+ function usePresence(register) {
193
+ useEffect(() => {
194
+ register(true);
195
+ return () => register(false);
196
+ }, []);
197
+ }
198
+ /** The strip floating at the top of the canvas, for a toolbar. */
199
+ function WorkbenchBar({ className, ...props }) {
200
+ useWorkbenchContext('Workbench.Bar');
201
+ const slots = workbenchVariants();
202
+ return _jsx("div", { "data-slot": "workbench-bar", className: slots.bar({ className }), ...props });
203
+ }
204
+ WorkbenchBar.displayName = 'Workbench.Bar';
205
+ /** A column before the canvas, for layers, pages or assets. It is hidden on a narrow screen and in focus. */
206
+ function WorkbenchSidebar({ className, ...props }) {
207
+ const workbench = useWorkbenchContext('Workbench.Sidebar');
208
+ usePresence(workbench.hasSidebar);
209
+ const slots = workbenchVariants();
210
+ return (_jsx("aside", { "data-slot": "workbench-sidebar", inert: workbench.focus || workbench.compact || undefined, className: slots.sidebar({ className }), ...props }));
211
+ }
212
+ WorkbenchSidebar.displayName = 'Workbench.Sidebar';
213
+ /** The stage in the middle: a field of dots with the work centred on it. */
214
+ function WorkbenchCanvas({ className, ...props }) {
215
+ useWorkbenchContext('Workbench.Canvas');
216
+ const slots = workbenchVariants();
217
+ return _jsx("div", { "data-slot": "workbench-canvas", className: slots.canvas({ className }), ...props });
218
+ }
219
+ WorkbenchCanvas.displayName = 'Workbench.Canvas';
220
+ /** The work itself: as large as its shape fits on the canvas, clear of the floating bar. */
221
+ function WorkbenchArtboard({ aspect = '16 / 9', className, style, ...props }) {
222
+ const slots = workbenchVariants();
223
+ const [wide, tall] = aspect.split('/').map((part) => Number(part.trim()) || 1);
224
+ return (_jsx("div", { "data-slot": "workbench-artboard", className: slots.artboard({ className }), style: { ...style, '--wb-aspect': aspect, '--wb-ratio': (wide ?? 1) / (tall ?? 1) }, ...props }));
225
+ }
226
+ WorkbenchArtboard.displayName = 'Workbench.Artboard';
227
+ /**
228
+ * The column after the canvas, for an `Inspector`. On a narrow screen the
229
+ * same children open in a sheet from the side instead.
230
+ */
231
+ function WorkbenchInspector({ title = 'Inspector', className, children, ...props }) {
232
+ const workbench = useWorkbenchContext('Workbench.Inspector');
233
+ usePresence(workbench.hasInspector);
234
+ const slots = workbenchVariants();
235
+ if (workbench.compact) {
236
+ return (_jsx(Sheet, { side: "right", open: workbench.inspectorOpen, onOpenChange: workbench.setInspectorOpen, children: _jsxs(Sheet.Content, { size: "full", children: [_jsx(Sheet.Header, { title: title }), _jsx("div", { "data-slot": "workbench-inspector", className: slots.sheetBody(), children: children })] }) }));
237
+ }
238
+ return (_jsx("div", { "data-slot": "workbench-inspector", inert: workbench.focus || undefined, className: slots.inspector({ className }), ...props, children: children }));
239
+ }
240
+ WorkbenchInspector.displayName = 'Workbench.Inspector';
241
+ /**
242
+ * The button that opens the inspector's sheet on a narrow screen. It is
243
+ * only there when the inspector is a sheet, so put it where it belongs on
244
+ * either size: in the bar, beside the tools.
245
+ */
246
+ function WorkbenchInspectorTrigger({ className, children, onClick, ...props }) {
247
+ const workbench = useWorkbenchContext('Workbench.InspectorTrigger');
248
+ const slots = workbenchVariants();
249
+ if (!workbench.compact)
250
+ return null;
251
+ return (_jsx("button", { type: "button", "data-slot": "workbench-inspector-trigger", "aria-label": "Open inspector", "aria-expanded": workbench.inspectorOpen, className: slots.trigger({ className }), onClick: (event) => {
252
+ onClick?.(event);
253
+ if (!event.defaultPrevented)
254
+ workbench.setInspectorOpen(true);
255
+ }, ...props, children: children ?? (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [_jsx("path", { d: "M4 6h10M18 6h2M4 12h4M12 12h8M4 18h12M20 18h0" }), _jsx("circle", { cx: "16", cy: "6", r: "2" }), _jsx("circle", { cx: "10", cy: "12", r: "2" }), _jsx("circle", { cx: "18", cy: "18", r: "2" })] })) }));
256
+ }
257
+ WorkbenchInspectorTrigger.displayName = 'Workbench.InspectorTrigger';
258
+ /** The row under everything, for a `Timeline`. Its top edge drags to resize it. */
259
+ function WorkbenchTimeline({ resizable = true, minHeight = 140, className, children, ...props }) {
260
+ const workbench = useWorkbenchContext('Workbench.Timeline');
261
+ usePresence(workbench.hasTimeline);
262
+ const slots = workbenchVariants();
263
+ const [active, setActive] = useState(false);
264
+ const maxHeight = () => Math.max(minHeight, workbench.rootHeight() * 0.6);
265
+ const drag = (event) => {
266
+ if (event.button !== 0)
267
+ return;
268
+ event.preventDefault();
269
+ event.currentTarget.setPointerCapture(event.pointerId);
270
+ const startY = event.clientY;
271
+ const startHeight = workbench.timelineHeight;
272
+ setActive(true);
273
+ workbench.setResizing(true);
274
+ const target = event.currentTarget;
275
+ const move = (next) => workbench.setTimelineHeight(Math.round(Math.min(Math.max(startHeight + startY - next.clientY, minHeight), maxHeight())));
276
+ const end = () => {
277
+ setActive(false);
278
+ workbench.setResizing(false);
279
+ target.removeEventListener('pointermove', move);
280
+ target.removeEventListener('pointerup', end);
281
+ target.removeEventListener('pointercancel', end);
282
+ };
283
+ target.addEventListener('pointermove', move);
284
+ target.addEventListener('pointerup', end);
285
+ target.addEventListener('pointercancel', end);
286
+ };
287
+ const keys = (event) => {
288
+ const by = event.key === 'ArrowUp' ? 16 : event.key === 'ArrowDown' ? -16 : 0;
289
+ if (!by)
290
+ return;
291
+ event.preventDefault();
292
+ workbench.setTimelineHeight(Math.min(Math.max(workbench.timelineHeight + by, minHeight), maxHeight()));
293
+ };
294
+ return (_jsxs("div", { "data-slot": "workbench-timeline", inert: workbench.focus || undefined, className: slots.timeline({ className }), ...props, children: [resizable ? (_jsx("div", { role: "separator", tabIndex: 0, "aria-orientation": "horizontal", "aria-label": "Timeline height", "aria-valuenow": workbench.timelineHeight, "aria-valuemin": minHeight, "data-slot": "workbench-resize", "data-active": active ? '' : undefined, className: slots.resize(), onPointerDown: drag, onKeyDown: keys })) : null, children] }));
295
+ }
296
+ WorkbenchTimeline.displayName = 'Workbench.Timeline';
297
+ export const Workbench = Object.assign(WorkbenchRoot, {
298
+ Bar: WorkbenchBar,
299
+ Sidebar: WorkbenchSidebar,
300
+ Canvas: WorkbenchCanvas,
301
+ Artboard: WorkbenchArtboard,
302
+ Inspector: WorkbenchInspector,
303
+ InspectorTrigger: WorkbenchInspectorTrigger,
304
+ Timeline: WorkbenchTimeline,
305
+ });
306
+ export { WorkbenchBar, WorkbenchSidebar, WorkbenchCanvas, WorkbenchArtboard, WorkbenchInspector, WorkbenchInspectorTrigger, WorkbenchTimeline, workbenchVariants, };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * XYPad — two numbers set with one drag: a point moved across a field.
3
+ *
4
+ * ```tsx
5
+ * <XYPad label="Framing" defaultValue={{ x: 0.2, y: -0.1 }}>
6
+ * <XYPad.Value />
7
+ * </XYPad>
8
+ * ```
9
+ *
10
+ * ## Two values that belong together
11
+ *
12
+ * A position, a pan, a balance between two pairs of things: values that the
13
+ * hand thinks of as one place are set as one place. Two sliders would make
14
+ * the reader find a point by turning two knobs in turn.
15
+ *
16
+ * ## The field reads like a chart
17
+ *
18
+ * A grid of dots marks the steps, two hairlines mark zero on each axis, and
19
+ * two fainter lines run through the handle to the edges, so where it stands
20
+ * can be read against both axes at once. While it is held, four brackets
21
+ * close round it.
22
+ *
23
+ * ## Joystick and trail
24
+ *
25
+ * `snapBack` makes it a joystick: it springs back to the centre when let go,
26
+ * for a value that only means something while it is held, such as a pan
27
+ * speed. `trail` draws the last moves as a fading line, for a value whose
28
+ * path matters as much as where it ends.
29
+ */
30
+ import { type ComponentProps, type ReactNode } from 'react';
31
+ export type XYPadColor = 'primary' | 'info' | 'success' | 'warning' | 'destructive';
32
+ export interface XYPadPoint {
33
+ x: number;
34
+ y: number;
35
+ }
36
+ declare const xyPadVariants: import("tailwind-variants").TVReturnType<{
37
+ color: {
38
+ primary: {
39
+ root: string;
40
+ };
41
+ info: {
42
+ root: string;
43
+ };
44
+ success: {
45
+ root: string;
46
+ };
47
+ warning: {
48
+ root: string;
49
+ };
50
+ destructive: {
51
+ root: string;
52
+ };
53
+ };
54
+ }, {
55
+ root: string;
56
+ field: string[];
57
+ plane: string;
58
+ dots: string;
59
+ axis: string;
60
+ guide: string[];
61
+ trail: string;
62
+ handle: string[];
63
+ bracket: string[];
64
+ value: string;
65
+ axisName: string;
66
+ }, undefined, {
67
+ color: {
68
+ primary: {
69
+ root: string;
70
+ };
71
+ info: {
72
+ root: string;
73
+ };
74
+ success: {
75
+ root: string;
76
+ };
77
+ warning: {
78
+ root: string;
79
+ };
80
+ destructive: {
81
+ root: string;
82
+ };
83
+ };
84
+ }, {
85
+ root: string;
86
+ field: string[];
87
+ plane: string;
88
+ dots: string;
89
+ axis: string;
90
+ guide: string[];
91
+ trail: string;
92
+ handle: string[];
93
+ bracket: string[];
94
+ value: string;
95
+ axisName: string;
96
+ }, import("tailwind-variants").TVReturnType<{
97
+ color: {
98
+ primary: {
99
+ root: string;
100
+ };
101
+ info: {
102
+ root: string;
103
+ };
104
+ success: {
105
+ root: string;
106
+ };
107
+ warning: {
108
+ root: string;
109
+ };
110
+ destructive: {
111
+ root: string;
112
+ };
113
+ };
114
+ }, {
115
+ root: string;
116
+ field: string[];
117
+ plane: string;
118
+ dots: string;
119
+ axis: string;
120
+ guide: string[];
121
+ trail: string;
122
+ handle: string[];
123
+ bracket: string[];
124
+ value: string;
125
+ axisName: string;
126
+ }, undefined, unknown, unknown, undefined>>;
127
+ export interface XYPadProps extends Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange' | 'children'> {
128
+ /** Where the point is, when the caller holds it. */
129
+ value?: XYPadPoint;
130
+ /** Where it starts, when the caller does not hold it. Default the centre. */
131
+ defaultValue?: XYPadPoint;
132
+ /** Called with the new point while it moves. */
133
+ onValueChange?: (value: XYPadPoint) => void;
134
+ /** Called once a move ends: on release, or after a key press. */
135
+ onValueCommitted?: (value: XYPadPoint) => void;
136
+ /** The range of `x`, left to right. Default `[-1, 1]`. */
137
+ x?: [number, number];
138
+ /** The range of `y`, bottom to top. Default `[-1, 1]`. */
139
+ y?: [number, number];
140
+ /** The granularity of both values. Default `0.01`. */
141
+ step?: number;
142
+ /** How many cells the dot grid has across. Default 8. */
143
+ grid?: number;
144
+ /** Snap the point to the dots of the grid. */
145
+ snapToGrid?: boolean;
146
+ /** Spring back to the centre on release, like a joystick. */
147
+ snapBack?: boolean;
148
+ /** Draw the last moves as a fading line behind the point. */
149
+ trail?: boolean;
150
+ /** The colour of the point, its guides and its trail. Default `info`. */
151
+ color?: XYPadColor;
152
+ /** The shape of the field, as a CSS aspect ratio. Default `16 / 10`. */
153
+ aspect?: string;
154
+ /** What it sets. Read out with its values. */
155
+ label?: string;
156
+ /** How each value is written, here and to a screen reader. Default two decimals. */
157
+ format?: (value: number, axis: 'x' | 'y') => string;
158
+ /** Turns it off. */
159
+ disabled?: boolean;
160
+ /** Submits `<name>.x` and `<name>.y` with a form. */
161
+ name?: string;
162
+ /** An `XYPad.Value` readout, set under the field. */
163
+ children?: ReactNode;
164
+ }
165
+ /** A field with a point in it, moved by a drag, a press or the arrow keys. */
166
+ declare function XYPadRoot({ value: valueProp, defaultValue, onValueChange, onValueCommitted, x: xRange, y: yRange, step, grid, snapToGrid, snapBack, trail, color, aspect, label, format, disabled, name, className, style, children, ...props }: XYPadProps): import("react").JSX.Element;
167
+ declare namespace XYPadRoot {
168
+ var displayName: string;
169
+ }
170
+ export interface XYPadValueProps extends ComponentProps<'div'> {
171
+ }
172
+ /** The two values written out, under the field. */
173
+ declare function XYPadValue({ className, ...props }: XYPadValueProps): import("react").JSX.Element;
174
+ declare namespace XYPadValue {
175
+ var displayName: string;
176
+ }
177
+ export declare const XYPad: typeof XYPadRoot & {
178
+ Value: typeof XYPadValue;
179
+ };
180
+ export { XYPadValue, xyPadVariants };