strata-react 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.
package/dist/index.js ADDED
@@ -0,0 +1,1085 @@
1
+ import { a as fetchJson, i as describeHttpError, n as StrataQueryProvider, o as toLoad, r as createQueryClient, s as useLoad, t as HttpError } from "./query-BXH-Gfj7.js";
2
+ import { clsx } from "clsx";
3
+ import { extendTailwindMerge } from "tailwind-merge";
4
+ import * as React from "react";
5
+ import { Slot } from "@radix-ui/react-slot";
6
+ import { cva } from "class-variance-authority";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { ArrowDown, ArrowUp, ArrowUpDown, Check, CircleAlert, LoaderCircle, RotateCcw } from "lucide-react";
9
+ import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
10
+ import { useVirtualizer } from "@tanstack/react-virtual";
11
+ import { Link, NavLink, useSearchParams } from "react-router";
12
+ // src/lib/utils.ts
13
+ var typeSteps = [
14
+ "caption",
15
+ "label",
16
+ "body-sm",
17
+ "body",
18
+ "heading",
19
+ "title",
20
+ "figure",
21
+ "display"
22
+ ];
23
+ var twMerge = extendTailwindMerge({ extend: { classGroups: { "font-size": [{ type: [...typeSteps] }] } } });
24
+ function cn(...inputs) {
25
+ return twMerge(clsx(inputs));
26
+ }
27
+ // src/lib/format.ts
28
+ var NBSP = "\xA0";
29
+ function createFormat(locale) {
30
+ const integer = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
31
+ const decimal = new Intl.NumberFormat(locale, { maximumFractionDigits: 1 });
32
+ const percent = new Intl.NumberFormat(locale, {
33
+ style: "percent",
34
+ maximumFractionDigits: 0
35
+ });
36
+ const clock = new Intl.DateTimeFormat(locale, {
37
+ hour: "2-digit",
38
+ minute: "2-digit"
39
+ });
40
+ const day = new Intl.DateTimeFormat(locale, {
41
+ weekday: "long",
42
+ day: "numeric",
43
+ month: "long"
44
+ });
45
+ const shortDay = new Intl.DateTimeFormat(locale, {
46
+ weekday: "short",
47
+ hour: "2-digit",
48
+ minute: "2-digit"
49
+ });
50
+ const withUnit = (value, unit, fmt = integer) => `${fmt.format(value)} ${unit}`;
51
+ return {
52
+ locale,
53
+ integer,
54
+ decimal,
55
+ percent,
56
+ clock,
57
+ day,
58
+ shortDay,
59
+ withUnit,
60
+ celsius: (value) => withUnit(value, "°C"),
61
+ /** "1 piece", "6 pieces": the count and its noun, so copy never reads "1 pieces". */
62
+ count: (value, one, many = `${one}s`) => `${integer.format(value)} ${new Intl.PluralRules(locale).select(value) === "one" ? one : many}`
63
+ };
64
+ }
65
+ var format = createFormat("en-GB");
66
+ // src/components/button.tsx
67
+ var buttonVariants = cva("inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-control type-label transition-state press disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
68
+ variants: {
69
+ variant: {
70
+ default: "bg-primary text-primary-foreground hover:bg-primary-10",
71
+ outline: "border border-input bg-background text-foreground hover:bg-muted",
72
+ secondary: "bg-muted text-foreground hover:bg-neutral-4",
73
+ ghost: "text-foreground hover:bg-muted",
74
+ destructive: "bg-destructive text-destructive-foreground hover:bg-danger",
75
+ link: "h-auto px-0 text-primary-11 underline underline-offset-4 hover:text-primary-12"
76
+ },
77
+ size: {
78
+ default: "h-(--control-h) px-4",
79
+ sm: "h-(--control-h-sm) px-3",
80
+ icon: "size-(--control-h)"
81
+ }
82
+ },
83
+ defaultVariants: {
84
+ variant: "outline",
85
+ size: "default"
86
+ }
87
+ });
88
+ function Button({ className, variant, size, asChild = false, type = "button", ...props }) {
89
+ return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
90
+ type: asChild ? void 0 : type,
91
+ className: cn(buttonVariants({
92
+ variant,
93
+ size
94
+ }), className),
95
+ ...props
96
+ });
97
+ }
98
+ function IconButton({ icon: Icon, label, variant = "ghost", ...props }) {
99
+ return /* @__PURE__ */ jsx(Button, {
100
+ size: "icon",
101
+ variant,
102
+ "aria-label": label,
103
+ title: label,
104
+ ...props,
105
+ children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": true })
106
+ });
107
+ }
108
+ // src/components/async-button.tsx
109
+ function AsyncButton({ phase, labels, disabled, ...props }) {
110
+ const errorLabel = labels.error ?? "Try again";
111
+ const current = {
112
+ idle: labels.idle,
113
+ pending: labels.pending,
114
+ done: labels.done,
115
+ error: errorLabel
116
+ }[phase];
117
+ const Icon = {
118
+ idle: null,
119
+ pending: LoaderCircle,
120
+ done: Check,
121
+ error: CircleAlert
122
+ }[phase];
123
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
124
+ ...props,
125
+ disabled: disabled || phase === "pending",
126
+ "aria-disabled": phase === "pending" || void 0,
127
+ children: [Icon && /* @__PURE__ */ jsx(Icon, {
128
+ "aria-hidden": true,
129
+ className: phase === "pending" ? "motion-safe:animate-spin" : void 0
130
+ }), /* @__PURE__ */ jsx("span", {
131
+ className: "grid",
132
+ children: [
133
+ labels.idle,
134
+ labels.pending,
135
+ labels.done,
136
+ errorLabel
137
+ ].map((l) => /* @__PURE__ */ jsx("span", {
138
+ "aria-hidden": l !== current,
139
+ className: l === current ? "[grid-area:1/1]" : "invisible [grid-area:1/1]",
140
+ children: l
141
+ }, l))
142
+ })]
143
+ }), /* @__PURE__ */ jsx("span", {
144
+ role: "status",
145
+ className: "sr-only",
146
+ children: phase === "done" ? labels.done : phase === "error" ? errorLabel : ""
147
+ })] });
148
+ }
149
+ /** Runs an async action and reports its phase. `done` returns to `idle` after `settleMs`. */
150
+ function useAsyncAction(action, settleMs = 2e3) {
151
+ const [phase, setPhase] = React.useState("idle");
152
+ const timer = React.useRef(void 0);
153
+ React.useEffect(() => () => window.clearTimeout(timer.current), []);
154
+ return [phase, React.useCallback(async (...args) => {
155
+ window.clearTimeout(timer.current);
156
+ setPhase("pending");
157
+ try {
158
+ await action(...args);
159
+ setPhase("done");
160
+ timer.current = window.setTimeout(() => setPhase("idle"), settleMs);
161
+ } catch {
162
+ setPhase("error");
163
+ }
164
+ }, [action, settleMs])];
165
+ }
166
+ // src/components/field.tsx
167
+ var FieldContext = React.createContext(null);
168
+ function useField(id) {
169
+ const field = React.useContext(FieldContext);
170
+ return {
171
+ id: field?.id ?? id,
172
+ "aria-describedby": field?.describedBy,
173
+ "aria-invalid": field?.invalid || void 0
174
+ };
175
+ }
176
+ function Field({ label, id: idProp, hint, error, children, className }) {
177
+ const generated = React.useId();
178
+ const id = idProp ?? generated;
179
+ const hintId = hint ? `${id}-hint` : void 0;
180
+ const errorId = error ? `${id}-error` : void 0;
181
+ const describedBy = [errorId, hintId].filter(Boolean).join(" ") || void 0;
182
+ return /* @__PURE__ */ jsx(FieldContext.Provider, {
183
+ value: {
184
+ id,
185
+ describedBy,
186
+ invalid: Boolean(error)
187
+ },
188
+ children: /* @__PURE__ */ jsxs("div", {
189
+ className: cn("grid min-w-0 content-start gap-1.5", className),
190
+ children: [
191
+ /* @__PURE__ */ jsx("label", {
192
+ htmlFor: id,
193
+ className: "type-label text-foreground",
194
+ children: label
195
+ }),
196
+ children,
197
+ error && /* @__PURE__ */ jsxs("p", {
198
+ id: errorId,
199
+ className: "flex items-start gap-1.5 type-body-sm text-danger",
200
+ children: [/* @__PURE__ */ jsx(CircleAlert, {
201
+ "aria-hidden": true,
202
+ className: "mt-0.5 size-4 shrink-0"
203
+ }), error]
204
+ }),
205
+ hint && /* @__PURE__ */ jsx("p", {
206
+ id: hintId,
207
+ className: "type-body-sm text-muted-foreground",
208
+ children: hint
209
+ })
210
+ ]
211
+ })
212
+ });
213
+ }
214
+ var control = "w-full min-w-0 rounded-control border border-input bg-background px-3 type-body text-foreground transition-state placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-70 aria-invalid:border-danger";
215
+ function Input({ className, id, ...props }) {
216
+ return /* @__PURE__ */ jsx("input", {
217
+ ...useField(id),
218
+ className: cn(control, "h-(--control-h)", className),
219
+ ...props
220
+ });
221
+ }
222
+ function Textarea({ className, id, ...props }) {
223
+ return /* @__PURE__ */ jsx("textarea", {
224
+ ...useField(id),
225
+ className: cn(control, "min-h-24 py-2", className),
226
+ ...props
227
+ });
228
+ }
229
+ function NativeSelect({ className, id, ...props }) {
230
+ return /* @__PURE__ */ jsx("select", {
231
+ ...useField(id),
232
+ className: cn(control, "h-(--control-h) pr-8", className),
233
+ ...props
234
+ });
235
+ }
236
+ function ChoiceGroup({ legend, name, choices, value, onChange, error }) {
237
+ const errorId = React.useId();
238
+ return /* @__PURE__ */ jsxs("fieldset", {
239
+ className: "grid gap-2",
240
+ "aria-describedby": error ? errorId : void 0,
241
+ children: [
242
+ /* @__PURE__ */ jsx("legend", {
243
+ className: "mb-1.5 type-label text-foreground",
244
+ children: legend
245
+ }),
246
+ choices.map((c) => /* @__PURE__ */ jsxs("label", {
247
+ className: "flex min-h-(--control-h-sm) cursor-pointer items-start gap-3 py-1",
248
+ children: [/* @__PURE__ */ jsx("input", {
249
+ type: "radio",
250
+ name,
251
+ value: c.value,
252
+ checked: value === c.value,
253
+ onChange: () => onChange(c.value),
254
+ className: "mt-1 size-4 shrink-0 accent-primary"
255
+ }), /* @__PURE__ */ jsxs("span", {
256
+ className: "grid",
257
+ children: [/* @__PURE__ */ jsx("span", {
258
+ className: "type-body",
259
+ children: c.label
260
+ }), c.detail && /* @__PURE__ */ jsx("span", {
261
+ className: "type-body-sm text-muted-foreground",
262
+ children: c.detail
263
+ })]
264
+ })]
265
+ }, c.value)),
266
+ error && /* @__PURE__ */ jsxs("p", {
267
+ id: errorId,
268
+ className: "flex items-start gap-1.5 type-body-sm text-danger",
269
+ children: [/* @__PURE__ */ jsx(CircleAlert, {
270
+ "aria-hidden": true,
271
+ className: "mt-0.5 size-4 shrink-0"
272
+ }), error]
273
+ })
274
+ ]
275
+ });
276
+ }
277
+ // src/components/status.tsx
278
+ var tones = {
279
+ neutral: "bg-muted text-muted-foreground",
280
+ info: "bg-info-subtle text-info",
281
+ success: "bg-success-subtle text-success",
282
+ warning: "bg-warning-subtle text-warning",
283
+ danger: "bg-danger-subtle text-danger"
284
+ };
285
+ function Status({ tone, icon: Icon, children, className }) {
286
+ return /* @__PURE__ */ jsxs("span", {
287
+ className: cn("inline-flex items-center gap-1.5 whitespace-nowrap rounded-pill py-0.5 pr-2.5 pl-2 type-caption font-medium", tones[tone], className),
288
+ children: [Icon && /* @__PURE__ */ jsx(Icon, {
289
+ "aria-hidden": true,
290
+ className: "size-3.5 shrink-0"
291
+ }), children]
292
+ });
293
+ }
294
+ // src/components/metric.tsx
295
+ function MetricStrip({ label, children, className }) {
296
+ return /* @__PURE__ */ jsx("dl", {
297
+ "aria-label": label,
298
+ className: cn("grid grid-cols-1 border-y border-border sm:auto-cols-fr sm:grid-flow-col sm:divide-x sm:divide-border", className),
299
+ children
300
+ });
301
+ }
302
+ function Metric({ label, value, unit, note }) {
303
+ return /* @__PURE__ */ jsxs("div", {
304
+ className: "grid content-start gap-0.5 border-border py-3 not-first:border-t sm:px-5 sm:not-first:border-t-0 sm:first:pl-0",
305
+ children: [
306
+ /* @__PURE__ */ jsx("dt", {
307
+ className: "type-label text-muted-foreground",
308
+ children: label
309
+ }),
310
+ /* @__PURE__ */ jsxs("dd", {
311
+ className: "type-figure text-foreground",
312
+ children: [value, unit && /* @__PURE__ */ jsx("span", {
313
+ className: "type-body text-muted-foreground",
314
+ children: "\xA0" + unit
315
+ })]
316
+ }),
317
+ note && /* @__PURE__ */ jsx("dd", {
318
+ className: "type-body-sm text-muted-foreground",
319
+ children: note
320
+ })
321
+ ]
322
+ });
323
+ }
324
+ // src/components/data-view.tsx
325
+ function DataView({ state, isEmpty, skeleton, empty, children, className }) {
326
+ return /* @__PURE__ */ jsxs("div", {
327
+ className: cn("min-w-0", className),
328
+ "aria-busy": state.status === "loading" || void 0,
329
+ children: [
330
+ state.status === "loading" && skeleton,
331
+ state.status === "error" && /* @__PURE__ */ jsxs("div", {
332
+ role: "alert",
333
+ className: "flex flex-wrap items-start gap-3 rounded-surface bg-danger-subtle p-4 text-danger",
334
+ children: [
335
+ /* @__PURE__ */ jsx(CircleAlert, {
336
+ "aria-hidden": true,
337
+ className: "mt-0.5 size-5 shrink-0"
338
+ }),
339
+ /* @__PURE__ */ jsx("p", {
340
+ className: "min-w-0 flex-1 type-body",
341
+ children: state.message
342
+ }),
343
+ /* @__PURE__ */ jsxs(Button, {
344
+ size: "sm",
345
+ onClick: state.retry,
346
+ children: [/* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": true }), "Try again"]
347
+ })
348
+ ]
349
+ }),
350
+ state.status === "ready" && (isEmpty(state.data) ? empty : children(state.data))
351
+ ]
352
+ });
353
+ }
354
+ function EmptyState({ title, children, action }) {
355
+ return /* @__PURE__ */ jsxs("div", {
356
+ className: "grid justify-items-start gap-2 border-y border-border py-6",
357
+ children: [
358
+ /* @__PURE__ */ jsx("p", {
359
+ className: "type-heading",
360
+ children: title
361
+ }),
362
+ children && /* @__PURE__ */ jsx("p", {
363
+ className: "max-w-prose type-body text-muted-foreground",
364
+ children
365
+ }),
366
+ action && /* @__PURE__ */ jsx("div", {
367
+ className: "mt-2",
368
+ children: action
369
+ })
370
+ ]
371
+ });
372
+ }
373
+ function Skeleton({ className }) {
374
+ return /* @__PURE__ */ jsx("div", {
375
+ "aria-hidden": true,
376
+ className: cn("rounded-control bg-muted motion-safe:animate-pulse", className)
377
+ });
378
+ }
379
+ // src/components/data-table.tsx
380
+ function useLocalSort(initial) {
381
+ const [sort, setSort] = React.useState(initial);
382
+ return [sort, setSort];
383
+ }
384
+ function parseSort(value) {
385
+ if (!value) return void 0;
386
+ const [key, dir] = value.split(".");
387
+ if (!key) return void 0;
388
+ return {
389
+ key,
390
+ dir: dir === "desc" ? "descending" : "ascending"
391
+ };
392
+ }
393
+ function serialiseSort(sort) {
394
+ return `${sort.key}.${sort.dir === "descending" ? "desc" : "asc"}`;
395
+ }
396
+ function useUrlSort(param, initial) {
397
+ const [params, setParams] = useSearchParams();
398
+ return [parseSort(params.get(param)) ?? initial, React.useCallback((next) => setParams((p) => {
399
+ const out = new URLSearchParams(p);
400
+ out.set(param, serialiseSort(next));
401
+ return out;
402
+ }, { replace: true }), [param, setParams])];
403
+ }
404
+ function DataTable(props) {
405
+ return props.sortParam ? /* @__PURE__ */ jsx(UrlSortedTable, {
406
+ ...props,
407
+ sortParam: props.sortParam
408
+ }) : /* @__PURE__ */ jsx(LocalSortedTable, { ...props });
409
+ }
410
+ function LocalSortedTable(props) {
411
+ return /* @__PURE__ */ jsx(Table, {
412
+ ...props,
413
+ sortState: useLocalSort(props.initialSort)
414
+ });
415
+ }
416
+ function UrlSortedTable(props) {
417
+ return /* @__PURE__ */ jsx(Table, {
418
+ ...props,
419
+ sortState: useUrlSort(props.sortParam, props.initialSort)
420
+ });
421
+ }
422
+ function Table({ caption, columns, rows, rowKey, sortState: [sort, setSort], virtualizeFrom = 100, maxHeight = 480, className }) {
423
+ const defs = React.useMemo(() => columns.map((c) => ({
424
+ id: c.key,
425
+ accessorFn: (row) => row,
426
+ header: c.header,
427
+ cell: (info) => c.cell(info.row.original),
428
+ enableSorting: Boolean(c.sort),
429
+ sortingFn: c.sort ? (a, b) => c.sort(a.original, b.original) : void 0,
430
+ meta: {
431
+ numeric: c.numeric,
432
+ truncate: c.truncate,
433
+ className: c.className
434
+ }
435
+ })), [columns]);
436
+ const sorting = sort && columns.some((c) => c.key === sort.key && c.sort) ? [{
437
+ id: sort.key,
438
+ desc: sort.dir === "descending"
439
+ }] : [];
440
+ const table = useReactTable({
441
+ data: rows,
442
+ columns: defs,
443
+ state: { sorting },
444
+ getRowId: (row) => rowKey(row),
445
+ getCoreRowModel: getCoreRowModel(),
446
+ getSortedRowModel: getSortedRowModel()
447
+ });
448
+ const toggle = (key) => setSort({
449
+ key,
450
+ dir: sort?.key === key && sort.dir === "ascending" ? "descending" : "ascending"
451
+ });
452
+ const modelRows = table.getRowModel().rows;
453
+ const virtual = modelRows.length >= virtualizeFrom;
454
+ const scroller = React.useRef(null);
455
+ const virtualizer = useVirtualizer({
456
+ count: virtual ? modelRows.length : 0,
457
+ getScrollElement: () => scroller.current,
458
+ estimateSize: () => 41,
459
+ overscan: 8,
460
+ initialRect: {
461
+ width: 0,
462
+ height: maxHeight
463
+ }
464
+ });
465
+ const items = virtual ? virtualizer.getVirtualItems() : [];
466
+ const padTop = items.length ? items[0].start : 0;
467
+ const padBottom = items.length ? virtualizer.getTotalSize() - items[items.length - 1].end : 0;
468
+ const visible = virtual ? items.map((i) => modelRows[i.index]) : modelRows;
469
+ return /* @__PURE__ */ jsx("div", {
470
+ ref: scroller,
471
+ role: "region",
472
+ "aria-label": caption,
473
+ tabIndex: 0,
474
+ className: cn("min-w-0 overflow-x-auto", virtual && "overflow-y-auto", className),
475
+ style: virtual ? { maxHeight } : void 0,
476
+ children: /* @__PURE__ */ jsxs("table", {
477
+ className: "w-full border-collapse type-body-sm",
478
+ "aria-rowcount": virtual ? modelRows.length + 1 : void 0,
479
+ children: [
480
+ /* @__PURE__ */ jsx("caption", {
481
+ className: "sr-only",
482
+ children: caption
483
+ }),
484
+ /* @__PURE__ */ jsx("thead", {
485
+ className: cn(virtual && "sticky top-0 z-10 bg-background"),
486
+ children: table.getHeaderGroups().map((group) => /* @__PURE__ */ jsx("tr", { children: group.headers.map((header) => {
487
+ const meta = header.column.columnDef.meta;
488
+ const active = sort?.key === header.id && header.column.getCanSort() ? sort.dir : void 0;
489
+ const Icon = active === "ascending" ? ArrowUp : active === "descending" ? ArrowDown : ArrowUpDown;
490
+ const label = flexRender(header.column.columnDef.header, header.getContext());
491
+ return /* @__PURE__ */ jsx("th", {
492
+ scope: "col",
493
+ "aria-sort": active,
494
+ className: cn("py-2 pr-4 text-left align-bottom type-caption text-muted-foreground last:pr-0", meta?.numeric && "text-right"),
495
+ children: header.column.getCanSort() ? /* @__PURE__ */ jsxs("button", {
496
+ type: "button",
497
+ onClick: () => toggle(header.id),
498
+ className: cn("-mx-1 inline-flex cursor-pointer items-center gap-1 rounded-control px-1 transition-state hover:text-foreground", meta?.numeric && "flex-row-reverse", active && "text-foreground"),
499
+ children: [label, /* @__PURE__ */ jsx(Icon, {
500
+ "aria-hidden": true,
501
+ className: cn("size-3.5", !active && "opacity-50")
502
+ })]
503
+ }) : label
504
+ }, header.id);
505
+ }) }, group.id))
506
+ }),
507
+ /* @__PURE__ */ jsxs("tbody", { children: [
508
+ padTop > 0 && /* @__PURE__ */ jsx("tr", {
509
+ "aria-hidden": true,
510
+ style: { height: padTop }
511
+ }),
512
+ visible.map((row, i) => /* @__PURE__ */ jsx("tr", {
513
+ className: "border-t border-border",
514
+ "aria-rowindex": virtual ? (items[i]?.index ?? 0) + 2 : void 0,
515
+ children: row.getVisibleCells().map((cell) => {
516
+ const meta = cell.column.columnDef.meta;
517
+ return /* @__PURE__ */ jsx("td", {
518
+ className: cn("py-2.5 pr-4 align-middle last:pr-0", meta?.numeric && "text-right tabular-nums", meta?.truncate && "max-w-48 truncate", meta?.className),
519
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
520
+ }, cell.id);
521
+ })
522
+ }, row.id)),
523
+ padBottom > 0 && /* @__PURE__ */ jsx("tr", {
524
+ "aria-hidden": true,
525
+ style: { height: padBottom }
526
+ })
527
+ ] })
528
+ ]
529
+ })
530
+ });
531
+ }
532
+ function TableSkeleton({ rows = 4, columns = 5 }) {
533
+ return /* @__PURE__ */ jsxs("div", {
534
+ "aria-hidden": true,
535
+ className: "grid gap-0",
536
+ children: [/* @__PURE__ */ jsx("div", {
537
+ className: "flex gap-4 py-2",
538
+ children: Array.from({ length: columns }, (_, i) => /* @__PURE__ */ jsx("div", { className: "h-3 flex-1 rounded-control bg-muted" }, i))
539
+ }), Array.from({ length: rows }, (_, r) => /* @__PURE__ */ jsx("div", {
540
+ className: "flex gap-4 border-t border-border py-3.5",
541
+ children: Array.from({ length: columns }, (_, i) => /* @__PURE__ */ jsx("div", { className: "h-4 flex-1 rounded-control bg-muted motion-safe:animate-pulse" }, i))
542
+ }, r))]
543
+ });
544
+ }
545
+ // src/components/virtual-list.tsx
546
+ function VirtualList({ label, items, itemKey, children, estimateSize = 48, height = 480, virtualizeFrom = 100, className }) {
547
+ const scroller = React.useRef(null);
548
+ const virtual = items.length >= virtualizeFrom;
549
+ const virtualizer = useVirtualizer({
550
+ count: virtual ? items.length : 0,
551
+ getScrollElement: () => scroller.current,
552
+ estimateSize: () => estimateSize,
553
+ overscan: 6,
554
+ initialRect: {
555
+ width: 0,
556
+ height
557
+ }
558
+ });
559
+ if (!virtual) return /* @__PURE__ */ jsx("ul", {
560
+ "aria-label": label,
561
+ className: cn("grid min-w-0", className),
562
+ children: items.map((item, i) => /* @__PURE__ */ jsx("li", {
563
+ className: "border-t border-border first:border-t-0",
564
+ children: children(item, i)
565
+ }, itemKey(item)))
566
+ });
567
+ return /* @__PURE__ */ jsx("div", {
568
+ ref: scroller,
569
+ role: "region",
570
+ "aria-label": label,
571
+ tabIndex: 0,
572
+ className: cn("min-w-0 overflow-y-auto", className),
573
+ style: { height },
574
+ children: /* @__PURE__ */ jsx("ul", {
575
+ "aria-label": label,
576
+ className: "relative w-full",
577
+ style: { height: virtualizer.getTotalSize() },
578
+ children: virtualizer.getVirtualItems().map((v) => {
579
+ const item = items[v.index];
580
+ return /* @__PURE__ */ jsx("li", {
581
+ ref: virtualizer.measureElement,
582
+ "data-index": v.index,
583
+ "aria-setsize": items.length,
584
+ "aria-posinset": v.index + 1,
585
+ className: cn("absolute inset-x-0 top-0", v.index > 0 && "border-t border-border"),
586
+ style: { translate: `0 ${v.start}px` },
587
+ children: children(item, v.index)
588
+ }, itemKey(item));
589
+ })
590
+ })
591
+ });
592
+ }
593
+ // src/components/line-chart.tsx
594
+ function useWidth() {
595
+ const ref = React.useRef(null);
596
+ const [width, setWidth] = React.useState(0);
597
+ React.useLayoutEffect(() => {
598
+ const el = ref.current;
599
+ if (!el) return;
600
+ setWidth(el.getBoundingClientRect().width);
601
+ const ro = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));
602
+ ro.observe(el);
603
+ return () => ro.disconnect();
604
+ }, []);
605
+ return [ref, width];
606
+ }
607
+ /** Four gridline steps of a size people write on a scale (1, 2, 2.5, 4 or 5 × 10ⁿ), ending above the max. */
608
+ function niceMax(max) {
609
+ if (!(max > 0)) return 4;
610
+ const raw = max / 4;
611
+ const mag = 10 ** Math.floor(Math.log10(raw));
612
+ return ([
613
+ 1,
614
+ 2,
615
+ 2.5,
616
+ 4,
617
+ 5,
618
+ 10
619
+ ].map((m) => m * mag).find((s) => s >= raw) ?? 10 * mag) * 4;
620
+ }
621
+ function LineChart({ label, series, seriesLabel, planned, plannedLabel, band, formatX, formatY, xTicks, colour = "var(--chart-1)", colourScale, colourScaleLabel, height = 280, className }) {
622
+ const [ref, width] = useWidth();
623
+ const gradientId = `heat-${React.useId().replace(/[^\w-]/g, "")}`;
624
+ const all = [...series, ...planned ?? []];
625
+ const xMin = Math.min(...all.map((p) => p.x));
626
+ const xMax = Math.max(...all.map((p) => p.x));
627
+ const yMax = niceMax(Math.max(...all.map((p) => p.y)));
628
+ const yTicks = [
629
+ 0,
630
+ .25,
631
+ .5,
632
+ .75,
633
+ 1
634
+ ].map((f) => f * yMax);
635
+ const pad = {
636
+ top: 16,
637
+ right: 16,
638
+ bottom: 28,
639
+ left: 56
640
+ };
641
+ const w = Math.max(0, width - pad.left - pad.right);
642
+ const h = height - pad.top - pad.bottom;
643
+ const sx = (x) => pad.left + (x - xMin) / (xMax - xMin || 1) * w;
644
+ const sy = (y) => pad.top + h - y / yMax * h;
645
+ const path = (pts) => pts.map((p, i) => `${i ? "L" : "M"}${sx(p.x).toFixed(1)},${sy(p.y).toFixed(1)}`).join("");
646
+ const last = series[series.length - 1];
647
+ const peak = series.reduce((a, b) => b.y > a.y ? b : a, series[0]);
648
+ const stops = colourScale ? [...colourScale].sort((a, b) => a.at - b.at) : [];
649
+ /** The colour a value lands on: the highest stop at or below it. Used for the labelled points. */
650
+ const colourAt = (y) => stops.length ? [...stops].reverse().find((s) => s.at <= y)?.colour ?? stops[0].colour : colour;
651
+ const stroke = stops.length ? `url(#${gradientId})` : colour;
652
+ return /* @__PURE__ */ jsxs("figure", {
653
+ className: cn("grid min-w-0 gap-2", className),
654
+ children: [
655
+ /* @__PURE__ */ jsx("div", {
656
+ ref,
657
+ className: "min-w-0",
658
+ children: width > 0 && /* @__PURE__ */ jsxs("svg", {
659
+ width,
660
+ height,
661
+ role: "img",
662
+ "aria-label": label,
663
+ className: "block overflow-visible",
664
+ children: [
665
+ stops.length > 0 && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("linearGradient", {
666
+ id: gradientId,
667
+ gradientUnits: "userSpaceOnUse",
668
+ x1: 0,
669
+ x2: 0,
670
+ y1: sy(0),
671
+ y2: sy(yMax),
672
+ children: stops.map((s) => /* @__PURE__ */ jsx("stop", {
673
+ offset: Math.min(1, Math.max(0, s.at / yMax)),
674
+ stopColor: s.colour
675
+ }, s.at))
676
+ }) }),
677
+ band && /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
678
+ x: pad.left,
679
+ y: sy(band.below),
680
+ width: w,
681
+ height: sy(0) - sy(band.below),
682
+ fill: "var(--success-subtle)"
683
+ }), /* @__PURE__ */ jsx("text", {
684
+ x: pad.left + 8,
685
+ y: sy(band.below) + 16,
686
+ className: "fill-success type-caption",
687
+ children: band.label
688
+ })] }),
689
+ yTicks.map((t) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
690
+ x1: pad.left,
691
+ x2: pad.left + w,
692
+ y1: sy(t),
693
+ y2: sy(t),
694
+ stroke: "var(--border)",
695
+ strokeWidth: 1
696
+ }), /* @__PURE__ */ jsx("text", {
697
+ x: pad.left - 8,
698
+ y: sy(t),
699
+ dy: "0.32em",
700
+ textAnchor: "end",
701
+ className: "fill-muted-foreground type-caption tabular-nums",
702
+ children: formatY(t)
703
+ })] }, t)),
704
+ xTicks.map((t) => /* @__PURE__ */ jsx("text", {
705
+ x: sx(t),
706
+ y: height - 8,
707
+ textAnchor: "middle",
708
+ className: "fill-muted-foreground type-caption tabular-nums",
709
+ children: formatX(t)
710
+ }, t)),
711
+ planned && /* @__PURE__ */ jsx("path", {
712
+ d: path(planned),
713
+ fill: "none",
714
+ stroke: "var(--muted-foreground)",
715
+ strokeWidth: 1.5,
716
+ strokeDasharray: "4 4"
717
+ }),
718
+ /* @__PURE__ */ jsx("path", {
719
+ d: path(series),
720
+ fill: "none",
721
+ stroke,
722
+ strokeWidth: stops.length ? 2.5 : 2,
723
+ strokeLinejoin: "round",
724
+ strokeLinecap: "round"
725
+ }),
726
+ /* @__PURE__ */ jsx("circle", {
727
+ cx: sx(peak.x),
728
+ cy: sy(peak.y),
729
+ r: 3,
730
+ fill: colourAt(peak.y)
731
+ }),
732
+ /* @__PURE__ */ jsxs("text", {
733
+ x: sx(peak.x) + 8,
734
+ y: sy(peak.y) - 8,
735
+ className: "fill-foreground type-caption tabular-nums",
736
+ children: [formatY(peak.y), " peak"]
737
+ }),
738
+ /* @__PURE__ */ jsx("line", {
739
+ x1: sx(last.x),
740
+ x2: sx(last.x),
741
+ y1: pad.top,
742
+ y2: pad.top + h,
743
+ stroke: "var(--foreground)",
744
+ strokeWidth: 1
745
+ }),
746
+ /* @__PURE__ */ jsx("circle", {
747
+ cx: sx(last.x),
748
+ cy: sy(last.y),
749
+ r: 3.5,
750
+ fill: colourAt(last.y)
751
+ }),
752
+ /* @__PURE__ */ jsxs("text", {
753
+ x: sx(last.x) - 8,
754
+ y: sy(last.y) - 10,
755
+ textAnchor: "end",
756
+ className: "fill-foreground type-label tabular-nums",
757
+ children: [formatY(last.y), " now"]
758
+ })
759
+ ]
760
+ })
761
+ }),
762
+ /* @__PURE__ */ jsxs("figcaption", {
763
+ className: "flex flex-wrap gap-x-5 gap-y-1 type-caption text-muted-foreground",
764
+ children: [/* @__PURE__ */ jsxs("span", {
765
+ className: "inline-flex items-center gap-2",
766
+ children: [
767
+ stops.length ? /* @__PURE__ */ jsx("svg", {
768
+ width: stops.length * 8,
769
+ height: "3",
770
+ "aria-hidden": true,
771
+ children: stops.map((s, i) => /* @__PURE__ */ jsx("line", {
772
+ x1: i * 8,
773
+ x2: i * 8 + 8,
774
+ y1: "1.5",
775
+ y2: "1.5",
776
+ stroke: s.colour,
777
+ strokeWidth: 3
778
+ }, s.at))
779
+ }) : /* @__PURE__ */ jsx("svg", {
780
+ width: "16",
781
+ height: "2",
782
+ "aria-hidden": true,
783
+ children: /* @__PURE__ */ jsx("line", {
784
+ x1: "0",
785
+ x2: "16",
786
+ y1: "1",
787
+ y2: "1",
788
+ stroke: colour,
789
+ strokeWidth: 2
790
+ })
791
+ }),
792
+ seriesLabel,
793
+ stops.length > 0 && colourScaleLabel && /* @__PURE__ */ jsxs("span", { children: [
794
+ "(",
795
+ colourScaleLabel,
796
+ ")"
797
+ ] })
798
+ ]
799
+ }), planned && plannedLabel && /* @__PURE__ */ jsxs("span", {
800
+ className: "inline-flex items-center gap-2",
801
+ children: [/* @__PURE__ */ jsx("svg", {
802
+ width: "16",
803
+ height: "2",
804
+ "aria-hidden": true,
805
+ children: /* @__PURE__ */ jsx("line", {
806
+ x1: "0",
807
+ x2: "16",
808
+ y1: "1",
809
+ y2: "1",
810
+ stroke: "var(--muted-foreground)",
811
+ strokeWidth: 1.5,
812
+ strokeDasharray: "4 4"
813
+ })
814
+ }), plannedLabel]
815
+ })]
816
+ }),
817
+ /* @__PURE__ */ jsx("div", {
818
+ className: "sr-only",
819
+ children: /* @__PURE__ */ jsxs("table", { children: [
820
+ /* @__PURE__ */ jsx("caption", { children: label }),
821
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
822
+ scope: "col",
823
+ children: "Time"
824
+ }), /* @__PURE__ */ jsx("th", {
825
+ scope: "col",
826
+ children: seriesLabel
827
+ })] }) }),
828
+ /* @__PURE__ */ jsx("tbody", { children: series.map((p) => /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("td", { children: formatX(p.x) }), /* @__PURE__ */ jsx("td", { children: formatY(p.y) })] }, p.x)) })
829
+ ] })
830
+ })
831
+ ]
832
+ });
833
+ }
834
+ // src/components/page-header.tsx
835
+ function PageHeader({ title, scope, actions, finding, as: Heading = "h1" }) {
836
+ return /* @__PURE__ */ jsxs("header", {
837
+ className: "grid gap-4",
838
+ children: [/* @__PURE__ */ jsxs("div", {
839
+ className: "flex flex-wrap items-end justify-between gap-x-6 gap-y-3",
840
+ children: [/* @__PURE__ */ jsxs("div", {
841
+ className: "grid min-w-0 gap-0.5",
842
+ children: [/* @__PURE__ */ jsx(Heading, {
843
+ className: "type-title text-balance",
844
+ children: title
845
+ }), scope && /* @__PURE__ */ jsx("p", {
846
+ className: "type-body-sm text-muted-foreground",
847
+ children: scope
848
+ })]
849
+ }), actions && /* @__PURE__ */ jsx("div", {
850
+ className: "flex flex-wrap gap-2",
851
+ children: actions
852
+ })]
853
+ }), finding && /* @__PURE__ */ jsxs("div", {
854
+ className: "flex flex-wrap items-center justify-between gap-x-6 gap-y-3 border-y border-border py-3",
855
+ children: [/* @__PURE__ */ jsx("p", {
856
+ className: "max-w-prose min-w-0 type-body text-pretty",
857
+ children: finding.text
858
+ }), finding.action]
859
+ })]
860
+ });
861
+ }
862
+ // src/components/undo.tsx
863
+ function useUndoable(windowMs = 8e3) {
864
+ const [pending, setPending] = React.useState(null);
865
+ const current = React.useRef(null);
866
+ const timer = React.useRef(void 0);
867
+ const set = React.useCallback((p) => {
868
+ current.current = p;
869
+ setPending(p);
870
+ }, []);
871
+ const schedule = React.useCallback((next) => {
872
+ window.clearTimeout(timer.current);
873
+ current.current?.commit();
874
+ set(next);
875
+ timer.current = window.setTimeout(() => {
876
+ current.current?.commit();
877
+ set(null);
878
+ }, windowMs);
879
+ }, [set, windowMs]);
880
+ const undo = React.useCallback(() => {
881
+ window.clearTimeout(timer.current);
882
+ set(null);
883
+ }, [set]);
884
+ React.useEffect(() => () => {
885
+ window.clearTimeout(timer.current);
886
+ current.current?.commit();
887
+ current.current = null;
888
+ }, []);
889
+ return {
890
+ pending,
891
+ schedule,
892
+ undo
893
+ };
894
+ }
895
+ function UndoBar({ pending, onUndo, inline = false }) {
896
+ return /* @__PURE__ */ jsx("div", {
897
+ "aria-live": "polite",
898
+ className: inline ? "flex" : "pointer-events-none fixed inset-x-0 bottom-4 z-50 flex justify-center px-4",
899
+ children: pending && /* @__PURE__ */ jsxs("div", {
900
+ className: "pointer-events-auto flex max-w-lg items-center gap-4 rounded-overlay bg-foreground py-2 pr-2 pl-4 text-background shadow-overlay",
901
+ children: [/* @__PURE__ */ jsx("p", {
902
+ className: "min-w-0 type-body-sm",
903
+ children: pending.message
904
+ }), /* @__PURE__ */ jsx(Button, {
905
+ size: "sm",
906
+ variant: "ghost",
907
+ className: "text-background hover:bg-neutral-11",
908
+ onClick: onUndo,
909
+ children: "Undo"
910
+ })]
911
+ })
912
+ });
913
+ }
914
+ // src/components/done.tsx
915
+ function Done({ title, next, figure, actions, focusOnMount = true, className }) {
916
+ const heading = React.useRef(null);
917
+ const titleId = React.useId();
918
+ React.useEffect(() => {
919
+ if (focusOnMount) heading.current?.focus();
920
+ }, [focusOnMount]);
921
+ return /* @__PURE__ */ jsxs("section", {
922
+ "aria-labelledby": titleId,
923
+ className: cn("grid gap-5", className),
924
+ children: [
925
+ /* @__PURE__ */ jsxs("div", {
926
+ className: "grid gap-2",
927
+ children: [/* @__PURE__ */ jsxs("h1", {
928
+ id: titleId,
929
+ ref: heading,
930
+ tabIndex: -1,
931
+ className: "flex items-start gap-2 type-title",
932
+ children: [/* @__PURE__ */ jsx(Check, {
933
+ "aria-hidden": true,
934
+ className: "mt-1 size-6 shrink-0 text-success"
935
+ }), title]
936
+ }), /* @__PURE__ */ jsx("div", {
937
+ className: "max-w-prose type-body text-muted-foreground",
938
+ children: next
939
+ })]
940
+ }),
941
+ figure,
942
+ actions && /* @__PURE__ */ jsx("div", {
943
+ className: "flex flex-wrap items-center gap-3",
944
+ children: actions
945
+ })
946
+ ]
947
+ });
948
+ }
949
+ // src/components/app-shell.tsx
950
+ function AppShell({ product, nav, navLabel = "Main", topBar, children, className }) {
951
+ return /* @__PURE__ */ jsxs("div", {
952
+ className: cn("min-h-dvh md:grid md:grid-cols-[14rem_minmax(0,1fr)]", className),
953
+ children: [/* @__PURE__ */ jsx("aside", {
954
+ className: "border-b border-border md:border-r md:border-b-0",
955
+ children: /* @__PURE__ */ jsxs("div", {
956
+ className: "flex items-center gap-4 px-4 py-3 md:block md:px-3 md:py-4",
957
+ children: [/* @__PURE__ */ jsx(Link, {
958
+ to: "/",
959
+ className: "shrink-0 rounded-control px-2 type-heading text-foreground no-underline",
960
+ children: product
961
+ }), /* @__PURE__ */ jsx(SideNav, {
962
+ items: nav,
963
+ label: navLabel,
964
+ className: "min-w-0 flex-1 md:mt-4"
965
+ })]
966
+ })
967
+ }), /* @__PURE__ */ jsxs("div", {
968
+ className: "min-w-0",
969
+ children: [topBar && /* @__PURE__ */ jsx("div", {
970
+ className: "flex items-center gap-3 border-b border-border px-4 py-2 md:px-8",
971
+ children: topBar
972
+ }), /* @__PURE__ */ jsx("main", {
973
+ className: "px-4 py-6 md:px-8 md:py-8",
974
+ children
975
+ })]
976
+ })]
977
+ });
978
+ }
979
+ function SideNav({ items, label, className }) {
980
+ return /* @__PURE__ */ jsx("nav", {
981
+ "aria-label": label,
982
+ className: cn("min-w-0", className),
983
+ children: /* @__PURE__ */ jsx("ul", {
984
+ className: "flex gap-1 overflow-x-auto md:grid md:overflow-visible",
985
+ children: items.map(({ to, label, icon: Icon, end }) => /* @__PURE__ */ jsx("li", {
986
+ className: "shrink-0",
987
+ children: /* @__PURE__ */ jsxs(NavLink, {
988
+ to,
989
+ end,
990
+ className: ({ isActive }) => cn("flex h-(--control-h-sm) items-center gap-2 rounded-control px-2 type-label whitespace-nowrap no-underline transition-state", isActive ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"),
991
+ children: [Icon && /* @__PURE__ */ jsx(Icon, {
992
+ "aria-hidden": true,
993
+ className: "size-4 shrink-0"
994
+ }), label]
995
+ })
996
+ }, to))
997
+ })
998
+ });
999
+ }
1000
+ function Breadcrumb({ items }) {
1001
+ return /* @__PURE__ */ jsx("nav", {
1002
+ "aria-label": "Breadcrumb",
1003
+ children: /* @__PURE__ */ jsx("ol", {
1004
+ className: "flex flex-wrap items-center gap-x-2 type-body-sm text-muted-foreground",
1005
+ children: items.map((item, i) => {
1006
+ const last = i === items.length - 1;
1007
+ return /* @__PURE__ */ jsxs("li", {
1008
+ className: "flex items-center gap-2",
1009
+ children: [item.to && !last ? /* @__PURE__ */ jsx(Link, {
1010
+ to: item.to,
1011
+ className: "text-muted-foreground underline hover:text-foreground",
1012
+ children: item.label
1013
+ }) : /* @__PURE__ */ jsx("span", {
1014
+ "aria-current": last ? "page" : void 0,
1015
+ className: cn(last && "text-foreground"),
1016
+ children: item.label
1017
+ }), !last && /* @__PURE__ */ jsx("span", {
1018
+ "aria-hidden": true,
1019
+ children: "/"
1020
+ })]
1021
+ }, `${item.label}-${i}`);
1022
+ })
1023
+ })
1024
+ });
1025
+ }
1026
+ // src/components/tabs.tsx
1027
+ function Tabs({ param, items, label, children, className }) {
1028
+ const [params, setParams] = useSearchParams();
1029
+ const fallback = items[0]?.value ?? "";
1030
+ const raw = params.get(param);
1031
+ const value = items.some((i) => i.value === raw) ? raw : fallback;
1032
+ const baseId = React.useId();
1033
+ const refs = React.useRef([]);
1034
+ const select = (next, focus = false) => {
1035
+ setParams((p) => {
1036
+ const out = new URLSearchParams(p);
1037
+ if (next === fallback) out.delete(param);
1038
+ else out.set(param, next);
1039
+ return out;
1040
+ }, { replace: true });
1041
+ if (focus) refs.current[items.findIndex((i) => i.value === next)]?.focus();
1042
+ };
1043
+ const onKeyDown = (e, index) => {
1044
+ const last = items.length - 1;
1045
+ const to = e.key === "ArrowRight" ? index === last ? 0 : index + 1 : e.key === "ArrowLeft" ? index === 0 ? last : index - 1 : e.key === "Home" ? 0 : e.key === "End" ? last : void 0;
1046
+ if (to === void 0) return;
1047
+ e.preventDefault();
1048
+ select(items[to].value, true);
1049
+ };
1050
+ return /* @__PURE__ */ jsxs("div", {
1051
+ className: cn("grid gap-4", className),
1052
+ children: [/* @__PURE__ */ jsx("div", {
1053
+ role: "tablist",
1054
+ "aria-label": label,
1055
+ className: "flex gap-4 overflow-x-auto border-b border-border",
1056
+ children: items.map((item, i) => {
1057
+ const selected = item.value === value;
1058
+ return /* @__PURE__ */ jsx("button", {
1059
+ ref: (el) => {
1060
+ refs.current[i] = el;
1061
+ },
1062
+ type: "button",
1063
+ role: "tab",
1064
+ id: `${baseId}-tab-${item.value}`,
1065
+ "aria-selected": selected,
1066
+ "aria-controls": `${baseId}-panel`,
1067
+ tabIndex: selected ? 0 : -1,
1068
+ onClick: () => select(item.value),
1069
+ onKeyDown: (e) => onKeyDown(e, i),
1070
+ className: cn("-mb-px cursor-pointer border-b-2 py-2 type-label whitespace-nowrap transition-state", selected ? "border-foreground font-semibold text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"),
1071
+ children: item.label
1072
+ }, item.value);
1073
+ })
1074
+ }), /* @__PURE__ */ jsx("div", {
1075
+ role: "tabpanel",
1076
+ id: `${baseId}-panel`,
1077
+ "aria-labelledby": `${baseId}-tab-${value}`,
1078
+ tabIndex: 0,
1079
+ children: children(value)
1080
+ })]
1081
+ });
1082
+ }
1083
+ export { AppShell, AsyncButton, Breadcrumb, Button, ChoiceGroup, DataTable, DataView, Done, EmptyState, Field, HttpError, IconButton, Input, LineChart, Metric, MetricStrip, NBSP, NativeSelect, PageHeader, SideNav, Skeleton, Status, StrataQueryProvider, TableSkeleton, Tabs, Textarea, UndoBar, VirtualList, buttonVariants, cn, createFormat, createQueryClient, describeHttpError, fetchJson, format, parseSort, serialiseSort, toLoad, typeSteps, useAsyncAction, useLoad, useUndoable };
1084
+
1085
+ //# sourceMappingURL=index.js.map