@pipelex/mthds-form 0.3.0 → 0.5.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/README.md +21 -7
- package/dist/chunk-25I6JDIK.js +28 -0
- package/dist/chunk-25I6JDIK.js.map +1 -0
- package/dist/chunk-5Q2W6HBA.js +201 -0
- package/dist/chunk-5Q2W6HBA.js.map +1 -0
- package/dist/chunk-GCXV76WU.js +213 -0
- package/dist/chunk-GCXV76WU.js.map +1 -0
- package/dist/chunk-GKV77RAS.js +46 -0
- package/dist/chunk-GKV77RAS.js.map +1 -0
- package/dist/chunk-IIJRJGZQ.js +128 -0
- package/dist/chunk-IIJRJGZQ.js.map +1 -0
- package/dist/chunk-IM2AHWBR.js +99 -0
- package/dist/chunk-IM2AHWBR.js.map +1 -0
- package/dist/chunk-LBFZXYCY.js +44 -0
- package/dist/chunk-LBFZXYCY.js.map +1 -0
- package/dist/chunk-OD3VYXUY.js +156 -0
- package/dist/chunk-OD3VYXUY.js.map +1 -0
- package/dist/chunk-PM55AHRO.js +52 -0
- package/dist/chunk-PM55AHRO.js.map +1 -0
- package/dist/chunk-RPA7DNSK.js +41 -0
- package/dist/chunk-RPA7DNSK.js.map +1 -0
- package/dist/chunk-V3OV2XQT.js +12 -0
- package/dist/chunk-V3OV2XQT.js.map +1 -0
- package/dist/chunk-VTPUDBSJ.js +61 -0
- package/dist/chunk-VTPUDBSJ.js.map +1 -0
- package/dist/chunk-XYQ7NXQZ.js +31 -0
- package/dist/chunk-XYQ7NXQZ.js.map +1 -0
- package/dist/chunk-YEVVR6FE.js +66 -0
- package/dist/chunk-YEVVR6FE.js.map +1 -0
- package/dist/core/contracts.d.ts +114 -0
- package/dist/core/contracts.js +4 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/date-format.d.ts +87 -0
- package/dist/core/date-format.js +3 -0
- package/dist/core/date-format.js.map +1 -0
- package/dist/core/derive.d.ts +55 -0
- package/dist/core/derive.js +6 -0
- package/dist/core/derive.js.map +1 -0
- package/dist/core/descriptor.d.ts +172 -0
- package/dist/core/descriptor.js +3 -0
- package/dist/core/descriptor.js.map +1 -0
- package/dist/core/gate-validator.d.ts +27 -0
- package/dist/core/gate-validator.js +4 -0
- package/dist/core/gate-validator.js.map +1 -0
- package/dist/core/gate.d.ts +144 -0
- package/dist/core/gate.js +12 -0
- package/dist/core/gate.js.map +1 -0
- package/dist/core/gating-fields.d.ts +38 -0
- package/dist/core/gating-fields.js +6 -0
- package/dist/core/gating-fields.js.map +1 -0
- package/dist/core/index.d.ts +13 -623
- package/dist/core/index.js +14 -1
- package/dist/core/normalize-schema.d.ts +30 -0
- package/dist/core/normalize-schema.js +4 -0
- package/dist/core/normalize-schema.js.map +1 -0
- package/dist/core/own-property.d.ts +46 -0
- package/dist/core/own-property.js +3 -0
- package/dist/core/own-property.js.map +1 -0
- package/dist/core/readiness.d.ts +91 -0
- package/dist/core/readiness.js +4 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core/schema-utils.d.ts +86 -0
- package/dist/core/schema-utils.js +3 -0
- package/dist/core/schema-utils.js.map +1 -0
- package/dist/core/validation-message.d.ts +39 -0
- package/dist/core/validation-message.js +4 -0
- package/dist/core/validation-message.js.map +1 -0
- package/dist/core/values.d.ts +98 -0
- package/dist/core/values.js +10 -0
- package/dist/core/values.js.map +1 -0
- package/dist/core/wire-format.d.ts +95 -0
- package/dist/core/wire-format.js +6 -0
- package/dist/core/wire-format.js.map +1 -0
- package/dist/react/index.d.ts +43 -2
- package/dist/react/index.js +166 -64
- package/dist/react/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -1
- package/dist/chunk-S6KOTTGD.js +0 -990
- package/dist/chunk-S6KOTTGD.js.map +0 -1
- package/dist/descriptor-DPrrfAjL.d.ts +0 -101
package/dist/react/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { isFilled } from '../chunk-PM55AHRO.js';
|
|
3
|
+
import { toStoredDateValue, toDateInputValue } from '../chunk-GKV77RAS.js';
|
|
4
|
+
import { ownProp } from '../chunk-V3OV2XQT.js';
|
|
3
5
|
import { clsx } from 'clsx';
|
|
4
6
|
import { twMerge } from 'tailwind-merge';
|
|
5
7
|
import { ChevronDown, ChevronUp, Check, List, Braces, ListChecks, ToggleLeft, Hash, Image, FileText, CalendarDays, Type, Loader2, Upload, Link2, ImageOff, EyeOff, Eye, X, Minus, Plus, Trash2 } from 'lucide-react';
|
|
6
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
9
|
import * as React2 from 'react';
|
|
8
|
-
import { createContext, use, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
|
|
10
|
+
import { createContext, use, useId, useMemo, useContext, useState, useRef, useCallback, useEffect } from 'react';
|
|
9
11
|
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
|
10
12
|
import { useDropzone } from 'react-dropzone';
|
|
11
13
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
@@ -83,9 +85,24 @@ var Switch = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */
|
|
|
83
85
|
}
|
|
84
86
|
));
|
|
85
87
|
Switch.displayName = SwitchPrimitives.Root.displayName;
|
|
88
|
+
var FieldDomIdContext = createContext(null);
|
|
89
|
+
function FieldDomIdProvider({
|
|
90
|
+
prefix,
|
|
91
|
+
children
|
|
92
|
+
}) {
|
|
93
|
+
const generated = useId();
|
|
94
|
+
return /* @__PURE__ */ jsx(FieldDomIdContext, { value: prefix ?? generated, children });
|
|
95
|
+
}
|
|
96
|
+
function useFieldDomId(path) {
|
|
97
|
+
const scoped = use(FieldDomIdContext);
|
|
98
|
+
const local = useId();
|
|
99
|
+
const prefix = scoped ?? local;
|
|
100
|
+
return prefix === "" ? path : `${prefix}-${path}`;
|
|
101
|
+
}
|
|
86
102
|
function BooleanField({ field, value, onChange, id, disabled }) {
|
|
87
103
|
const presentation = useFieldPresentation();
|
|
88
104
|
const isApp = presentation === "app";
|
|
105
|
+
const domId = useFieldDomId(id);
|
|
89
106
|
return /* @__PURE__ */ jsxs(
|
|
90
107
|
"div",
|
|
91
108
|
{
|
|
@@ -99,7 +116,7 @@ function BooleanField({ field, value, onChange, id, disabled }) {
|
|
|
99
116
|
/* @__PURE__ */ jsx(
|
|
100
117
|
"label",
|
|
101
118
|
{
|
|
102
|
-
htmlFor:
|
|
119
|
+
htmlFor: domId,
|
|
103
120
|
className: cn(
|
|
104
121
|
"text-[13px] font-medium leading-none text-foreground",
|
|
105
122
|
isApp ? "text-[13.5px]" : "font-mono"
|
|
@@ -114,7 +131,7 @@ function BooleanField({ field, value, onChange, id, disabled }) {
|
|
|
114
131
|
/* @__PURE__ */ jsx(
|
|
115
132
|
Switch,
|
|
116
133
|
{
|
|
117
|
-
id,
|
|
134
|
+
id: domId,
|
|
118
135
|
checked: value ?? false,
|
|
119
136
|
onCheckedChange: onChange,
|
|
120
137
|
disabled,
|
|
@@ -137,6 +154,7 @@ var DEFAULT_FIELD_STRINGS = {
|
|
|
137
154
|
addItem: "Add item",
|
|
138
155
|
removeItemAria: (index) => `Remove item ${index}`,
|
|
139
156
|
itemsCount: (count) => count === 1 ? "1 item" : `${count} items`,
|
|
157
|
+
itemsCountOf: (count, total) => `${count} of ${total} ${total === 1 ? "item" : "items"}`,
|
|
140
158
|
uploading: "Uploading\u2026",
|
|
141
159
|
dropToUpload: "Drop to upload",
|
|
142
160
|
dropOrBrowse: "Drop a file or click to browse",
|
|
@@ -211,6 +229,7 @@ var fieldControlClass = cn(
|
|
|
211
229
|
"aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/30"
|
|
212
230
|
);
|
|
213
231
|
function DateField({ field, value, onChange, id, error, disabled }) {
|
|
232
|
+
const domId = useFieldDomId(id);
|
|
214
233
|
return /* @__PURE__ */ jsx(
|
|
215
234
|
FieldShell,
|
|
216
235
|
{
|
|
@@ -221,11 +240,11 @@ function DateField({ field, value, onChange, id, error, disabled }) {
|
|
|
221
240
|
description: field.description,
|
|
222
241
|
required: field.required,
|
|
223
242
|
error,
|
|
224
|
-
htmlFor:
|
|
243
|
+
htmlFor: domId,
|
|
225
244
|
children: /* @__PURE__ */ jsx(
|
|
226
245
|
"input",
|
|
227
246
|
{
|
|
228
|
-
id,
|
|
247
|
+
id: domId,
|
|
229
248
|
type: "date",
|
|
230
249
|
value: toDateInputValue(value),
|
|
231
250
|
disabled,
|
|
@@ -239,6 +258,13 @@ function DateField({ field, value, onChange, id, error, disabled }) {
|
|
|
239
258
|
}
|
|
240
259
|
var IMAGE_EXT_RE = /\.(jpg|jpeg|png|gif|webp|bmp|svg|avif)(\?|$)/i;
|
|
241
260
|
var PDF_EXT_RE = /\.pdf(\?|$)/i;
|
|
261
|
+
var DATA_URL_MIME_RE = /^data:([^;,]+)/i;
|
|
262
|
+
function isDirectlyViewable(url) {
|
|
263
|
+
return !!url && (/^https?:/i.test(url) || /^data:/i.test(url) || /^blob:/i.test(url));
|
|
264
|
+
}
|
|
265
|
+
function dataUrlMime(url) {
|
|
266
|
+
return DATA_URL_MIME_RE.exec(url)?.[1]?.toLowerCase();
|
|
267
|
+
}
|
|
242
268
|
function DocumentField(props) {
|
|
243
269
|
return /* @__PURE__ */ jsx(FileField, { ...props, category: "document" });
|
|
244
270
|
}
|
|
@@ -258,27 +284,35 @@ function FileField({
|
|
|
258
284
|
category
|
|
259
285
|
}) {
|
|
260
286
|
const s = useFieldStrings();
|
|
287
|
+
const domId = useFieldDomId(id);
|
|
261
288
|
const [showUrl, setShowUrl] = useState(false);
|
|
262
289
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
263
290
|
const [localPreview, setLocalPreview] = useState(null);
|
|
291
|
+
const [resolved, setResolved] = useState(null);
|
|
264
292
|
const localUrlRef = useRef(null);
|
|
265
293
|
const setLocal = useCallback((next) => {
|
|
266
294
|
if (localUrlRef.current) URL.revokeObjectURL(localUrlRef.current);
|
|
267
|
-
localUrlRef.current = next?.
|
|
295
|
+
localUrlRef.current = next?.objectUrl ?? null;
|
|
268
296
|
setLocalPreview(next);
|
|
269
297
|
}, []);
|
|
270
298
|
useEffect(() => () => setLocal(null), [setLocal]);
|
|
271
299
|
const handleFile = useCallback(
|
|
272
300
|
(file) => {
|
|
273
|
-
setLocal({
|
|
301
|
+
setLocal({ objectUrl: URL.createObjectURL(file), type: file.type });
|
|
274
302
|
onDropFile(file);
|
|
275
303
|
},
|
|
276
304
|
[setLocal, onDropFile]
|
|
277
305
|
);
|
|
306
|
+
const busy = disabled || uploading;
|
|
278
307
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
279
308
|
maxFiles: 1,
|
|
280
309
|
multiple: false,
|
|
281
|
-
disabled:
|
|
310
|
+
disabled: busy,
|
|
311
|
+
// The tab stop belongs on the INPUT, not on this div - see the root element
|
|
312
|
+
// below. Without this, react-dropzone puts `tabIndex: 0` and its own key
|
|
313
|
+
// handlers on a `role="presentation"` div, and the element a keyboard or
|
|
314
|
+
// voice-control user lands on is a generic with no role and no name.
|
|
315
|
+
noKeyboard: true,
|
|
282
316
|
onDrop: (files) => {
|
|
283
317
|
const file = files[0];
|
|
284
318
|
if (file) handleFile(file);
|
|
@@ -286,28 +320,43 @@ function FileField({
|
|
|
286
320
|
});
|
|
287
321
|
const clear = useCallback(() => {
|
|
288
322
|
setLocal(null);
|
|
289
|
-
|
|
323
|
+
setResolved(null);
|
|
290
324
|
setPreviewOpen(false);
|
|
291
325
|
onChange(void 0);
|
|
292
326
|
}, [setLocal, onChange]);
|
|
327
|
+
const localIsCurrent = !!localPreview && (localPreview.boundUrl === void 0 || localPreview.boundUrl === value?.url);
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
if (!localPreview) return;
|
|
330
|
+
if (localPreview.boundUrl === void 0) {
|
|
331
|
+
if (uploading || !value?.url) return;
|
|
332
|
+
setLocalPreview({ ...localPreview, boundUrl: value.url });
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (value?.url !== localPreview.boundUrl) setLocal(null);
|
|
336
|
+
}, [localPreview, value?.url, uploading, setLocal]);
|
|
293
337
|
const hasFile = !!value?.url;
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
const
|
|
338
|
+
const localType = localIsCurrent ? localPreview?.type : void 0;
|
|
339
|
+
const filename = value?.filename ?? "";
|
|
340
|
+
const url = value?.url ?? "";
|
|
341
|
+
const urlMime = dataUrlMime(url);
|
|
342
|
+
const isImage = category === "image" || localType?.startsWith("image/") === true || urlMime?.startsWith("image/") === true || IMAGE_EXT_RE.test(filename) || IMAGE_EXT_RE.test(url);
|
|
343
|
+
const isPdf = localType === "application/pdf" || urlMime === "application/pdf" || PDF_EXT_RE.test(filename) || PDF_EXT_RE.test(url);
|
|
297
344
|
const canPreview = isImage || isPdf;
|
|
298
|
-
const
|
|
299
|
-
const storageUri = value?.url && !value.url.startsWith("http") ? value.url : null;
|
|
345
|
+
const storageUri = value?.url && !isDirectlyViewable(value.url) ? value.url : null;
|
|
300
346
|
useEffect(() => {
|
|
301
|
-
if (!previewOpen || !storageUri ||
|
|
347
|
+
if (!previewOpen || !storageUri || localIsCurrent || !resolveUrl) return;
|
|
302
348
|
let cancelled = false;
|
|
303
|
-
void resolveUrl(storageUri).then((
|
|
304
|
-
if (!cancelled)
|
|
349
|
+
void resolveUrl(storageUri).then((src) => {
|
|
350
|
+
if (!cancelled) setResolved(src ? { uri: storageUri, src } : null);
|
|
351
|
+
}).catch(() => {
|
|
352
|
+
if (!cancelled) setResolved(null);
|
|
305
353
|
});
|
|
306
354
|
return () => {
|
|
307
355
|
cancelled = true;
|
|
308
356
|
};
|
|
309
|
-
}, [previewOpen, storageUri,
|
|
310
|
-
const
|
|
357
|
+
}, [previewOpen, storageUri, localIsCurrent, resolveUrl]);
|
|
358
|
+
const resolvedSrc = resolved && resolved.uri === storageUri ? resolved.src : void 0;
|
|
359
|
+
const previewSrc = (localIsCurrent ? localPreview?.objectUrl : void 0) ?? (isDirectlyViewable(value?.url) ? value.url : resolvedSrc);
|
|
311
360
|
return /* @__PURE__ */ jsxs(
|
|
312
361
|
FieldShell,
|
|
313
362
|
{
|
|
@@ -318,6 +367,7 @@ function FileField({
|
|
|
318
367
|
description: field.description,
|
|
319
368
|
required: field.required,
|
|
320
369
|
error,
|
|
370
|
+
htmlFor: domId,
|
|
321
371
|
children: [
|
|
322
372
|
hasFile && !uploading ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
323
373
|
/* @__PURE__ */ jsx(
|
|
@@ -332,36 +382,53 @@ function FileField({
|
|
|
332
382
|
}
|
|
333
383
|
),
|
|
334
384
|
previewOpen && (previewSrc && isImage ? /* @__PURE__ */ jsx(ImagePreview, { src: previewSrc, filename: value?.filename }) : previewSrc && isPdf ? /* @__PURE__ */ jsx(PdfPreview, { src: previewSrc }) : /* @__PURE__ */ jsx("div", { className: "flex h-24 items-center justify-center rounded-md border border-border bg-muted", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }))
|
|
335
|
-
] }) :
|
|
336
|
-
"div
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
385
|
+
] }) : (
|
|
386
|
+
// `role="presentation"` and no tab stop: this div is the drop TARGET and
|
|
387
|
+
// the visual affordance, and the control inside it is a real
|
|
388
|
+
// `<input type="file">`, which already has the right role and now has a
|
|
389
|
+
// name. Giving the div a `role="button"` of its own would put a second
|
|
390
|
+
// named control in the accessibility tree for one value. The focus ring
|
|
391
|
+
// is `focus-within` because the element that takes focus is clip-hidden.
|
|
392
|
+
/* @__PURE__ */ jsxs(
|
|
393
|
+
"div",
|
|
394
|
+
{
|
|
395
|
+
...getRootProps(),
|
|
396
|
+
className: cn(
|
|
397
|
+
"flex flex-col items-center justify-center gap-2 rounded-lg border border-dashed px-4 py-6 text-center",
|
|
398
|
+
"cursor-pointer select-none transition-colors",
|
|
399
|
+
"focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background",
|
|
400
|
+
isDragActive ? "border-primary/60 bg-primary/5" : "border-border bg-input hover:border-border hover:bg-muted",
|
|
401
|
+
busy && "cursor-not-allowed opacity-60"
|
|
402
|
+
),
|
|
403
|
+
children: [
|
|
404
|
+
/* @__PURE__ */ jsx(
|
|
405
|
+
"input",
|
|
406
|
+
{
|
|
407
|
+
...getInputProps({ tabIndex: 0 }),
|
|
408
|
+
id: domId,
|
|
409
|
+
disabled: busy,
|
|
410
|
+
"aria-invalid": !!error
|
|
411
|
+
}
|
|
412
|
+
),
|
|
413
|
+
uploading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
414
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }),
|
|
415
|
+
/* @__PURE__ */ jsx("p", { className: "text-[12px] text-muted-foreground", children: s.uploading })
|
|
416
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
417
|
+
/* @__PURE__ */ jsx(Upload, { className: "h-5 w-5 text-muted-foreground", strokeWidth: 1.75 }),
|
|
418
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
419
|
+
/* @__PURE__ */ jsx("p", { className: "text-[13px] text-foreground", children: isDragActive ? s.dropToUpload : s.dropOrBrowse }),
|
|
420
|
+
field.accept && /* @__PURE__ */ jsx("p", { className: "font-mono text-[10.5px] text-muted-foreground", children: field.accept })
|
|
421
|
+
] })
|
|
355
422
|
] })
|
|
356
|
-
]
|
|
357
|
-
|
|
358
|
-
|
|
423
|
+
]
|
|
424
|
+
}
|
|
425
|
+
)
|
|
359
426
|
),
|
|
360
427
|
!showUrl ? /* @__PURE__ */ jsxs(
|
|
361
428
|
"button",
|
|
362
429
|
{
|
|
363
430
|
type: "button",
|
|
364
|
-
disabled,
|
|
431
|
+
disabled: busy,
|
|
365
432
|
onClick: () => setShowUrl(true),
|
|
366
433
|
className: "inline-flex items-center gap-1 font-mono text-[11px] text-muted-foreground transition-colors hover:text-foreground disabled:opacity-50",
|
|
367
434
|
children: [
|
|
@@ -375,7 +442,7 @@ function FileField({
|
|
|
375
442
|
type: "text",
|
|
376
443
|
autoFocus: true,
|
|
377
444
|
value: value?.url ?? "",
|
|
378
|
-
disabled,
|
|
445
|
+
disabled: busy,
|
|
379
446
|
placeholder: s.urlPlaceholder,
|
|
380
447
|
onChange: (e) => {
|
|
381
448
|
setLocal(null);
|
|
@@ -632,6 +699,7 @@ var CLEAR_VALUE = "__none__";
|
|
|
632
699
|
function EnumField({ field, value, onChange, id, error, disabled }) {
|
|
633
700
|
const s = useFieldStrings();
|
|
634
701
|
const isSegmented = field.options.length <= 4 && field.options.every((o) => o.length <= 16);
|
|
702
|
+
const domId = useFieldDomId(id);
|
|
635
703
|
return /* @__PURE__ */ jsx(
|
|
636
704
|
FieldShell,
|
|
637
705
|
{
|
|
@@ -642,7 +710,7 @@ function EnumField({ field, value, onChange, id, error, disabled }) {
|
|
|
642
710
|
description: field.description,
|
|
643
711
|
required: field.required,
|
|
644
712
|
error,
|
|
645
|
-
htmlFor: isSegmented ? void 0 :
|
|
713
|
+
htmlFor: isSegmented ? void 0 : domId,
|
|
646
714
|
children: isSegmented ? (
|
|
647
715
|
// `type="single"` gives the roving tabindex and arrow-key navigation a
|
|
648
716
|
// radiogroup owes its user, and emits `''` when the active item is
|
|
@@ -678,7 +746,7 @@ function EnumField({ field, value, onChange, id, error, disabled }) {
|
|
|
678
746
|
disabled,
|
|
679
747
|
onValueChange: (next) => onChange(next === CLEAR_VALUE ? void 0 : next),
|
|
680
748
|
children: [
|
|
681
|
-
/* @__PURE__ */ jsx(SelectTrigger, { id, "aria-invalid": !!error, className: "h-11", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: s.selectPlaceholder }) }),
|
|
749
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: domId, "aria-invalid": !!error, className: "h-11", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: s.selectPlaceholder }) }),
|
|
682
750
|
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
683
751
|
/* @__PURE__ */ jsx(SelectItem, { value: CLEAR_VALUE, children: s.selectPlaceholder }),
|
|
684
752
|
field.options.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option, children: option }, option))
|
|
@@ -691,6 +759,7 @@ function EnumField({ field, value, onChange, id, error, disabled }) {
|
|
|
691
759
|
}
|
|
692
760
|
function NumberField({ field, value, onChange, id, error, disabled }) {
|
|
693
761
|
const s = useFieldStrings();
|
|
762
|
+
const domId = useFieldDomId(id);
|
|
694
763
|
const step = field.integer ? 1 : 0.1;
|
|
695
764
|
const clamp = (n) => {
|
|
696
765
|
let next = n;
|
|
@@ -710,12 +779,12 @@ function NumberField({ field, value, onChange, id, error, disabled }) {
|
|
|
710
779
|
description: field.description ?? rangeHint,
|
|
711
780
|
required: field.required,
|
|
712
781
|
error,
|
|
713
|
-
htmlFor:
|
|
782
|
+
htmlFor: domId,
|
|
714
783
|
children: /* @__PURE__ */ jsxs("div", { className: "flex items-stretch gap-1.5", children: [
|
|
715
784
|
/* @__PURE__ */ jsx(
|
|
716
785
|
"input",
|
|
717
786
|
{
|
|
718
|
-
id,
|
|
787
|
+
id: domId,
|
|
719
788
|
type: "number",
|
|
720
789
|
inputMode: field.integer ? "numeric" : "decimal",
|
|
721
790
|
value: value ?? "",
|
|
@@ -761,6 +830,7 @@ function StepButton({
|
|
|
761
830
|
}
|
|
762
831
|
function TextField({ field, value, onChange, id, error, disabled }) {
|
|
763
832
|
const s = useFieldStrings();
|
|
833
|
+
const domId = useFieldDomId(id);
|
|
764
834
|
return /* @__PURE__ */ jsx(
|
|
765
835
|
FieldShell,
|
|
766
836
|
{
|
|
@@ -771,11 +841,11 @@ function TextField({ field, value, onChange, id, error, disabled }) {
|
|
|
771
841
|
description: field.description,
|
|
772
842
|
required: field.required,
|
|
773
843
|
error,
|
|
774
|
-
htmlFor:
|
|
844
|
+
htmlFor: domId,
|
|
775
845
|
children: /* @__PURE__ */ jsx(
|
|
776
846
|
"input",
|
|
777
847
|
{
|
|
778
|
-
id,
|
|
848
|
+
id: domId,
|
|
779
849
|
type: "text",
|
|
780
850
|
value: value ?? "",
|
|
781
851
|
disabled,
|
|
@@ -790,6 +860,7 @@ function TextField({ field, value, onChange, id, error, disabled }) {
|
|
|
790
860
|
}
|
|
791
861
|
function ProseField({ field, value, onChange, id, error, disabled }) {
|
|
792
862
|
const s = useFieldStrings();
|
|
863
|
+
const domId = useFieldDomId(id);
|
|
793
864
|
return /* @__PURE__ */ jsx(
|
|
794
865
|
FieldShell,
|
|
795
866
|
{
|
|
@@ -800,11 +871,11 @@ function ProseField({ field, value, onChange, id, error, disabled }) {
|
|
|
800
871
|
description: field.description,
|
|
801
872
|
required: field.required,
|
|
802
873
|
error,
|
|
803
|
-
htmlFor:
|
|
874
|
+
htmlFor: domId,
|
|
804
875
|
children: /* @__PURE__ */ jsx(
|
|
805
876
|
"textarea",
|
|
806
877
|
{
|
|
807
|
-
id,
|
|
878
|
+
id: domId,
|
|
808
879
|
value: value ?? "",
|
|
809
880
|
disabled,
|
|
810
881
|
"aria-invalid": !!error,
|
|
@@ -842,8 +913,8 @@ function ObjectField({ field, value, onChange, id, error, env }) {
|
|
|
842
913
|
const isApp = presentation === "app";
|
|
843
914
|
const data = value ?? {};
|
|
844
915
|
const setChild = (name, childValue) => onChange({ ...data, [name]: childValue });
|
|
845
|
-
const optionalEmpty = field.fields.filter((f) => !f.required && !isFilled(data
|
|
846
|
-
const visible = showOptional ? field.fields : field.fields.filter((f) => f.required || isFilled(data
|
|
916
|
+
const optionalEmpty = field.fields.filter((f) => !f.required && !isFilled(ownProp(data, f.name)));
|
|
917
|
+
const visible = showOptional ? field.fields : field.fields.filter((f) => f.required || isFilled(ownProp(data, f.name)));
|
|
847
918
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
848
919
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
|
|
849
920
|
/* @__PURE__ */ jsx(
|
|
@@ -872,7 +943,7 @@ function ObjectField({ field, value, onChange, id, error, env }) {
|
|
|
872
943
|
FieldRenderer,
|
|
873
944
|
{
|
|
874
945
|
field: child,
|
|
875
|
-
value: data
|
|
946
|
+
value: ownProp(data, child.name),
|
|
876
947
|
onChange: (v) => setChild(child.name, v),
|
|
877
948
|
id: `${id}.${child.name}`,
|
|
878
949
|
env
|
|
@@ -899,13 +970,19 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
899
970
|
const presentation = useFieldPresentation();
|
|
900
971
|
const isApp = presentation === "app";
|
|
901
972
|
const items = value ?? [];
|
|
973
|
+
const [rowKeys, setRowKeys] = useRowKeys(items.length);
|
|
902
974
|
const setItem = (index, itemValue) => {
|
|
903
975
|
const next = items.slice();
|
|
904
976
|
next[index] = itemValue;
|
|
905
977
|
onChange(next);
|
|
906
978
|
};
|
|
907
|
-
const removeItem = (index) =>
|
|
979
|
+
const removeItem = (index) => {
|
|
980
|
+
setRowKeys((prev) => prev.filter((_, i) => i !== index));
|
|
981
|
+
onChange(items.filter((_, i) => i !== index));
|
|
982
|
+
};
|
|
908
983
|
const addItem = () => onChange([...items, emptyValue(field.item)]);
|
|
984
|
+
const busy = listIsBusy(id, env?.uploadingIds);
|
|
985
|
+
const isFull = field.maxItemCount !== void 0 && items.length >= field.maxItemCount;
|
|
909
986
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
910
987
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [
|
|
911
988
|
/* @__PURE__ */ jsx(
|
|
@@ -919,7 +996,7 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
919
996
|
}
|
|
920
997
|
),
|
|
921
998
|
!isApp && /* @__PURE__ */ jsx(ConceptPill, { conceptRef: field.conceptRef, category: "list" }),
|
|
922
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: s.itemsCount(items.length) }),
|
|
999
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10.5px] text-muted-foreground", children: field.itemCount !== void 0 && field.maxItemCount === field.itemCount ? s.itemsCountOf(items.length, field.itemCount) : s.itemsCount(items.length) }),
|
|
923
1000
|
!field.required && /* @__PURE__ */ jsx("span", { className: "ml-auto font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: s.optionalBadge })
|
|
924
1001
|
] }),
|
|
925
1002
|
field.description && /* @__PURE__ */ jsx("p", { className: "text-[12px] leading-relaxed text-muted-foreground", children: field.description }),
|
|
@@ -946,7 +1023,7 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
946
1023
|
{
|
|
947
1024
|
type: "button",
|
|
948
1025
|
onClick: () => removeItem(index),
|
|
949
|
-
disabled: env?.disabled,
|
|
1026
|
+
disabled: env?.disabled || busy,
|
|
950
1027
|
"aria-label": s.removeItemAria(index + 1),
|
|
951
1028
|
className: "mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-destructive disabled:opacity-50",
|
|
952
1029
|
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
@@ -954,14 +1031,14 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
954
1031
|
)
|
|
955
1032
|
]
|
|
956
1033
|
},
|
|
957
|
-
index
|
|
1034
|
+
rowKeys[index] ?? index
|
|
958
1035
|
)),
|
|
959
1036
|
/* @__PURE__ */ jsxs(
|
|
960
1037
|
"button",
|
|
961
1038
|
{
|
|
962
1039
|
type: "button",
|
|
963
1040
|
onClick: addItem,
|
|
964
|
-
disabled: env?.disabled,
|
|
1041
|
+
disabled: env?.disabled || isFull,
|
|
965
1042
|
className: cn(
|
|
966
1043
|
"inline-flex h-9 w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-border text-[13px] text-muted-foreground transition-colors",
|
|
967
1044
|
"hover:border-primary/40 hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
|
@@ -976,6 +1053,30 @@ function ListField({ field, value, onChange, id, error, env }) {
|
|
|
976
1053
|
error && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-destructive", role: "alert", children: error })
|
|
977
1054
|
] });
|
|
978
1055
|
}
|
|
1056
|
+
function listIsBusy(id, uploadingIds) {
|
|
1057
|
+
if (!uploadingIds?.size) return false;
|
|
1058
|
+
const prefix = `${id}.`;
|
|
1059
|
+
for (const uploading of uploadingIds) if (uploading.startsWith(prefix)) return true;
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
function useRowKeys(length) {
|
|
1063
|
+
const [state, setState] = useState(() => ({
|
|
1064
|
+
keys: Array.from({ length }, (_, i) => i),
|
|
1065
|
+
next: length
|
|
1066
|
+
}));
|
|
1067
|
+
if (state.keys.length !== length) {
|
|
1068
|
+
setState((prev) => {
|
|
1069
|
+
if (length <= prev.keys.length) return { keys: prev.keys.slice(0, length), next: prev.next };
|
|
1070
|
+
const added = length - prev.keys.length;
|
|
1071
|
+
return {
|
|
1072
|
+
keys: [...prev.keys, ...Array.from({ length: added }, (_, i) => prev.next + i)],
|
|
1073
|
+
next: prev.next + added
|
|
1074
|
+
};
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
const update = (fn) => setState((prev) => ({ keys: fn(prev.keys), next: prev.next }));
|
|
1078
|
+
return [state.keys, update];
|
|
1079
|
+
}
|
|
979
1080
|
function emptyValue(field) {
|
|
980
1081
|
switch (field.kind) {
|
|
981
1082
|
case "boolean":
|
|
@@ -993,6 +1094,7 @@ function emptyValue(field) {
|
|
|
993
1094
|
}
|
|
994
1095
|
function UnknownField({ field, value, onChange, id, error, disabled }) {
|
|
995
1096
|
const s = useFieldStrings();
|
|
1097
|
+
const domId = useFieldDomId(id);
|
|
996
1098
|
return /* @__PURE__ */ jsx(
|
|
997
1099
|
FieldShell,
|
|
998
1100
|
{
|
|
@@ -1003,11 +1105,11 @@ function UnknownField({ field, value, onChange, id, error, disabled }) {
|
|
|
1003
1105
|
description: field.description ?? s.jsonHint,
|
|
1004
1106
|
required: field.required,
|
|
1005
1107
|
error,
|
|
1006
|
-
htmlFor:
|
|
1108
|
+
htmlFor: domId,
|
|
1007
1109
|
children: /* @__PURE__ */ jsx(
|
|
1008
1110
|
"textarea",
|
|
1009
1111
|
{
|
|
1010
|
-
id,
|
|
1112
|
+
id: domId,
|
|
1011
1113
|
value: value ?? "",
|
|
1012
1114
|
disabled,
|
|
1013
1115
|
"aria-invalid": !!error,
|
|
@@ -1164,6 +1266,6 @@ function FieldRenderer({ field, value, onChange, id, error, env }) {
|
|
|
1164
1266
|
}
|
|
1165
1267
|
}
|
|
1166
1268
|
|
|
1167
|
-
export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, ImageField, ListField, NumberField, ObjectField, OptionalToggle, ProseField, TextField, UnknownField, fieldControlClass, humanizeFieldName, useFieldPresentation, useFieldStrings };
|
|
1269
|
+
export { BooleanField, ConceptPill, DEFAULT_FIELD_STRINGS, DateField, DocumentField, EnumField, FieldDomIdProvider, FieldPresentationProvider, FieldRenderer, FieldShell, FieldStringsProvider, ImageField, ListField, NumberField, ObjectField, OptionalToggle, ProseField, TextField, UnknownField, fieldControlClass, humanizeFieldName, useFieldDomId, useFieldPresentation, useFieldStrings };
|
|
1168
1270
|
//# sourceMappingURL=index.js.map
|
|
1169
1271
|
//# sourceMappingURL=index.js.map
|