@surflane/ui 0.0.0-stage → 0.1.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.
Files changed (89) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/LICENSE-MIT +18 -0
  3. package/README.md +55 -2
  4. package/dist/advanced-forms.d.ts +115 -0
  5. package/dist/advanced-forms.js +93 -0
  6. package/dist/app-shell.d.ts +23 -0
  7. package/dist/app-shell.js +29 -0
  8. package/dist/button.d.ts +11 -0
  9. package/dist/button.js +16 -0
  10. package/dist/chart.d.ts +48 -0
  11. package/dist/chart.js +124 -0
  12. package/dist/commands.d.ts +47 -0
  13. package/dist/commands.js +27 -0
  14. package/dist/content.d.ts +59 -0
  15. package/dist/content.js +66 -0
  16. package/dist/context-overlays.d.ts +95 -0
  17. package/dist/context-overlays.js +50 -0
  18. package/dist/data.d.ts +95 -0
  19. package/dist/data.js +40 -0
  20. package/dist/date-picker.d.ts +23 -0
  21. package/dist/date-picker.js +39 -0
  22. package/dist/dialog-focus.d.ts +5 -0
  23. package/dist/dialog-focus.js +35 -0
  24. package/dist/dialog.d.ts +59 -0
  25. package/dist/dialog.js +49 -0
  26. package/dist/feedback.d.ts +76 -0
  27. package/dist/feedback.js +35 -0
  28. package/dist/file-upload.d.ts +77 -0
  29. package/dist/file-upload.js +343 -0
  30. package/dist/filter-bar.d.ts +17 -0
  31. package/dist/filter-bar.js +23 -0
  32. package/dist/form-state.d.ts +5 -0
  33. package/dist/form-state.js +63 -0
  34. package/dist/forms.d.ts +91 -0
  35. package/dist/forms.js +109 -0
  36. package/dist/foundations.d.ts +51 -0
  37. package/dist/foundations.js +55 -0
  38. package/dist/icon.d.ts +37 -0
  39. package/dist/icon.js +28 -0
  40. package/dist/index.d.ts +37 -0
  41. package/dist/index.js +38 -0
  42. package/dist/input-extensions.d.ts +117 -0
  43. package/dist/input-extensions.js +64 -0
  44. package/dist/input.d.ts +7 -0
  45. package/dist/input.js +10 -0
  46. package/dist/markdown.d.ts +15 -0
  47. package/dist/markdown.js +121 -0
  48. package/dist/menu.d.ts +79 -0
  49. package/dist/menu.js +44 -0
  50. package/dist/messages.d.ts +63 -0
  51. package/dist/messages.js +38 -0
  52. package/dist/navigation-menu.d.ts +52 -0
  53. package/dist/navigation-menu.js +31 -0
  54. package/dist/navigation.d.ts +107 -0
  55. package/dist/navigation.js +108 -0
  56. package/dist/otp-field.d.ts +12 -0
  57. package/dist/otp-field.js +46 -0
  58. package/dist/pagination.d.ts +23 -0
  59. package/dist/pagination.js +44 -0
  60. package/dist/panels.d.ts +66 -0
  61. package/dist/panels.js +99 -0
  62. package/dist/parts.d.ts +9 -0
  63. package/dist/parts.js +22 -0
  64. package/dist/popover.d.ts +21 -0
  65. package/dist/popover.js +13 -0
  66. package/dist/scroll-area.d.ts +8 -0
  67. package/dist/scroll-area.js +39 -0
  68. package/dist/select.d.ts +33 -0
  69. package/dist/select.js +97 -0
  70. package/dist/sizing.d.ts +13 -0
  71. package/dist/sizing.js +23 -0
  72. package/dist/slot.d.ts +2 -0
  73. package/dist/slot.js +10 -0
  74. package/dist/status.d.ts +22 -0
  75. package/dist/status.js +17 -0
  76. package/dist/syntax-engine.d.ts +5 -0
  77. package/dist/syntax-engine.js +129 -0
  78. package/dist/syntax-highlighter.d.ts +17 -0
  79. package/dist/syntax-highlighter.js +64 -0
  80. package/dist/toast.d.ts +59 -0
  81. package/dist/toast.js +99 -0
  82. package/dist/tone-transition.d.ts +4 -0
  83. package/dist/tone-transition.js +81 -0
  84. package/dist/tooltip.d.ts +11 -0
  85. package/dist/tooltip.js +13 -0
  86. package/dist/tree.d.ts +28 -0
  87. package/dist/tree.js +311 -0
  88. package/package.json +109 -4
  89. package/src/styles.css +3708 -0
@@ -0,0 +1,343 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Field } from "./forms.js";
5
+ import { Button } from "./button.js";
6
+ import { Progress } from "./feedback.js";
7
+ import { StatusBadge } from "./status.js";
8
+ import { skin } from "./parts.js";
9
+ import { useResetValue, useRootRef } from "./form-state.js";
10
+ const labelsDefault = {
11
+ choose: "Choose files",
12
+ drop: "or drop files here",
13
+ upload: "Upload",
14
+ retry: "Retry",
15
+ cancel: "Cancel",
16
+ remove: "Remove",
17
+ status: {
18
+ ready: "Ready",
19
+ uploading: "Uploading",
20
+ success: "Uploaded",
21
+ error: "Failed",
22
+ canceled: "Canceled",
23
+ },
24
+ rejection: ({ file, reason }) => `${file.name}: ${reason === "type" ? "file type is not accepted" : reason === "size" ? "file is too large" : reason === "count" ? "file limit reached" : "file is already selected"}.`,
25
+ changed: (count) => `${count} file${count === 1 ? "" : "s"} selected.`,
26
+ complete: (file) => `${file.name}: upload complete.`,
27
+ canceled: (file) => `${file.name}: upload canceled.`,
28
+ failed: (file) => `${file.name}: upload failed.`,
29
+ reset: "Files reset.",
30
+ progress: (file) => `${file.name} upload progress`,
31
+ };
32
+ function signature(file) {
33
+ return JSON.stringify([file.name, file.size, file.type, file.lastModified]);
34
+ }
35
+ function accepts(file, accept) {
36
+ const rules = accept
37
+ .split(",")
38
+ .map((rule) => rule.trim().toLowerCase())
39
+ .filter(Boolean);
40
+ return (!rules.length ||
41
+ rules.some((rule) => rule.startsWith(".")
42
+ ? file.name.toLowerCase().endsWith(rule)
43
+ : rule.endsWith("/*")
44
+ ? file.type.toLowerCase().startsWith(rule.slice(0, -1))
45
+ : file.type.toLowerCase() === rule));
46
+ }
47
+ function sizeLabel(bytes) {
48
+ const unit = bytes >= 1024 * 1024 ? "MB" : bytes >= 1024 ? "KB" : "B";
49
+ return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(bytes / (unit === "MB" ? 1024 * 1024 : unit === "KB" ? 1024 : 1))} ${unit}`;
50
+ }
51
+ /** Native selection/validation and a cancellable transport adapter; no network request is built in. */
52
+ export const FileUpload = React.forwardRef(function FileUpload({ label, description, name, form, accept = "", capture, multiple = false, required = false, maxFiles = Infinity, maxFileSize = Infinity, files: controlled, defaultFiles = [], onFilesChange, onReject, transport, getUploadError, labels: customLabels, disabled = false, className, onDragEnter, onDragLeave, onDragOver, onDrop, ...props }, forwardedRef) {
53
+ const labels = {
54
+ ...labelsDefault,
55
+ ...customLabels,
56
+ status: { ...labelsDefault.status, ...customLabels?.status },
57
+ };
58
+ const [root, ref] = useRootRef(forwardedRef), input = React.useRef(null);
59
+ const uid = React.useId(), inputId = `${uid}-input`, errorId = `${uid}-selection-errors`;
60
+ const [metadata, setMetadata] = React.useState(new Map()), [rejections, setRejections] = React.useState([]), [announcement, announce] = React.useState(""), [dragging, setDragging] = React.useState(false);
61
+ const notifying = React.useRef(false), lastSynced = React.useRef(null), skipNotifyFor = React.useRef(null);
62
+ const primaryButtons = React.useRef(new WeakMap()), removeButtons = React.useRef(new WeakMap()), focusRemove = React.useRef(null), focusPicker = React.useRef(null);
63
+ const requests = React.useRef(new Map()), mounted = React.useRef(false), dragDepth = React.useRef(0);
64
+ const identities = React.useRef(new WeakMap()), nextId = React.useRef(0);
65
+ function syncInput(next) {
66
+ if (!input.current)
67
+ return;
68
+ const transfer = new DataTransfer();
69
+ next.forEach((file) => transfer.items.add(file));
70
+ input.current.files = transfer.files;
71
+ }
72
+ function abortAll() {
73
+ requests.current.forEach((request) => request.controller.abort());
74
+ requests.current.clear();
75
+ }
76
+ const [files, setFiles, revision] = useResetValue(controlled, defaultFiles, root, form, () => {
77
+ abortAll();
78
+ setMetadata(new Map());
79
+ setRejections([]);
80
+ dragDepth.current = 0;
81
+ setDragging(false);
82
+ skipNotifyFor.current = controlled ?? defaultFiles;
83
+ syncInput(controlled ?? defaultFiles);
84
+ announce(labels.reset);
85
+ });
86
+ const latestFiles = React.useRef(files);
87
+ latestFiles.current = files;
88
+ React.useLayoutEffect(() => {
89
+ syncInput(files);
90
+ if (lastSynced.current !== null &&
91
+ lastSynced.current !== files &&
92
+ skipNotifyFor.current !== files &&
93
+ input.current) {
94
+ notifying.current = true;
95
+ try {
96
+ input.current.dispatchEvent(new Event("change", { bubbles: true }));
97
+ }
98
+ finally {
99
+ notifying.current = false;
100
+ }
101
+ }
102
+ lastSynced.current = files;
103
+ skipNotifyFor.current = null;
104
+ }, [files, revision]);
105
+ React.useLayoutEffect(() => {
106
+ if (focusPicker.current && !files.includes(focusPicker.current)) {
107
+ input.current?.focus();
108
+ focusPicker.current = null;
109
+ }
110
+ if (focusRemove.current) {
111
+ removeButtons.current.get(focusRemove.current)?.focus();
112
+ focusRemove.current = null;
113
+ }
114
+ }, [files, metadata]);
115
+ React.useEffect(() => {
116
+ mounted.current = true;
117
+ return () => {
118
+ mounted.current = false;
119
+ abortAll();
120
+ };
121
+ }, []);
122
+ React.useEffect(() => {
123
+ const selected = new Set(files);
124
+ requests.current.forEach((request, file) => {
125
+ if (!selected.has(file)) {
126
+ requests.current.delete(file);
127
+ request.controller.abort();
128
+ }
129
+ });
130
+ setMetadata((previous) => {
131
+ if ([...previous.keys()].every((file) => selected.has(file)))
132
+ return previous;
133
+ return new Map([...previous].filter(([file]) => selected.has(file)));
134
+ });
135
+ }, [files]);
136
+ function update(file, next) {
137
+ setMetadata((previous) => new Map(previous).set(file, next));
138
+ }
139
+ function cancel(file) {
140
+ const request = requests.current.get(file);
141
+ if (!request)
142
+ return;
143
+ requests.current.delete(file);
144
+ request.controller.abort();
145
+ update(file, { status: "canceled", progress: null });
146
+ announce(labels.canceled(file));
147
+ }
148
+ React.useEffect(() => {
149
+ if (disabled) {
150
+ [...requests.current.keys()].forEach(cancel);
151
+ dragDepth.current = 0;
152
+ setDragging(false);
153
+ }
154
+ }, [disabled]);
155
+ function commit(next, reason) {
156
+ const details = {
157
+ reason,
158
+ isCanceled: false,
159
+ cancel() {
160
+ this.isCanceled = true;
161
+ },
162
+ };
163
+ let accepted = false;
164
+ try {
165
+ onFilesChange?.(next, details);
166
+ accepted = !details.isCanceled;
167
+ }
168
+ finally {
169
+ if (accepted && controlled === undefined) {
170
+ files.forEach((file) => {
171
+ if (!next.includes(file)) {
172
+ const request = requests.current.get(file);
173
+ requests.current.delete(file);
174
+ request?.controller.abort();
175
+ }
176
+ });
177
+ setFiles(next);
178
+ syncInput(next);
179
+ }
180
+ else
181
+ syncInput(files);
182
+ }
183
+ if (accepted)
184
+ announce(labels.changed(next.length));
185
+ return accepted;
186
+ }
187
+ function select(incoming, reason) {
188
+ if (disabled) {
189
+ syncInput(files);
190
+ return;
191
+ }
192
+ const next = multiple ? [...files] : [], rejected = [];
193
+ const limit = multiple ? Math.max(0, Math.floor(maxFiles)) : 1;
194
+ for (const file of incoming) {
195
+ let rejection;
196
+ if (!accepts(file, accept))
197
+ rejection = "type";
198
+ else if (file.size > maxFileSize)
199
+ rejection = "size";
200
+ else if (next.some((current) => signature(current) === signature(file)))
201
+ rejection = "duplicate";
202
+ else if (next.length >= limit)
203
+ rejection = "count";
204
+ if (rejection)
205
+ rejected.push({ file, reason: rejection });
206
+ else
207
+ next.push(file);
208
+ }
209
+ // A fully rejected single selection must not erase a previously accepted file.
210
+ const selected = next.length || multiple ? next : [...files];
211
+ setRejections(rejected);
212
+ if (selected.length !== files.length ||
213
+ selected.some((file, index) => file !== files[index]))
214
+ commit(selected, reason);
215
+ else
216
+ syncInput(files);
217
+ onReject?.(rejected);
218
+ }
219
+ async function upload(file) {
220
+ if (disabled ||
221
+ !transport ||
222
+ requests.current.has(file) ||
223
+ !latestFiles.current.includes(file))
224
+ return;
225
+ const request = { controller: new AbortController() };
226
+ requests.current.set(file, request);
227
+ const current = () => mounted.current &&
228
+ requests.current.get(file) === request &&
229
+ !request.controller.signal.aborted &&
230
+ latestFiles.current.includes(file);
231
+ update(file, { status: "uploading", progress: null });
232
+ try {
233
+ await transport(file, {
234
+ signal: request.controller.signal,
235
+ onProgress(percent) {
236
+ if (current())
237
+ update(file, {
238
+ status: "uploading",
239
+ progress: percent === null || !Number.isFinite(percent)
240
+ ? null
241
+ : Math.max(0, Math.min(100, percent)),
242
+ });
243
+ },
244
+ });
245
+ if (current()) {
246
+ if (primaryButtons.current.get(file) ===
247
+ input.current?.ownerDocument.activeElement)
248
+ focusRemove.current = file;
249
+ update(file, { status: "success", progress: 100 });
250
+ announce(labels.complete(file));
251
+ }
252
+ }
253
+ catch (error) {
254
+ if (current()) {
255
+ let message = "Upload failed. Try again.";
256
+ try {
257
+ message = getUploadError?.(error) || message;
258
+ }
259
+ catch { }
260
+ update(file, { status: "error", progress: null, error: message });
261
+ announce(labels.failed(file));
262
+ }
263
+ }
264
+ finally {
265
+ if (requests.current.get(file) === request)
266
+ requests.current.delete(file);
267
+ }
268
+ }
269
+ const hasFiles = (event) => Array.from(event.dataTransfer.types).includes("Files");
270
+ return (_jsxs(Field.Root, { ...props, ref: ref, name: name, disabled: disabled, className: skin("pui-field pui-file-upload", className), "data-dragging": dragging || undefined, children: [_jsx(Field.Label, { children: label }), description != null && description !== false && (_jsx(Field.Description, { children: description })), _jsxs("div", { className: "pui-file-dropzone", onDragEnter: (event) => {
271
+ onDragEnter?.(event);
272
+ if (!event.defaultPrevented && hasFiles(event) && !disabled) {
273
+ event.preventDefault();
274
+ dragDepth.current++;
275
+ setDragging(true);
276
+ }
277
+ }, onDragLeave: (event) => {
278
+ onDragLeave?.(event);
279
+ dragDepth.current = Math.max(0, dragDepth.current - 1);
280
+ if (!dragDepth.current)
281
+ setDragging(false);
282
+ }, onDragOver: (event) => {
283
+ onDragOver?.(event);
284
+ if (hasFiles(event)) {
285
+ event.preventDefault();
286
+ event.dataTransfer.dropEffect = disabled ? "none" : "copy";
287
+ }
288
+ }, onDrop: (event) => {
289
+ onDrop?.(event);
290
+ dragDepth.current = 0;
291
+ setDragging(false);
292
+ if (!event.defaultPrevented && hasFiles(event)) {
293
+ event.preventDefault();
294
+ select(Array.from(event.dataTransfer.files), "drop");
295
+ }
296
+ }, children: [_jsx(Field.Control, { ref: input, id: inputId, name: name, form: form, type: "file", accept: accept, capture: capture, multiple: multiple, required: required, disabled: disabled, className: "pui-file-native", "aria-describedby": errorId, onKeyDown: (event) => {
297
+ if (event.key === "Enter")
298
+ event.preventBaseUIHandler();
299
+ }, onChange: (event) => {
300
+ if (!notifying.current) {
301
+ event.preventBaseUIHandler();
302
+ select(Array.from(event.currentTarget.files ?? []), "select");
303
+ }
304
+ } }), _jsxs("label", { htmlFor: inputId, className: "pui-button pui-file-trigger", "data-variant": "outline", "data-disabled": disabled || undefined, children: [_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", "aria-hidden": "true", children: _jsx("path", { d: "M12 16V3m-5 5 5-5 5 5M4 16v5h16v-5" }) }), labels.choose] }), _jsx("p", { className: "pui-file-drop-hint", children: labels.drop })] }), _jsx("div", { id: errorId, role: "alert", className: "pui-file-errors", children: rejections.length > 0 && (_jsx("ul", { children: rejections.map((rejection, index) => (_jsx("li", { children: labels.rejection(rejection) }, index))) })) }), _jsx(Field.Error, {}), files.length > 0 && (_jsx("ul", { className: "pui-file-list", role: "list", children: files.map((file) => {
305
+ const state = metadata.get(file) ?? {
306
+ status: "ready",
307
+ progress: null,
308
+ };
309
+ if (!identities.current.has(file))
310
+ identities.current.set(file, `${uid}-${++nextId.current}`);
311
+ const itemId = identities.current.get(file);
312
+ return (_jsxs("li", { className: "pui-file-item", children: [_jsxs("div", { className: "pui-file-info", children: [_jsx("strong", { children: file.name }), _jsx("span", { children: sizeLabel(file.size) }), _jsx(StatusBadge, { tone: state.status === "success"
313
+ ? "success"
314
+ : state.status === "error"
315
+ ? "danger"
316
+ : state.status === "uploading"
317
+ ? "progress"
318
+ : "neutral", children: labels.status[state.status] })] }), state.status === "uploading" && (_jsx(Progress.Root, { value: state.progress, "aria-label": labels.progress(file), children: _jsx(Progress.Track, { children: _jsx(Progress.Indicator, {}) }) })), state.error && (_jsx("p", { id: `${itemId}-error`, className: "pui-file-upload-error", children: state.error })), _jsxs("div", { className: "pui-file-actions", children: [transport && state.status !== "success" && (_jsx(Button, { ref: (node) => {
319
+ if (node)
320
+ primaryButtons.current.set(file, node);
321
+ else
322
+ primaryButtons.current.delete(file);
323
+ }, type: "button", size: "sm", variant: "outline", disabled: disabled, "aria-describedby": state.error ? `${itemId}-error` : undefined, "aria-label": `${state.status === "uploading" ? labels.cancel : state.status === "ready" ? labels.upload : labels.retry} ${file.name}`, onClick: () => {
324
+ if (state.status === "uploading")
325
+ cancel(file);
326
+ else
327
+ void upload(file);
328
+ }, children: state.status === "uploading"
329
+ ? labels.cancel
330
+ : state.status === "ready"
331
+ ? labels.upload
332
+ : labels.retry })), _jsx(Button, { type: "button", size: "sm", variant: "ghost", ref: (node) => {
333
+ if (node)
334
+ removeButtons.current.set(file, node);
335
+ else
336
+ removeButtons.current.delete(file);
337
+ }, disabled: disabled, "aria-label": `${labels.remove} ${file.name}`, onClick: () => {
338
+ setRejections([]);
339
+ if (commit(files.filter((current) => current !== file), "remove"))
340
+ focusPicker.current = file;
341
+ }, children: labels.remove })] })] }, itemId));
342
+ }) })), _jsx("span", { className: "pui-visually-hidden", role: "status", children: announcement })] }));
343
+ });
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ export declare const FilterBar: {
3
+ Root: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, "ref"> & {
4
+ label: string;
5
+ } & React.RefAttributes<HTMLFormElement>>;
6
+ Controls: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
7
+ Field: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, "ref"> & {
8
+ width?: "search" | "filter";
9
+ } & React.RefAttributes<HTMLLabelElement>>;
10
+ Actions: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
11
+ Summary: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
12
+ };
13
+ export declare const FilterBarRoot: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, "ref"> & {
14
+ label: string;
15
+ } & React.RefAttributes<HTMLFormElement>>, FilterBarControls: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>, FilterBarField: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, "ref"> & {
16
+ width?: "search" | "filter";
17
+ } & React.RefAttributes<HTMLLabelElement>>, FilterBarActions: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>, FilterBarSummary: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ const cx = (base, extra) => [base, extra].filter(Boolean).join(" ");
5
+ /** Layout only: filtering, routing and async results remain controlled by the caller. */
6
+ const Root = React.forwardRef(function FilterBarRoot({ label, className, ...props }, ref) {
7
+ return (_jsx("form", { ...props, ref: ref, role: "search", "aria-label": label, className: cx("pui-filter-bar", className) }));
8
+ });
9
+ const Controls = React.forwardRef(function FilterBarControls({ className, ...props }, ref) {
10
+ return (_jsx("div", { ...props, ref: ref, className: cx("pui-filter-bar-controls", className) }));
11
+ });
12
+ const Field = React.forwardRef(function FilterBarField({ width = "filter", className, ...props }, ref) {
13
+ return (_jsx("label", { ...props, ref: ref, "data-width": width, className: cx("pui-filter-bar-field", className) }));
14
+ });
15
+ const Actions = React.forwardRef(function FilterBarActions({ className, ...props }, ref) {
16
+ return (_jsx("div", { ...props, ref: ref, className: cx("pui-filter-bar-actions", className) }));
17
+ });
18
+ const Summary = React.forwardRef(function FilterBarSummary({ className, ...props }, ref) {
19
+ return (_jsx("div", { role: "status", "aria-atomic": "true", ...props, ref: ref, className: cx("pui-filter-bar-summary", className) }));
20
+ });
21
+ export const FilterBar = { Root, Controls, Field, Actions, Summary };
22
+ // Flat names for React Server Components, which cannot dot into client exports.
23
+ export const { Root: FilterBarRoot, Controls: FilterBarControls, Field: FilterBarField, Actions: FilterBarActions, Summary: FilterBarSummary, } = FilterBar;
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ /** Share the actual DOM node, including React 19 callback-ref cleanup semantics. */
3
+ export declare function useRootRef<Element extends HTMLElement>(forwarded: React.Ref<Element> | undefined): readonly [React.RefObject<Element | null>, (node: Element | null) => () => void];
4
+ /** Reset stateful primitives with their native form, without firing change callbacks. */
5
+ export declare function useResetValue<Value>(controlled: Value | undefined, initial: Value, element: React.RefObject<HTMLElement | null>, formId?: string, afterReset?: () => void): readonly [Value, React.Dispatch<React.SetStateAction<Value>>, number];
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ /** Share the actual DOM node, including React 19 callback-ref cleanup semantics. */
4
+ export function useRootRef(forwarded) {
5
+ const element = React.useRef(null);
6
+ const attach = React.useCallback((node) => {
7
+ element.current = node;
8
+ const cleanup = typeof forwarded === "function" ? forwarded(node) : undefined;
9
+ if (forwarded && typeof forwarded !== "function")
10
+ forwarded.current = node;
11
+ return () => {
12
+ if (element.current === node)
13
+ element.current = null;
14
+ if (typeof cleanup === "function")
15
+ cleanup();
16
+ else if (typeof forwarded === "function")
17
+ forwarded(null);
18
+ else if (forwarded)
19
+ forwarded.current = null;
20
+ };
21
+ }, [forwarded]);
22
+ return [element, attach];
23
+ }
24
+ /** Reset stateful primitives with their native form, without firing change callbacks. */
25
+ export function useResetValue(controlled, initial, element, formId, afterReset) {
26
+ const [local, setLocal] = React.useState(initial);
27
+ const [revision, refresh] = React.useReducer((value) => value + 1, 0);
28
+ const latest = React.useRef({ controlled, initial, afterReset });
29
+ latest.current = { controlled, initial, afterReset };
30
+ React.useEffect(() => {
31
+ const node = element.current;
32
+ const owner = (formId ? node?.ownerDocument.getElementById(formId) : undefined) ??
33
+ node?.closest("form") ??
34
+ Array.from(node?.querySelectorAll("input") ?? []).find((input) => input.form)?.form;
35
+ if (!owner || owner.tagName !== "FORM")
36
+ return;
37
+ const pending = new Set();
38
+ const reset = (event) => {
39
+ // Native dispatch can run microtasks between listeners. Wait for all cancel handlers.
40
+ const timer = setTimeout(() => {
41
+ pending.delete(timer);
42
+ if (event.defaultPrevented)
43
+ return;
44
+ if (latest.current.controlled === undefined)
45
+ setLocal(latest.current.initial);
46
+ else
47
+ refresh(); // Reassert controlled hidden inputs after the browser's native reset.
48
+ latest.current.afterReset?.();
49
+ }, 0);
50
+ pending.add(timer);
51
+ };
52
+ owner.addEventListener("reset", reset);
53
+ return () => {
54
+ owner.removeEventListener("reset", reset);
55
+ pending.forEach(clearTimeout);
56
+ };
57
+ }, [element, formId]);
58
+ return [
59
+ controlled === undefined ? local : controlled,
60
+ setLocal,
61
+ revision,
62
+ ];
63
+ }
@@ -0,0 +1,91 @@
1
+ import * as React from "react";
2
+ import { type ComponentSize } from "./sizing.js";
3
+ import { Form as BaseForm } from "@base-ui/react/form";
4
+ import { Radio as BaseRadio } from "@base-ui/react/radio";
5
+ import { RadioGroup as BaseRadioGroup } from "@base-ui/react/radio-group";
6
+ declare const FieldRoot: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldRootProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
7
+ size?: ComponentSize;
8
+ } & React.RefAttributes<HTMLDivElement>>;
9
+ declare const FieldLabel: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldLabelProps, "ref"> & React.RefAttributes<HTMLElement>, "ref">, "size"> & {
10
+ size?: ComponentSize;
11
+ } & React.RefAttributes<HTMLElement>>;
12
+ declare const FieldControl: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldControlProps, "ref"> & React.RefAttributes<HTMLElement>, "ref">, "size"> & {
13
+ size?: ComponentSize;
14
+ } & React.RefAttributes<HTMLElement>>;
15
+ declare const FieldDescription: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>, "ref">, "size"> & {
16
+ size?: ComponentSize;
17
+ } & React.RefAttributes<HTMLParagraphElement>>;
18
+ declare const FieldError: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldErrorProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
19
+ size?: ComponentSize;
20
+ } & React.RefAttributes<HTMLDivElement>>;
21
+ declare const FieldItem: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldItemProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
22
+ size?: ComponentSize;
23
+ } & React.RefAttributes<HTMLDivElement>>;
24
+ export declare const Field: {
25
+ Root: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldRootProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
26
+ size?: ComponentSize;
27
+ } & React.RefAttributes<HTMLDivElement>>;
28
+ Label: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldLabelProps, "ref"> & React.RefAttributes<HTMLElement>, "ref">, "size"> & {
29
+ size?: ComponentSize;
30
+ } & React.RefAttributes<HTMLElement>>;
31
+ Control: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldControlProps, "ref"> & React.RefAttributes<HTMLElement>, "ref">, "size"> & {
32
+ size?: ComponentSize;
33
+ } & React.RefAttributes<HTMLElement>>;
34
+ Description: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldDescriptionProps, "ref"> & React.RefAttributes<HTMLParagraphElement>, "ref">, "size"> & {
35
+ size?: ComponentSize;
36
+ } & React.RefAttributes<HTMLParagraphElement>>;
37
+ Error: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldErrorProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
38
+ size?: ComponentSize;
39
+ } & React.RefAttributes<HTMLDivElement>>;
40
+ Item: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/field").FieldItemProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
41
+ size?: ComponentSize;
42
+ } & React.RefAttributes<HTMLDivElement>>;
43
+ Validity: React.FC<import("@base-ui/react/field").FieldValidityProps>;
44
+ };
45
+ export declare const Fieldset: {
46
+ Root: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/fieldset").FieldsetRootProps, "ref"> & React.RefAttributes<HTMLElement>, "ref">, "size"> & {
47
+ size?: ComponentSize;
48
+ } & React.RefAttributes<HTMLElement>>;
49
+ Legend: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/fieldset").FieldsetLegendProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
50
+ size?: ComponentSize;
51
+ } & React.RefAttributes<HTMLDivElement>>;
52
+ };
53
+ export declare function Form<Values extends Record<string, unknown> = Record<string, unknown>>({ className, ...props }: BaseForm.Props<Values>): React.JSX.Element;
54
+ export declare const Textarea: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>, "ref"> & {
55
+ size?: ComponentSize;
56
+ } & React.RefAttributes<HTMLTextAreaElement>>;
57
+ declare const CheckboxRoot: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/checkbox").CheckboxRootProps, "ref"> & React.RefAttributes<HTMLElement>, "ref"> & React.RefAttributes<HTMLElement>>;
58
+ declare const CheckboxIndicator: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/checkbox").CheckboxIndicatorProps, "ref"> & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
59
+ export declare const Checkbox: {
60
+ Root: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/checkbox").CheckboxRootProps, "ref"> & React.RefAttributes<HTMLElement>, "ref"> & React.RefAttributes<HTMLElement>>;
61
+ Indicator: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/checkbox").CheckboxIndicatorProps, "ref"> & React.RefAttributes<HTMLSpanElement>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
62
+ };
63
+ export declare const CheckboxGroup: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/checkbox-group").CheckboxGroupProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
64
+ export declare function RadioGroup<Value = string>({ value, defaultValue, onValueChange, className, form, ref, ...props }: BaseRadioGroup.Props<Value>): React.JSX.Element;
65
+ declare const RadioRoot: React.ForwardRefExoticComponent<Omit<BaseRadio.Root.Props<unknown>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
66
+ export declare const Radio: {
67
+ Root: React.ForwardRefExoticComponent<Omit<BaseRadio.Root.Props<unknown>, "ref"> & React.RefAttributes<HTMLSpanElement>>;
68
+ Indicator: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/radio").RadioIndicatorProps, "ref"> & React.RefAttributes<HTMLSpanElement>, "ref">, "size"> & {
69
+ size?: ComponentSize;
70
+ } & React.RefAttributes<HTMLSpanElement>>;
71
+ };
72
+ declare const SwitchRoot: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/switch").SwitchRootProps, "ref"> & React.RefAttributes<HTMLElement>, "ref"> & React.RefAttributes<HTMLElement>>;
73
+ export declare const Switch: {
74
+ Root: React.ForwardRefExoticComponent<Omit<Omit<import("@base-ui/react/switch").SwitchRootProps, "ref"> & React.RefAttributes<HTMLElement>, "ref"> & React.RefAttributes<HTMLElement>>;
75
+ Thumb: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/switch").SwitchThumbProps, "ref"> & React.RefAttributes<HTMLSpanElement>, "ref">, "size"> & {
76
+ size?: ComponentSize;
77
+ } & React.RefAttributes<HTMLSpanElement>>;
78
+ };
79
+ export { CheckboxIndicator, CheckboxRoot, FieldControl, FieldDescription, FieldError, FieldItem, FieldLabel, FieldRoot, RadioRoot, SwitchRoot, };
80
+ export declare const FieldValidity: React.FC<import("@base-ui/react/field").FieldValidityProps>;
81
+ export declare const FieldsetLegend: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/fieldset").FieldsetLegendProps, "ref"> & React.RefAttributes<HTMLDivElement>, "ref">, "size"> & {
82
+ size?: ComponentSize;
83
+ } & React.RefAttributes<HTMLDivElement>>, FieldsetRoot: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/fieldset").FieldsetRootProps, "ref"> & React.RefAttributes<HTMLElement>, "ref">, "size"> & {
84
+ size?: ComponentSize;
85
+ } & React.RefAttributes<HTMLElement>>;
86
+ export declare const RadioIndicator: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/radio").RadioIndicatorProps, "ref"> & React.RefAttributes<HTMLSpanElement>, "ref">, "size"> & {
87
+ size?: ComponentSize;
88
+ } & React.RefAttributes<HTMLSpanElement>>;
89
+ export declare const SwitchThumb: React.ForwardRefExoticComponent<Omit<Omit<Omit<import("@base-ui/react/switch").SwitchThumbProps, "ref"> & React.RefAttributes<HTMLSpanElement>, "ref">, "size"> & {
90
+ size?: ComponentSize;
91
+ } & React.RefAttributes<HTMLSpanElement>>;