@formancy/react 0.5.0 → 0.7.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.d.mts +31 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +107 -52
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ComponentType, ReactElement, ReactNode } from "react";
|
|
2
2
|
import { ControlProps, FieldSnapshot, FormEngine, Path } from "@formancy/core";
|
|
3
3
|
import { FormText, FormText as FormText$1, FormWordId, FormWordValues, FormWords, FormWordsByLocale, FormWordsByLocale as FormWordsByLocale$1 } from "@formancy/core/words";
|
|
4
|
-
import { FieldType, RemoteOption, RichCommand } from "@formancy/spec";
|
|
4
|
+
import { FieldType, RemoteOption, RemoteOptionsAnswer, RemoteOptionsAnswer as RemoteOptionsAnswer$1, RemoteOptionsPage, RichCommand } from "@formancy/spec";
|
|
5
5
|
//#region src/context.d.ts
|
|
6
6
|
export declare function FormancyProvider({ engine, words, children }: {
|
|
7
7
|
engine: FormEngine;
|
|
@@ -58,6 +58,10 @@ interface RepeaterBinding {
|
|
|
58
58
|
rowCount: number;
|
|
59
59
|
/** Stable per-row keys, in row order. Never key a row by its index. */
|
|
60
60
|
rowIds: readonly string[];
|
|
61
|
+
/** Whether the engine would add a row: under the repeater's `maxItems`, or unbounded. */
|
|
62
|
+
canAdd: boolean;
|
|
63
|
+
/** Whether the engine would remove a row: above the repeater's `minItems`. */
|
|
64
|
+
canRemove: boolean;
|
|
61
65
|
addRow(): void;
|
|
62
66
|
removeRow(index: number): void;
|
|
63
67
|
/** Move a row. The keyboard route to reordering; a drag is a second route to this. */
|
|
@@ -246,7 +250,13 @@ interface OptionsRequest {
|
|
|
246
250
|
* shows an opaque identifier. So the source answers in the right language instead.
|
|
247
251
|
*/
|
|
248
252
|
locale: string;
|
|
249
|
-
/**
|
|
253
|
+
/**
|
|
254
|
+
* How many rows the control can show. A hint: the control caps what arrives anyway.
|
|
255
|
+
*
|
|
256
|
+
* A source that pages reads `limit + 1` rows to know whether there are more, answers a
|
|
257
|
+
* page of `limit`, and never counts the rest: a count reads every matching row on every
|
|
258
|
+
* keystroke, which is the scan paging exists to avoid.
|
|
259
|
+
*/
|
|
250
260
|
limit: number;
|
|
251
261
|
/** Aborted when the answer stops being wanted — a newer keystroke, or an unmount. */
|
|
252
262
|
signal: AbortSignal;
|
|
@@ -261,10 +271,26 @@ interface OptionsRequest {
|
|
|
261
271
|
* surface on a self-hosted instance.
|
|
262
272
|
*/
|
|
263
273
|
interface OptionsSource {
|
|
264
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Every matching row, as a list — exactly as before a page could be answered, its true
|
|
276
|
+
* total included — or one page of them: `{ options, more, withheld? }`.
|
|
277
|
+
*
|
|
278
|
+
* `more` says rows match beyond these, and the control says so without a number, since
|
|
279
|
+
* nobody counted one. `withheld` counts rows the source matched and cannot hand over — a
|
|
280
|
+
* value a select could not store — because a source withholds and counts rather than
|
|
281
|
+
* filtering silently. A page with any other key, a `more` that is not a boolean or a
|
|
282
|
+
* `withheld` that is not a whole count is refused whole, and the field says its source
|
|
283
|
+
* failed rather than showing a list that looks complete
|
|
284
|
+
* ([0177](../../../docs/decisions/0177-a-source-may-say-its-list-goes-on.md)).
|
|
285
|
+
*/
|
|
286
|
+
resolve(request: OptionsRequest): Promise<RemoteOptionsAnswer$1>;
|
|
265
287
|
/** How long to wait after a keystroke before asking. The control has a default. */
|
|
266
288
|
debounceMs?: number;
|
|
267
|
-
/**
|
|
289
|
+
/**
|
|
290
|
+
* Below this many characters, do not ask at all. The control says so on screen: a
|
|
291
|
+
* typeahead how many to type, and a plain select — whose query is always empty, so it
|
|
292
|
+
* never asks — that its answers are found by searching.
|
|
293
|
+
*/
|
|
268
294
|
minQueryLength?: number;
|
|
269
295
|
/** How many rows to show at once. */
|
|
270
296
|
maxRows?: number;
|
|
@@ -509,5 +535,5 @@ interface ResumeNoticeProps {
|
|
|
509
535
|
}
|
|
510
536
|
export declare function ResumeNotice({ migration, labels }: ResumeNoticeProps): ReactElement | null;
|
|
511
537
|
//#endregion
|
|
512
|
-
export type { ErrorSummaryProps, FieldBinding, FieldComponent, FieldComponentProps, FormText, FormWordId, FormWordValues, FormWords, FormWordsByLocale, FormancyFormProps, OptionsRequest, OptionsSource, OptionsSources, Registry, RepeaterBinding, ResumeMigration, ResumeNoticeProps, RichTextEditorFactory, RichTextEditorHandle, RichTextEditorMount, ScanRequest, Scanner, StoredFile, SubmitOutcome, UploadOptions, Uploader, WizardBinding };
|
|
538
|
+
export type { ErrorSummaryProps, FieldBinding, FieldComponent, FieldComponentProps, FormText, FormWordId, FormWordValues, FormWords, FormWordsByLocale, FormancyFormProps, OptionsRequest, OptionsSource, OptionsSources, Registry, RemoteOption, RemoteOptionsAnswer, RemoteOptionsPage, RepeaterBinding, ResumeMigration, ResumeNoticeProps, RichTextEditorFactory, RichTextEditorHandle, RichTextEditorMount, ScanRequest, Scanner, StoredFile, SubmitOutcome, UploadOptions, Uploader, WizardBinding };
|
|
513
539
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/context.tsx","../src/use-field.ts","../src/use-repeater.ts","../src/use-wizard.ts","../src/use-submit.ts","../src/fields/internals.tsx","../src/form.tsx","../src/error-summary.tsx","../src/rich-text.tsx","../src/options-source.ts","../src/scanning.ts","../src/uploads.ts","../src/rich-text-editor.ts","../src/resume-notice.tsx"],"mappings":";;;;;wBAsBgB,mBACd,QACA,OACA;EAEA,QAAQ;;;;;;;EAOR,QAAQ;EACR,WAAW;oBACZ,IAAA;wBAsBe,iBAAiB;;;;;;;;;wBAkBjB,eAAe;;;UCvEd,qBAAqB;EACpC,SAAS;EACT;;EAEA,cAAc;;EAEd;IAAc;IAAY;;;EAE1B;IAAc;;;;;;;;;;;;;wBAaA,SAAS,eAAe,OAAO;;;UCrB9B;EACf;;EAEA;EACA;EACA,UAAU;;EAEV,QAAQ,cAAc;;;;;;;wBAQR,YAAY,eAAe,OAAO;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/context.tsx","../src/use-field.ts","../src/use-repeater.ts","../src/use-wizard.ts","../src/use-submit.ts","../src/fields/internals.tsx","../src/form.tsx","../src/error-summary.tsx","../src/rich-text.tsx","../src/options-source.ts","../src/scanning.ts","../src/uploads.ts","../src/rich-text-editor.ts","../src/resume-notice.tsx"],"mappings":";;;;;wBAsBgB,mBACd,QACA,OACA;EAEA,QAAQ;;;;;;;EAOR,QAAQ;EACR,WAAW;oBACZ,IAAA;wBAsBe,iBAAiB;;;;;;;;;wBAkBjB,eAAe;;;UCvEd,qBAAqB;EACpC,SAAS;EACT;;EAEA,cAAc;;EAEd;IAAc;IAAY;;;EAE1B;IAAc;;;;;;;;;;;;;wBAaA,SAAS,eAAe,OAAO;;;UCrB9B;EACf;;EAEA;;EAEA;;EAEA;EACA;EACA,UAAU;;EAEV,QAAQ,cAAc;;;;;;;wBAQR,YAAY,eAAe,OAAO;;;UCrBjC;EACf;EACA;;;EAGA;EACA;;EAEA,QAAQ;EACR;;EAEA,KAAK;;;;;;;wBAQS,aAAa;;;;;;;;;;;wBCTb;EAAqB;EAAa,QAAQ;;;;;;;;;;;;;;;;;UCQzC;EACf;EACA;;KAGU,iBAAiB,cAAc;UAE1B;EACf,SAAS,QAAQ,OAAO,WAAW;EACnC,SAAS,eAAe;;UAGT;EACf;EACA,QAAQ;;EAER;;UAGe;;;;;;;EAOf,SAAS;EACT,WAAW;;;;;;;EAOX;EACA;EACA,YAAY,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;wBCCP,aAAa,OAAO,oCAAiB,IAAA;;;UCrDpC;EACf,SAAS;;;;;;;;;;;wBAYK,eAAe,UAAU,oCAAiB,IAAA;;;;;;;;;;;;;;;;;;;;wBCI1C,WAAW;EAAY;IAAmB;;;;;;;;;;UCZzC;;;;;EAKf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;;EASA;;;;;;;;EAQA;;EAEA,QAAQ;;;;;;;;;;;UAYO;;;;;;;;;;;;;EAaf,QAAQ,SAAS,iBAAiB,QAAQ;;EAE1C;;;;;;EAMA;;EAEA;;;;;;;;;;;;;KAcU,iBAAiB,SAAS,eAAe;qBAIxC,wCAAsB,SAAA,SAAA,eAAA;;;;;;;;;wBAUnB,qBAAqB;;;;;;;;;;;;UC9FpB;;EAEf;;EAEA;;;;;;;;;;;;;;;;;;;KAoBU,WAAW,SAAS,gBAAgB;qBAInC,iCAAe,SAAA;;;;;;;;wBASZ,cAAc;;;;;;;;;;;;;UCpCb;;EAEf;EACA;EACA;EACA;;EAEA;;;;;;;;;;UAWe;;;;;EAKf;;EAEA,QAAQ;;EAER,WAAW,cAAc;;;;;;;;;KAUf,YAAY,MAAM,MAAM,SAAS,kBAAkB,QAAQ;qBAI1D,kCAAgB,SAAA;;;;;;;;;wBAUb,eAAe;;;;;;;;;;;;;;;;;;;;;UC1Cd;;EAEf;;EAEA,WAAW;;;;;;;;;;;;;EAaX,MAAM,SAAS,aAAa;;EAE5B,WAAW,SAAS;EACpB;;UAGe;;WAEN,SAAS;;WAET;;WAEA,WAAW;WACX;;;;;;;;;;WAUA,YAAY,SAAS;;KAGpB,yBAAyB,OAAO,wBAAwB;qBAIvD,wCAAsB,SAAA;;;;;;;;;wBAUnB,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UC9C3B;WACN;WACA,SAAS;aAAyB;aAAuB;;;UAGnD;;WAEN,YAAY;;WAEZ,SAAS,SAAS;;wBAGb,eAAe,WAAW,UAAU,oBAAoB"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment, createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
2
|
import { cancelUploads, fieldUploads, formatPath, parsePath, placedGroup, placedPage, thumbnailSize } from "@formancy/core";
|
|
3
|
-
import { createFormText } from "@formancy/core/words";
|
|
4
|
-
import { LAYOUT_LEAF_KINDS,
|
|
3
|
+
import { createFormText, optionsStatus } from "@formancy/core/words";
|
|
4
|
+
import { LAYOUT_LEAF_KINDS, acceptRemoteAnswer, answerFromText, applyRichCommand, capRemoteAnswer, datagridColumns, editMasked, formatMasked, isImageSource, layoutChildren, layoutNodeShows, maskIsNumeric, maskPlaceholder, narrowOptionsByLabel, parseRichText, resolveText, resolvedLocale } from "@formancy/spec";
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { flushSync } from "react-dom";
|
|
7
7
|
import { encode } from "uqr";
|
|
@@ -103,6 +103,10 @@ function useRepeater(path) {
|
|
|
103
103
|
const subscribe = useCallback((onChange) => engine.subscribeField(parsed, onChange), [engine, parsed]);
|
|
104
104
|
const getRowCount = useCallback(() => engine.rowCount(parsed), [engine, parsed]);
|
|
105
105
|
const rowCount = useSyncExternalStore(subscribe, getRowCount, getRowCount);
|
|
106
|
+
const getCanAdd = useCallback(() => engine.canAddRow(parsed), [engine, parsed]);
|
|
107
|
+
const canAdd = useSyncExternalStore(subscribe, getCanAdd, getCanAdd);
|
|
108
|
+
const getCanRemove = useCallback(() => engine.canRemoveRow(parsed), [engine, parsed]);
|
|
109
|
+
const canRemove = useSyncExternalStore(subscribe, getCanRemove, getCanRemove);
|
|
106
110
|
const addRow = useCallback(() => engine.addRow(parsed), [engine, parsed]);
|
|
107
111
|
const removeRow = useCallback((index) => engine.removeRow(parsed, index), [engine, parsed]);
|
|
108
112
|
const moveRow = useCallback((from, to) => engine.moveRow(parsed, from, to), [engine, parsed]);
|
|
@@ -112,12 +116,16 @@ function useRepeater(path) {
|
|
|
112
116
|
return useMemo(() => ({
|
|
113
117
|
rowCount,
|
|
114
118
|
rowIds,
|
|
119
|
+
canAdd,
|
|
120
|
+
canRemove,
|
|
115
121
|
addRow,
|
|
116
122
|
removeRow,
|
|
117
123
|
moveRow
|
|
118
124
|
}), [
|
|
119
125
|
rowCount,
|
|
120
126
|
rowIds,
|
|
127
|
+
canAdd,
|
|
128
|
+
canRemove,
|
|
121
129
|
addRow,
|
|
122
130
|
removeRow,
|
|
123
131
|
moveRow
|
|
@@ -547,9 +555,13 @@ function useOptionsSources() {
|
|
|
547
555
|
const DEBOUNCE_MS = 250;
|
|
548
556
|
const MIN_QUERY = 0;
|
|
549
557
|
const MAX_ROWS = 50;
|
|
558
|
+
/**
|
|
559
|
+
* Nothing left out, as one value: a reset that stores it again is the same state, so
|
|
560
|
+
* React bails out of the render rather than repeating one.
|
|
561
|
+
*/
|
|
562
|
+
const COMPLETE = { kind: "complete" };
|
|
550
563
|
function useSourcedOptions(field, query, enabled = true) {
|
|
551
564
|
const engine = useFormEngine();
|
|
552
|
-
const text = useFormText();
|
|
553
565
|
const sources = useOptionsSources();
|
|
554
566
|
const name = field.def.optionsSource;
|
|
555
567
|
const source = name === void 0 ? void 0 : sources?.[name];
|
|
@@ -557,19 +569,28 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
557
569
|
const [named, setNamed] = useState(/* @__PURE__ */ new Map());
|
|
558
570
|
const [busy, setBusy] = useState(false);
|
|
559
571
|
const [failed, setFailed] = useState(false);
|
|
560
|
-
const [
|
|
572
|
+
const [completeness, setCompleteness] = useState(COMPLETE);
|
|
573
|
+
const [attempt, setAttempt] = useState(0);
|
|
574
|
+
const retry = useCallback(() => setAttempt((was) => was + 1), []);
|
|
561
575
|
const debounceMs = source?.debounceMs ?? DEBOUNCE_MS;
|
|
562
576
|
const minQueryLength = source?.minQueryLength ?? MIN_QUERY;
|
|
563
577
|
const maxRows = source?.maxRows ?? MAX_ROWS;
|
|
564
578
|
const stored = typeof field.value === "string" && field.value !== "" ? field.value : void 0;
|
|
565
579
|
const path = field.path;
|
|
580
|
+
const disabled = field.disabled;
|
|
566
581
|
const asked = useRef(0);
|
|
567
582
|
useEffect(() => {
|
|
568
|
-
if (!enabled || source === void 0) return;
|
|
569
583
|
const generation = asked.current += 1;
|
|
584
|
+
if (!enabled || source === void 0) return;
|
|
585
|
+
if (disabled) {
|
|
586
|
+
setBusy(false);
|
|
587
|
+
setFailed(false);
|
|
588
|
+
setCompleteness(COMPLETE);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
570
591
|
if (query.trim().length < minQueryLength) {
|
|
571
592
|
setRows([]);
|
|
572
|
-
|
|
593
|
+
setCompleteness(COMPLETE);
|
|
573
594
|
setBusy(false);
|
|
574
595
|
return;
|
|
575
596
|
}
|
|
@@ -588,23 +609,21 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
588
609
|
signal: controller.signal
|
|
589
610
|
}).then((answer) => {
|
|
590
611
|
if (generation !== asked.current) return;
|
|
591
|
-
const accepted =
|
|
612
|
+
const accepted = acceptRemoteAnswer(answer);
|
|
592
613
|
if (accepted === void 0) {
|
|
593
614
|
setFailed(true);
|
|
594
615
|
setRows([]);
|
|
595
|
-
|
|
616
|
+
setCompleteness(COMPLETE);
|
|
596
617
|
return;
|
|
597
618
|
}
|
|
598
|
-
const
|
|
599
|
-
setRows(shown);
|
|
600
|
-
|
|
601
|
-
shown: shown.length,
|
|
602
|
-
total
|
|
603
|
-
} : null);
|
|
619
|
+
const settled = capRemoteAnswer(accepted, maxRows);
|
|
620
|
+
setRows(settled.shown);
|
|
621
|
+
setCompleteness(settled.completeness);
|
|
604
622
|
}).catch(() => {
|
|
605
623
|
if (generation !== asked.current) return;
|
|
606
624
|
setFailed(true);
|
|
607
625
|
setRows([]);
|
|
626
|
+
setCompleteness(COMPLETE);
|
|
608
627
|
}).finally(() => {
|
|
609
628
|
if (generation === asked.current) setBusy(false);
|
|
610
629
|
});
|
|
@@ -615,6 +634,7 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
615
634
|
};
|
|
616
635
|
}, [
|
|
617
636
|
enabled,
|
|
637
|
+
disabled,
|
|
618
638
|
source,
|
|
619
639
|
name,
|
|
620
640
|
path,
|
|
@@ -622,7 +642,8 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
622
642
|
minQueryLength,
|
|
623
643
|
maxRows,
|
|
624
644
|
debounceMs,
|
|
625
|
-
engine
|
|
645
|
+
engine,
|
|
646
|
+
attempt
|
|
626
647
|
]);
|
|
627
648
|
const askedFor = useRef(/* @__PURE__ */ new Set());
|
|
628
649
|
useEffect(() => {
|
|
@@ -640,11 +661,11 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
640
661
|
limit: 1,
|
|
641
662
|
signal: controller.signal
|
|
642
663
|
}).then((answer) => {
|
|
643
|
-
const accepted =
|
|
664
|
+
const accepted = acceptRemoteAnswer(answer);
|
|
644
665
|
if (accepted === void 0) return;
|
|
645
666
|
setNamed((was) => {
|
|
646
667
|
const next = new Map(was);
|
|
647
|
-
for (const row of accepted) next.set(row.value, row.label);
|
|
668
|
+
for (const row of accepted.options) next.set(row.value, row.label);
|
|
648
669
|
return next;
|
|
649
670
|
});
|
|
650
671
|
}).catch(() => void 0);
|
|
@@ -677,33 +698,11 @@ function useSourcedOptions(field, query, enabled = true) {
|
|
|
677
698
|
busy,
|
|
678
699
|
failed,
|
|
679
700
|
needs: minQueryLength,
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
busy,
|
|
683
|
-
needs: minQueryLength,
|
|
684
|
-
query,
|
|
685
|
-
capped
|
|
686
|
-
})
|
|
701
|
+
completeness,
|
|
702
|
+
retry
|
|
687
703
|
}
|
|
688
704
|
};
|
|
689
705
|
}
|
|
690
|
-
/**
|
|
691
|
-
* The one thing the field says out loud, and never more than one.
|
|
692
|
-
*
|
|
693
|
-
* Success announces nothing: the listbox and `aria-activedescendant` are the
|
|
694
|
-
* feedback, and a region that spoke on every successful keystroke would talk over
|
|
695
|
-
* them. A failure is a sentence somebody can act on rather than a code.
|
|
696
|
-
*/
|
|
697
|
-
function statusFor(text, { failed, busy, needs, query, capped }) {
|
|
698
|
-
if (failed) return text("options.failed");
|
|
699
|
-
if (query.trim().length < needs) return text("options.tooShort", { count: needs });
|
|
700
|
-
if (busy) return text("options.searching");
|
|
701
|
-
if (capped !== null) return text("options.capped", {
|
|
702
|
-
shown: capped.shown,
|
|
703
|
-
total: capped.total
|
|
704
|
-
});
|
|
705
|
-
return "";
|
|
706
|
-
}
|
|
707
706
|
//#endregion
|
|
708
707
|
//#region src/uploads.ts
|
|
709
708
|
const UploaderContext = createContext(void 0);
|
|
@@ -784,11 +783,11 @@ function FieldShell({ path, field, label, children }) {
|
|
|
784
783
|
* Not the text: a theme that keyed off English prose would break in every other
|
|
785
784
|
* language the form is offered in.
|
|
786
785
|
*/
|
|
787
|
-
function statusState({ sourced, open, matches }) {
|
|
786
|
+
function statusState({ sourced, said, open, matches }) {
|
|
788
787
|
if (sourced.remote === null) return open && matches === 0 ? "empty" : void 0;
|
|
789
788
|
if (sourced.remote.busy) return "busy";
|
|
790
789
|
if (sourced.remote.failed) return "failed";
|
|
791
|
-
if (
|
|
790
|
+
if (said !== "") return "hint";
|
|
792
791
|
return open && matches === 0 ? "empty" : void 0;
|
|
793
792
|
}
|
|
794
793
|
/**
|
|
@@ -867,7 +866,11 @@ function TypeaheadSelectField({ path, label, field }) {
|
|
|
867
866
|
* first clears the answer on the way out.
|
|
868
867
|
*/
|
|
869
868
|
const [query, setQuery] = useState(null);
|
|
870
|
-
const [
|
|
869
|
+
const [opened, setOpen] = useState(false);
|
|
870
|
+
/** A field disabled by a rule while its list was showing has no list: nobody can
|
|
871
|
+
* choose from it, and its region would say "no options match" under a read-only
|
|
872
|
+
* answer. */
|
|
873
|
+
const open = opened && !field.disabled;
|
|
871
874
|
/** The arrowed-over option by VALUE, not by index: the filtered list changes on
|
|
872
875
|
* every keystroke and an index would point at a different row after one. */
|
|
873
876
|
const [activeValue, setActiveValue] = useState(null);
|
|
@@ -876,6 +879,16 @@ function TypeaheadSelectField({ path, label, field }) {
|
|
|
876
879
|
path
|
|
877
880
|
}, query ?? "");
|
|
878
881
|
const options = sourced.options;
|
|
882
|
+
const said = sourced.remote === null ? "" : optionsStatus(text, {
|
|
883
|
+
control: "typeahead",
|
|
884
|
+
open,
|
|
885
|
+
disabled: field.disabled,
|
|
886
|
+
failed: sourced.remote.failed,
|
|
887
|
+
busy: sourced.remote.busy,
|
|
888
|
+
needs: sourced.remote.needs,
|
|
889
|
+
query: query ?? "",
|
|
890
|
+
completeness: sourced.remote.completeness
|
|
891
|
+
});
|
|
879
892
|
const chosen = options.find((option) => option.value === field.value);
|
|
880
893
|
const matches = sourced.remote === null ? narrowOptionsByLabel(options, query ?? "") : options;
|
|
881
894
|
/** Collapsed whenever there is nothing on the screen, so `aria-expanded` never
|
|
@@ -988,14 +1001,16 @@ function TypeaheadSelectField({ path, label, field }) {
|
|
|
988
1001
|
"data-formancy-part": "typeahead-status",
|
|
989
1002
|
...statusState({
|
|
990
1003
|
sourced,
|
|
1004
|
+
said,
|
|
991
1005
|
open,
|
|
992
1006
|
matches: matches.length
|
|
993
1007
|
}) === void 0 ? {} : { "data-state": statusState({
|
|
994
1008
|
sourced,
|
|
1009
|
+
said,
|
|
995
1010
|
open,
|
|
996
1011
|
matches: matches.length
|
|
997
1012
|
}) },
|
|
998
|
-
children:
|
|
1013
|
+
children: said !== "" ? said : open && matches.length === 0 && sourced.remote?.busy !== true ? text("options.noMatch") : ""
|
|
999
1014
|
})]
|
|
1000
1015
|
});
|
|
1001
1016
|
}
|
|
@@ -1031,6 +1046,17 @@ function SelectField({ path, label }) {
|
|
|
1031
1046
|
path
|
|
1032
1047
|
}, "", field.def.widget !== "typeahead");
|
|
1033
1048
|
const options = sourced.options;
|
|
1049
|
+
const remote = sourced.remote;
|
|
1050
|
+
const said = remote === null ? "" : optionsStatus(text, {
|
|
1051
|
+
control: "select",
|
|
1052
|
+
open: true,
|
|
1053
|
+
disabled: field.disabled,
|
|
1054
|
+
failed: remote.failed,
|
|
1055
|
+
busy: remote.busy,
|
|
1056
|
+
needs: remote.needs,
|
|
1057
|
+
query: "",
|
|
1058
|
+
completeness: remote.completeness
|
|
1059
|
+
});
|
|
1034
1060
|
if (sourced.remote?.unavailable === true) return /* @__PURE__ */ jsx(FieldShell, {
|
|
1035
1061
|
path,
|
|
1036
1062
|
field,
|
|
@@ -1053,15 +1079,18 @@ function SelectField({ path, label }) {
|
|
|
1053
1079
|
...field.controlProps,
|
|
1054
1080
|
value: typeof field.value === "string" ? field.value : "",
|
|
1055
1081
|
onChange: (event) => field.setValue(event.target.value === "" ? null : event.target.value),
|
|
1082
|
+
onFocus: () => {
|
|
1083
|
+
if (remote?.failed === true) remote.retry();
|
|
1084
|
+
},
|
|
1056
1085
|
onBlur: () => field.touch(),
|
|
1057
1086
|
children: [/* @__PURE__ */ jsx("option", { value: "" }), options.map((option) => /* @__PURE__ */ jsx("option", {
|
|
1058
1087
|
value: option.value,
|
|
1059
1088
|
children: option.label
|
|
1060
1089
|
}, option.value))]
|
|
1061
|
-
}),
|
|
1090
|
+
}), remote === null ? null : /* @__PURE__ */ jsx("p", {
|
|
1062
1091
|
role: "status",
|
|
1063
1092
|
"data-formancy-part": "select-status",
|
|
1064
|
-
children:
|
|
1093
|
+
children: said
|
|
1065
1094
|
})]
|
|
1066
1095
|
});
|
|
1067
1096
|
}
|
|
@@ -1070,6 +1099,7 @@ function RadioGroupField({ path, label }) {
|
|
|
1070
1099
|
const options = useResolvedOptions(field);
|
|
1071
1100
|
const showError = field.touched && field.errors.length > 0;
|
|
1072
1101
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1102
|
+
id: field.ids.control,
|
|
1073
1103
|
"data-formancy-part": "field",
|
|
1074
1104
|
"data-formancy-field-path": path,
|
|
1075
1105
|
"data-state": showError ? "invalid" : "valid",
|
|
@@ -1700,7 +1730,7 @@ function TagPickerField({ path, label }) {
|
|
|
1700
1730
|
const listboxId = `${field.ids.control}:listbox`;
|
|
1701
1731
|
const remaining = options.filter((option) => !chosen.includes(option.value));
|
|
1702
1732
|
const matches = narrowOptionsByLabel(remaining, query);
|
|
1703
|
-
const expanded = open && matches.length > 0;
|
|
1733
|
+
const expanded = open && !field.disabled && matches.length > 0;
|
|
1704
1734
|
const add = (value) => {
|
|
1705
1735
|
const next = options.map((option) => option.value).filter((candidate) => candidate === value || chosen.includes(candidate));
|
|
1706
1736
|
field.setValue(next);
|
|
@@ -1726,6 +1756,7 @@ function TagPickerField({ path, label }) {
|
|
|
1726
1756
|
type: "button",
|
|
1727
1757
|
"data-formancy-part": "tagpicker-remove",
|
|
1728
1758
|
"aria-label": text("tagpicker.remove", { option: labelFor(value) }),
|
|
1759
|
+
disabled: field.disabled,
|
|
1729
1760
|
onClick: () => remove(value),
|
|
1730
1761
|
children: "×"
|
|
1731
1762
|
})]
|
|
@@ -1792,6 +1823,7 @@ function SelectBoxesField({ path, label }) {
|
|
|
1792
1823
|
field.setValue(next);
|
|
1793
1824
|
};
|
|
1794
1825
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1826
|
+
id: field.ids.control,
|
|
1795
1827
|
"data-formancy-part": "field",
|
|
1796
1828
|
"data-formancy-field-path": path,
|
|
1797
1829
|
"data-state": showError ? "invalid" : "valid",
|
|
@@ -2179,6 +2211,7 @@ function NumberFallback({ path, label }) {
|
|
|
2179
2211
|
children: /* @__PURE__ */ jsx("input", {
|
|
2180
2212
|
type: "number",
|
|
2181
2213
|
...field.controlProps,
|
|
2214
|
+
readOnly: field.readOnly,
|
|
2182
2215
|
value: typeof field.value === "number" ? field.value : "",
|
|
2183
2216
|
onChange: (event) => field.setValue(event.target.value === "" ? null : event.target.valueAsNumber),
|
|
2184
2217
|
onBlur: () => field.touch()
|
|
@@ -2307,6 +2340,7 @@ function SignatureField({ path, label }) {
|
|
|
2307
2340
|
}, at))
|
|
2308
2341
|
}),
|
|
2309
2342
|
/* @__PURE__ */ jsx("input", {
|
|
2343
|
+
id: field.ids.control,
|
|
2310
2344
|
"data-formancy-part": "signature-typed",
|
|
2311
2345
|
type: "text",
|
|
2312
2346
|
value: answer.typed ?? "",
|
|
@@ -2372,6 +2406,7 @@ function DateField({ path, label }) {
|
|
|
2372
2406
|
children: /* @__PURE__ */ jsx("input", {
|
|
2373
2407
|
type: "date",
|
|
2374
2408
|
...field.controlProps,
|
|
2409
|
+
readOnly: field.readOnly,
|
|
2375
2410
|
value: typeof field.value === "string" ? field.value : "",
|
|
2376
2411
|
onChange: (event) => field.setValue(event.target.value === "" ? null : event.target.value),
|
|
2377
2412
|
onBlur: () => field.touch()
|
|
@@ -2400,6 +2435,7 @@ function TimeField({ path, label }) {
|
|
|
2400
2435
|
children: /* @__PURE__ */ jsx("input", {
|
|
2401
2436
|
type: "time",
|
|
2402
2437
|
...field.controlProps,
|
|
2438
|
+
readOnly: field.readOnly,
|
|
2403
2439
|
...typeof field.def.earliest === "string" ? { min: field.def.earliest } : {},
|
|
2404
2440
|
...typeof field.def.latest === "string" ? { max: field.def.latest } : {},
|
|
2405
2441
|
value: typeof field.value === "string" ? field.value : "",
|
|
@@ -2446,6 +2482,7 @@ function DateTimeField({ path, label }) {
|
|
|
2446
2482
|
children: /* @__PURE__ */ jsx("input", {
|
|
2447
2483
|
type: "datetime-local",
|
|
2448
2484
|
...field.controlProps,
|
|
2485
|
+
readOnly: field.readOnly,
|
|
2449
2486
|
value: asLocal(),
|
|
2450
2487
|
onChange: (event) => {
|
|
2451
2488
|
const local = event.target.value;
|
|
@@ -2529,6 +2566,7 @@ function TextField({ path, label }) {
|
|
|
2529
2566
|
children: [/* @__PURE__ */ jsx("input", {
|
|
2530
2567
|
type: "text",
|
|
2531
2568
|
...field.controlProps,
|
|
2569
|
+
readOnly: field.readOnly,
|
|
2532
2570
|
value: shown,
|
|
2533
2571
|
placeholder: mask === void 0 ? void 0 : maskPlaceholder(mask),
|
|
2534
2572
|
inputMode: mask !== void 0 && maskIsNumeric(mask) ? "numeric" : void 0,
|
|
@@ -2579,6 +2617,7 @@ function TextareaField({ path, label }) {
|
|
|
2579
2617
|
label,
|
|
2580
2618
|
children: /* @__PURE__ */ jsx("textarea", {
|
|
2581
2619
|
...field.controlProps,
|
|
2620
|
+
readOnly: field.readOnly,
|
|
2582
2621
|
value: typeof field.value === "string" ? field.value : "",
|
|
2583
2622
|
onChange: (event) => field.setValue(event.target.value),
|
|
2584
2623
|
onBlur: () => field.touch()
|
|
@@ -2772,6 +2811,7 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2772
2811
|
const engine = useFormEngine();
|
|
2773
2812
|
const text = useFormText();
|
|
2774
2813
|
const repeater = useRepeater(wire);
|
|
2814
|
+
const field = useField(wire);
|
|
2775
2815
|
const def = engine.repeaters().find((candidate) => candidate.wire === wire)?.def;
|
|
2776
2816
|
const label = engine.text(def?.label) ?? labels?.[wire] ?? wire;
|
|
2777
2817
|
const addLabel = def?.addLabel ?? text("repeater.add", { label });
|
|
@@ -2823,9 +2863,10 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2823
2863
|
* the consumer ([0004](../../../docs/decisions/0004-headless-core.md)), and a renderer
|
|
2824
2864
|
* that shipped a glyph would be choosing one for every design system at once. */
|
|
2825
2865
|
const rowButtons = (index) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2826
|
-
/* @__PURE__ */ jsx("button", {
|
|
2866
|
+
repeater.canRemove ? /* @__PURE__ */ jsx("button", {
|
|
2827
2867
|
type: "button",
|
|
2828
2868
|
"data-formancy-part": "row-remove",
|
|
2869
|
+
disabled: field.disabled,
|
|
2829
2870
|
onClick: () => repeater.removeRow(index),
|
|
2830
2871
|
children: /* @__PURE__ */ jsx("span", {
|
|
2831
2872
|
"data-formancy-part": "row-action-text",
|
|
@@ -2835,10 +2876,11 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2835
2876
|
count: repeater.rowCount
|
|
2836
2877
|
})
|
|
2837
2878
|
})
|
|
2838
|
-
}),
|
|
2879
|
+
}) : null,
|
|
2839
2880
|
index > 0 ? /* @__PURE__ */ jsx("button", {
|
|
2840
2881
|
type: "button",
|
|
2841
2882
|
"data-formancy-part": "row-up",
|
|
2883
|
+
disabled: field.disabled,
|
|
2842
2884
|
onClick: () => repeater.moveRow(index, index - 1),
|
|
2843
2885
|
children: /* @__PURE__ */ jsx("span", {
|
|
2844
2886
|
"data-formancy-part": "row-action-text",
|
|
@@ -2852,6 +2894,7 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2852
2894
|
index < repeater.rowCount - 1 ? /* @__PURE__ */ jsx("button", {
|
|
2853
2895
|
type: "button",
|
|
2854
2896
|
"data-formancy-part": "row-down",
|
|
2897
|
+
disabled: field.disabled,
|
|
2855
2898
|
onClick: () => repeater.moveRow(index, index + 1),
|
|
2856
2899
|
children: /* @__PURE__ */ jsx("span", {
|
|
2857
2900
|
"data-formancy-part": "row-action-text",
|
|
@@ -2892,14 +2935,20 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2892
2935
|
"data-formancy-part": "row",
|
|
2893
2936
|
children: [engine.fieldPaths().filter((candidate) => candidate.startsWith(`${wire}[${String(index)}]`)).map(slot), rowButtons(index)]
|
|
2894
2937
|
}, rowId));
|
|
2938
|
+
if (!field.visible) return null;
|
|
2939
|
+
const showError = field.touched && field.errors.length > 0;
|
|
2895
2940
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
2896
2941
|
"data-formancy-part": "repeater",
|
|
2897
2942
|
"data-formancy-field-path": wire,
|
|
2943
|
+
"data-state": showError ? "invalid" : "valid",
|
|
2944
|
+
id: field.controlProps.id,
|
|
2945
|
+
"aria-describedby": field.controlProps["aria-describedby"],
|
|
2898
2946
|
children: [
|
|
2899
2947
|
/* @__PURE__ */ jsx("legend", {
|
|
2900
2948
|
"data-formancy-part": "repeater-legend",
|
|
2901
2949
|
children: label
|
|
2902
2950
|
}),
|
|
2951
|
+
/* @__PURE__ */ jsx(RequiredHint, { field }),
|
|
2903
2952
|
grid ? /* @__PURE__ */ jsxs("div", {
|
|
2904
2953
|
"data-formancy-part": "datagrid",
|
|
2905
2954
|
"data-columns": String(plan.length),
|
|
@@ -2913,11 +2962,17 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2913
2962
|
}, entry.key))
|
|
2914
2963
|
}) : null, rows]
|
|
2915
2964
|
}) : rows,
|
|
2916
|
-
/* @__PURE__ */ jsx("
|
|
2965
|
+
showError ? /* @__PURE__ */ jsx("p", {
|
|
2966
|
+
"data-formancy-part": "error",
|
|
2967
|
+
...field.errorProps,
|
|
2968
|
+
children: field.errors.join(", ")
|
|
2969
|
+
}) : null,
|
|
2970
|
+
repeater.canAdd ? /* @__PURE__ */ jsx("button", {
|
|
2917
2971
|
type: "button",
|
|
2972
|
+
disabled: field.disabled,
|
|
2918
2973
|
onClick: () => repeater.addRow(),
|
|
2919
2974
|
children: addLabel
|
|
2920
|
-
})
|
|
2975
|
+
}) : null
|
|
2921
2976
|
]
|
|
2922
2977
|
});
|
|
2923
2978
|
}
|