@formancy/react 0.5.0 → 0.6.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 +104 -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
|
}
|
|
@@ -1700,7 +1729,7 @@ function TagPickerField({ path, label }) {
|
|
|
1700
1729
|
const listboxId = `${field.ids.control}:listbox`;
|
|
1701
1730
|
const remaining = options.filter((option) => !chosen.includes(option.value));
|
|
1702
1731
|
const matches = narrowOptionsByLabel(remaining, query);
|
|
1703
|
-
const expanded = open && matches.length > 0;
|
|
1732
|
+
const expanded = open && !field.disabled && matches.length > 0;
|
|
1704
1733
|
const add = (value) => {
|
|
1705
1734
|
const next = options.map((option) => option.value).filter((candidate) => candidate === value || chosen.includes(candidate));
|
|
1706
1735
|
field.setValue(next);
|
|
@@ -1726,6 +1755,7 @@ function TagPickerField({ path, label }) {
|
|
|
1726
1755
|
type: "button",
|
|
1727
1756
|
"data-formancy-part": "tagpicker-remove",
|
|
1728
1757
|
"aria-label": text("tagpicker.remove", { option: labelFor(value) }),
|
|
1758
|
+
disabled: field.disabled,
|
|
1729
1759
|
onClick: () => remove(value),
|
|
1730
1760
|
children: "×"
|
|
1731
1761
|
})]
|
|
@@ -2179,6 +2209,7 @@ function NumberFallback({ path, label }) {
|
|
|
2179
2209
|
children: /* @__PURE__ */ jsx("input", {
|
|
2180
2210
|
type: "number",
|
|
2181
2211
|
...field.controlProps,
|
|
2212
|
+
readOnly: field.readOnly,
|
|
2182
2213
|
value: typeof field.value === "number" ? field.value : "",
|
|
2183
2214
|
onChange: (event) => field.setValue(event.target.value === "" ? null : event.target.valueAsNumber),
|
|
2184
2215
|
onBlur: () => field.touch()
|
|
@@ -2372,6 +2403,7 @@ function DateField({ path, label }) {
|
|
|
2372
2403
|
children: /* @__PURE__ */ jsx("input", {
|
|
2373
2404
|
type: "date",
|
|
2374
2405
|
...field.controlProps,
|
|
2406
|
+
readOnly: field.readOnly,
|
|
2375
2407
|
value: typeof field.value === "string" ? field.value : "",
|
|
2376
2408
|
onChange: (event) => field.setValue(event.target.value === "" ? null : event.target.value),
|
|
2377
2409
|
onBlur: () => field.touch()
|
|
@@ -2400,6 +2432,7 @@ function TimeField({ path, label }) {
|
|
|
2400
2432
|
children: /* @__PURE__ */ jsx("input", {
|
|
2401
2433
|
type: "time",
|
|
2402
2434
|
...field.controlProps,
|
|
2435
|
+
readOnly: field.readOnly,
|
|
2403
2436
|
...typeof field.def.earliest === "string" ? { min: field.def.earliest } : {},
|
|
2404
2437
|
...typeof field.def.latest === "string" ? { max: field.def.latest } : {},
|
|
2405
2438
|
value: typeof field.value === "string" ? field.value : "",
|
|
@@ -2446,6 +2479,7 @@ function DateTimeField({ path, label }) {
|
|
|
2446
2479
|
children: /* @__PURE__ */ jsx("input", {
|
|
2447
2480
|
type: "datetime-local",
|
|
2448
2481
|
...field.controlProps,
|
|
2482
|
+
readOnly: field.readOnly,
|
|
2449
2483
|
value: asLocal(),
|
|
2450
2484
|
onChange: (event) => {
|
|
2451
2485
|
const local = event.target.value;
|
|
@@ -2529,6 +2563,7 @@ function TextField({ path, label }) {
|
|
|
2529
2563
|
children: [/* @__PURE__ */ jsx("input", {
|
|
2530
2564
|
type: "text",
|
|
2531
2565
|
...field.controlProps,
|
|
2566
|
+
readOnly: field.readOnly,
|
|
2532
2567
|
value: shown,
|
|
2533
2568
|
placeholder: mask === void 0 ? void 0 : maskPlaceholder(mask),
|
|
2534
2569
|
inputMode: mask !== void 0 && maskIsNumeric(mask) ? "numeric" : void 0,
|
|
@@ -2579,6 +2614,7 @@ function TextareaField({ path, label }) {
|
|
|
2579
2614
|
label,
|
|
2580
2615
|
children: /* @__PURE__ */ jsx("textarea", {
|
|
2581
2616
|
...field.controlProps,
|
|
2617
|
+
readOnly: field.readOnly,
|
|
2582
2618
|
value: typeof field.value === "string" ? field.value : "",
|
|
2583
2619
|
onChange: (event) => field.setValue(event.target.value),
|
|
2584
2620
|
onBlur: () => field.touch()
|
|
@@ -2772,6 +2808,7 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2772
2808
|
const engine = useFormEngine();
|
|
2773
2809
|
const text = useFormText();
|
|
2774
2810
|
const repeater = useRepeater(wire);
|
|
2811
|
+
const field = useField(wire);
|
|
2775
2812
|
const def = engine.repeaters().find((candidate) => candidate.wire === wire)?.def;
|
|
2776
2813
|
const label = engine.text(def?.label) ?? labels?.[wire] ?? wire;
|
|
2777
2814
|
const addLabel = def?.addLabel ?? text("repeater.add", { label });
|
|
@@ -2823,9 +2860,10 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2823
2860
|
* the consumer ([0004](../../../docs/decisions/0004-headless-core.md)), and a renderer
|
|
2824
2861
|
* that shipped a glyph would be choosing one for every design system at once. */
|
|
2825
2862
|
const rowButtons = (index) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2826
|
-
/* @__PURE__ */ jsx("button", {
|
|
2863
|
+
repeater.canRemove ? /* @__PURE__ */ jsx("button", {
|
|
2827
2864
|
type: "button",
|
|
2828
2865
|
"data-formancy-part": "row-remove",
|
|
2866
|
+
disabled: field.disabled,
|
|
2829
2867
|
onClick: () => repeater.removeRow(index),
|
|
2830
2868
|
children: /* @__PURE__ */ jsx("span", {
|
|
2831
2869
|
"data-formancy-part": "row-action-text",
|
|
@@ -2835,10 +2873,11 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2835
2873
|
count: repeater.rowCount
|
|
2836
2874
|
})
|
|
2837
2875
|
})
|
|
2838
|
-
}),
|
|
2876
|
+
}) : null,
|
|
2839
2877
|
index > 0 ? /* @__PURE__ */ jsx("button", {
|
|
2840
2878
|
type: "button",
|
|
2841
2879
|
"data-formancy-part": "row-up",
|
|
2880
|
+
disabled: field.disabled,
|
|
2842
2881
|
onClick: () => repeater.moveRow(index, index - 1),
|
|
2843
2882
|
children: /* @__PURE__ */ jsx("span", {
|
|
2844
2883
|
"data-formancy-part": "row-action-text",
|
|
@@ -2852,6 +2891,7 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2852
2891
|
index < repeater.rowCount - 1 ? /* @__PURE__ */ jsx("button", {
|
|
2853
2892
|
type: "button",
|
|
2854
2893
|
"data-formancy-part": "row-down",
|
|
2894
|
+
disabled: field.disabled,
|
|
2855
2895
|
onClick: () => repeater.moveRow(index, index + 1),
|
|
2856
2896
|
children: /* @__PURE__ */ jsx("span", {
|
|
2857
2897
|
"data-formancy-part": "row-action-text",
|
|
@@ -2892,14 +2932,20 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2892
2932
|
"data-formancy-part": "row",
|
|
2893
2933
|
children: [engine.fieldPaths().filter((candidate) => candidate.startsWith(`${wire}[${String(index)}]`)).map(slot), rowButtons(index)]
|
|
2894
2934
|
}, rowId));
|
|
2935
|
+
if (!field.visible) return null;
|
|
2936
|
+
const showError = field.touched && field.errors.length > 0;
|
|
2895
2937
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
2896
2938
|
"data-formancy-part": "repeater",
|
|
2897
2939
|
"data-formancy-field-path": wire,
|
|
2940
|
+
"data-state": showError ? "invalid" : "valid",
|
|
2941
|
+
id: field.controlProps.id,
|
|
2942
|
+
"aria-describedby": field.controlProps["aria-describedby"],
|
|
2898
2943
|
children: [
|
|
2899
2944
|
/* @__PURE__ */ jsx("legend", {
|
|
2900
2945
|
"data-formancy-part": "repeater-legend",
|
|
2901
2946
|
children: label
|
|
2902
2947
|
}),
|
|
2948
|
+
/* @__PURE__ */ jsx(RequiredHint, { field }),
|
|
2903
2949
|
grid ? /* @__PURE__ */ jsxs("div", {
|
|
2904
2950
|
"data-formancy-part": "datagrid",
|
|
2905
2951
|
"data-columns": String(plan.length),
|
|
@@ -2913,11 +2959,17 @@ function RepeaterSection({ wire, labels, registry }) {
|
|
|
2913
2959
|
}, entry.key))
|
|
2914
2960
|
}) : null, rows]
|
|
2915
2961
|
}) : rows,
|
|
2916
|
-
/* @__PURE__ */ jsx("
|
|
2962
|
+
showError ? /* @__PURE__ */ jsx("p", {
|
|
2963
|
+
"data-formancy-part": "error",
|
|
2964
|
+
...field.errorProps,
|
|
2965
|
+
children: field.errors.join(", ")
|
|
2966
|
+
}) : null,
|
|
2967
|
+
repeater.canAdd ? /* @__PURE__ */ jsx("button", {
|
|
2917
2968
|
type: "button",
|
|
2969
|
+
disabled: field.disabled,
|
|
2918
2970
|
onClick: () => repeater.addRow(),
|
|
2919
2971
|
children: addLabel
|
|
2920
|
-
})
|
|
2972
|
+
}) : null
|
|
2921
2973
|
]
|
|
2922
2974
|
});
|
|
2923
2975
|
}
|