@dynamicforms/vue-forms 0.12.0 → 0.13.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/dynamicforms-vue-forms.js +1371 -1154
- package/dist/dynamicforms-vue-forms.js.map +1 -1
- package/dist/index.d.ts +243 -25
- package/package.json +1 -1
- package/readme.md +47 -25
package/dist/index.d.ts
CHANGED
|
@@ -110,6 +110,12 @@ export declare interface ActionValue {
|
|
|
110
110
|
icon?: string;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* The values a field may hold: a fixed list, or a reference or a callback for a list that is filled in or replaced
|
|
115
|
+
* after the validator is built - the options a server answers with, or the ones another field's value leaves open.
|
|
116
|
+
*/
|
|
117
|
+
declare type AllowedValues<T> = T[] | Ref<T[]> | (() => T[]);
|
|
118
|
+
|
|
113
119
|
/**
|
|
114
120
|
* Depending on the useMarkdownInValidators setting, return either markdown or a plain string error message
|
|
115
121
|
* @param mdErrorString markdown source to optionally be stripped
|
|
@@ -269,8 +275,8 @@ export declare namespace DisplayMode {
|
|
|
269
275
|
* walk their way down a structure over own keys, enumerable symbols included, and the `parent` slot is in the
|
|
270
276
|
* state: reachable, it would take either walker back into the container the element came from.
|
|
271
277
|
*
|
|
272
|
-
* What they do reach on an element is `_actions` once something is registered, `_fields`
|
|
273
|
-
* `_itemTemplate` on a `List`. All
|
|
278
|
+
* What they do reach on an element is `_actions` once something is registered, `_fields` and the guarded view
|
|
279
|
+
* over it on a `Group`, and `_itemTemplate` on a `List`. All of them lead downwards only.
|
|
274
280
|
*/
|
|
275
281
|
declare interface ElementSlots<T = any> {
|
|
276
282
|
/** the value the element was given at construction; isChanged compares against it */
|
|
@@ -284,6 +290,8 @@ declare interface ElementSlots<T = any> {
|
|
|
284
290
|
valueVersion: number;
|
|
285
291
|
/** how many asynchronous validation runs are in flight on this element */
|
|
286
292
|
validatingCount: number;
|
|
293
|
+
/** how many direct children answer `validating` with true; a child that starts or stops running moves it */
|
|
294
|
+
validatingChildren: number;
|
|
287
295
|
/** the container that holds this element, absent while none does; takeChild writes it, releaseChild clears it */
|
|
288
296
|
parent: FieldBase | undefined;
|
|
289
297
|
/** the name the containing Group holds this element under; a List row carries none */
|
|
@@ -499,16 +507,56 @@ export declare abstract class FieldBase<T = any, X extends object = {}> {
|
|
|
499
507
|
* Puts a captured state back. It writes through the tracked view, so an effect that ran on the state the
|
|
500
508
|
* transaction produced runs again on the state it is being returned to, and only for the slots that differ.
|
|
501
509
|
*
|
|
502
|
-
*
|
|
503
|
-
* un-start one. Put back, the
|
|
510
|
+
* The two validation counters are the slots the element keeps: they count runs that are in flight, and a
|
|
511
|
+
* rollback cannot un-start one. Put back, the counts would no longer match the endValidating calls still to come.
|
|
504
512
|
*/
|
|
505
513
|
protected [TxRestore](snapshot: TxSnapshot): void;
|
|
506
|
-
/**
|
|
514
|
+
/**
|
|
515
|
+
* True while an asynchronous validation is in flight on this element or on anything below it, so a form answers
|
|
516
|
+
* for the whole tree it holds. The answer is a pair of counters rather than a walk: the element's own runs, and
|
|
517
|
+
* how many of its children answer the same question with true. A child that starts running while it was idle,
|
|
518
|
+
* or stops while it was the last one running, moves its container's tally, and a tally that changes the
|
|
519
|
+
* container's own answer moves the one above it - the transition costs the nesting depth and the read costs
|
|
520
|
+
* nothing.
|
|
521
|
+
*/
|
|
507
522
|
get validating(): boolean;
|
|
508
523
|
/** announces the start of one asynchronous validation run; validators pair it with endValidating */
|
|
509
524
|
beginValidating(): void;
|
|
510
525
|
/** announces the end of one asynchronous validation run */
|
|
511
526
|
endValidating(): void;
|
|
527
|
+
/**
|
|
528
|
+
* Records that a child started or stopped answering `validating` with true, and carries the transition further
|
|
529
|
+
* up where it changes this element's own answer.
|
|
530
|
+
*/
|
|
531
|
+
protected childValidatingChanged(started: boolean): void;
|
|
532
|
+
/**
|
|
533
|
+
* True while an `Action.execute()` at or below this element has yet to settle. An `Action` answers for its own
|
|
534
|
+
* runs; a container answers for the actions below it; anything else answers false, because an element that is
|
|
535
|
+
* not an action has nothing to execute.
|
|
536
|
+
*
|
|
537
|
+
* It states one thing and `validating` states the other, so a form that gates a submit button on the tree being
|
|
538
|
+
* idle reads both.
|
|
539
|
+
*/
|
|
540
|
+
get busy(): boolean;
|
|
541
|
+
/**
|
|
542
|
+
* The composed answer a container gives `busy`, memoised by Vue. An `Action` counts its executions in a counter
|
|
543
|
+
* of its own, which no container is told about, so the answer is composed over the members instead of tallied;
|
|
544
|
+
* the computed keeps the walk from repeating while nothing it read has moved, and a member that is itself a
|
|
545
|
+
* container answers from its own computed.
|
|
546
|
+
*/
|
|
547
|
+
protected get busyRead(): boolean;
|
|
548
|
+
/** What busyRead computes. A container answers over its members; anything else executes nothing. */
|
|
549
|
+
protected composeBusy(): boolean;
|
|
550
|
+
/**
|
|
551
|
+
* Resolves once nothing at or below this element is running - no asynchronous validation, no `Action.execute()`
|
|
552
|
+
* that has yet to settle. It is what a submit path awaits instead of polling `validating` and `busy`, and it
|
|
553
|
+
* resolves at once where nothing is running to begin with.
|
|
554
|
+
*
|
|
555
|
+
* It answers for the moment it resolves and makes no promise about the one after: work started later leaves the
|
|
556
|
+
* element running again, so a caller that has to act on a settled tree reads what it needs immediately after
|
|
557
|
+
* awaiting rather than at its leisure.
|
|
558
|
+
*/
|
|
559
|
+
settled(): Promise<void>;
|
|
512
560
|
/**
|
|
513
561
|
* List of errors. The array handed out is the one the element holds, and a validator writes into it in place,
|
|
514
562
|
* so a transaction that is open takes the element into its record here: a rollback that put back everything
|
|
@@ -670,7 +718,7 @@ export declare abstract class FieldBase<T = any, X extends object = {}> {
|
|
|
670
718
|
protected [TxSettleValidity](tx: Transaction): void;
|
|
671
719
|
validate(revalidate?: boolean): void;
|
|
672
720
|
get valid(): boolean;
|
|
673
|
-
get fullValue():
|
|
721
|
+
get fullValue(): T;
|
|
674
722
|
get isChanged(): boolean;
|
|
675
723
|
/**
|
|
676
724
|
* Brings a fresh element into the state a binding of `source` is in: it records what it was declared as, takes
|
|
@@ -725,6 +773,15 @@ declare interface FieldSlots<T = any> extends ElementSlots<T> {
|
|
|
725
773
|
touched: boolean;
|
|
726
774
|
}
|
|
727
775
|
|
|
776
|
+
/**
|
|
777
|
+
* What Group.fullValue reads back. The indexed access reads each field's fullValue getter, so a nested group
|
|
778
|
+
* contributes its own full structure rather than the partial one its `value` builds, and the recursion carries
|
|
779
|
+
* the guarantee all the way down: every key is present and none of them is null.
|
|
780
|
+
*/
|
|
781
|
+
export declare type FieldsToFullValues<T extends GenericFieldsInterface> = {
|
|
782
|
+
[K in keyof T]: T[K]['fullValue'];
|
|
783
|
+
};
|
|
784
|
+
|
|
728
785
|
export declare type FieldsToValues<T extends GenericFieldsInterface> = {
|
|
729
786
|
[K in keyof T]: T[K]['value'];
|
|
730
787
|
};
|
|
@@ -768,6 +825,7 @@ declare namespace Form {
|
|
|
768
825
|
FieldBase,
|
|
769
826
|
GenericFieldsInterface,
|
|
770
827
|
FieldsToValues,
|
|
828
|
+
FieldsToFullValues,
|
|
771
829
|
GroupValue,
|
|
772
830
|
GroupValueInput,
|
|
773
831
|
Group,
|
|
@@ -803,26 +861,79 @@ export declare const forms: {
|
|
|
803
861
|
install(app: any, options?: Partial<FormsConfig>): void;
|
|
804
862
|
};
|
|
805
863
|
|
|
806
|
-
|
|
864
|
+
/**
|
|
865
|
+
* Global configuration for @dynamicforms/vue-forms.
|
|
866
|
+
*
|
|
867
|
+
* The configuration is module-global: `setConfig` and the plugin's `install` write the single record this module
|
|
868
|
+
* holds, so in a process running several Vue apps the configuration applied last is the one all of them read.
|
|
869
|
+
*/
|
|
870
|
+
export declare interface FormsConfig {
|
|
871
|
+
/** Whether the built-in validators phrase their error messages as `MdString` markdown instead of plain text. */
|
|
807
872
|
useMarkdownInValidators: boolean;
|
|
808
873
|
}
|
|
809
874
|
|
|
810
875
|
export declare type GenericFieldsInterface = Record<string, FieldBase>;
|
|
811
876
|
|
|
877
|
+
/** The current configuration. The object is the module's own record, and reading it again reports later writes. */
|
|
878
|
+
export declare function getConfig(): FormsConfig;
|
|
879
|
+
|
|
812
880
|
export declare class Group<T extends GenericFieldsInterface = GenericFieldsInterface, X extends object = {}> extends FieldBase<GroupValue<T>, X> {
|
|
813
|
-
protected get state():
|
|
814
|
-
protected get raw():
|
|
881
|
+
protected get state(): GroupSlots<GroupValue<T>>;
|
|
882
|
+
protected get raw(): GroupSlots<GroupValue<T>>;
|
|
815
883
|
private readonly _fields;
|
|
816
884
|
constructor(fields: T, params?: IFieldParams<GroupValueInput<T>, X>);
|
|
885
|
+
/**
|
|
886
|
+
* The name array is copied as well: a rollback puts back the set of members the group held, not the array it
|
|
887
|
+
* went on to hold.
|
|
888
|
+
*/
|
|
889
|
+
protected [TxCapture](): TxSnapshot;
|
|
817
890
|
/**
|
|
818
891
|
* The copy of a built value that serves as a baseline. The value getter hands out one object per version, and
|
|
819
892
|
* a baseline holding that same object would report every value as its own original.
|
|
820
893
|
*/
|
|
821
894
|
private static baseline;
|
|
822
|
-
|
|
895
|
+
/**
|
|
896
|
+
* Takes `field` into this group under `fieldName`. The group ends up holding it exactly as it holds a field the
|
|
897
|
+
* constructor was given: the field carries the back-reference and the name, its verdict counts towards the
|
|
898
|
+
* group's, and a rule of the field's that names another member of the form reaches it here.
|
|
899
|
+
*
|
|
900
|
+
* The change is announced through the ordinary path, so a group whose value the new field adds to says so once
|
|
901
|
+
* the transaction closes, and its verdict is re-formed over the members it now holds. The baseline behind
|
|
902
|
+
* `isChanged` is not rewritten: a group that gains a field holds something its original value does not carry,
|
|
903
|
+
* and reports itself changed until `originalValue` is written.
|
|
904
|
+
*
|
|
905
|
+
* @throws Error where this group already holds a field under `fieldName`.
|
|
906
|
+
* @throws TypeError where `field` already belongs to a container - pass a `bind()` of it instead.
|
|
907
|
+
*/
|
|
908
|
+
addField(fieldName: string, field: FieldBase): this;
|
|
909
|
+
/**
|
|
910
|
+
* Takes the field held under `fieldName` out of this group and hands it back, answering undefined where the
|
|
911
|
+
* group holds no field of that name. The field is released whole: the back-reference and the name are gone, its
|
|
912
|
+
* verdict and its runs in flight no longer count towards the group's, and it is free to be taken by another
|
|
913
|
+
* container. What it holds - its value, its errors, the change history behind `isChanged` - is its own to report.
|
|
914
|
+
*
|
|
915
|
+
* The change is announced through the ordinary path, so the group's value and verdict settle over the members it
|
|
916
|
+
* has left. The baseline behind `isChanged` is not rewritten, the same way it is not on `addField`.
|
|
917
|
+
*/
|
|
918
|
+
removeField(fieldName: string): FieldBase | undefined;
|
|
919
|
+
/**
|
|
920
|
+
* Writes one entry of the member map. The map is typed by the fields interface, which names the members a group
|
|
921
|
+
* of that type holds and no others, so both here and in dropEntry it is reached as the plain record it is.
|
|
922
|
+
*/
|
|
923
|
+
private static setEntry;
|
|
924
|
+
/** Drops one entry out of the member map. */
|
|
925
|
+
private static dropEntry;
|
|
823
926
|
private static isValidFields;
|
|
824
927
|
static createFromFormData(data: Record<string, any> | null): Group;
|
|
825
928
|
field<K extends keyof T>(fieldName: K): T[K] | null;
|
|
929
|
+
/**
|
|
930
|
+
* The typed map of this group's members. What it hands out is a view over the map the group holds: reading it
|
|
931
|
+
* reaches the members themselves, and every write to it is refused - addField() and removeField() are what
|
|
932
|
+
* change the set. The view is built the first time something asks for it.
|
|
933
|
+
*
|
|
934
|
+
* Reading through it is a tracked read of the set of members, so a template rendering off `group.fields`
|
|
935
|
+
* re-renders when a field is added or removed; what a member itself holds is tracked by that member.
|
|
936
|
+
*/
|
|
826
937
|
get fields(): T;
|
|
827
938
|
protected get members(): FieldBase[];
|
|
828
939
|
get value(): GroupValue<T>;
|
|
@@ -841,7 +952,7 @@ export declare class Group<T extends GenericFieldsInterface = GenericFieldsInter
|
|
|
841
952
|
protected resetTo(source: FieldBase, value: any): void;
|
|
842
953
|
get touched(): boolean;
|
|
843
954
|
set touched(touched: boolean);
|
|
844
|
-
get fullValue():
|
|
955
|
+
get fullValue(): FieldsToFullValues<T>;
|
|
845
956
|
/**
|
|
846
957
|
* Records that a member changed its value, so that the transaction in progress works out at commit what this
|
|
847
958
|
* group's own value became and announces it once. A mutation method calls it itself; you rarely need to.
|
|
@@ -856,6 +967,8 @@ export declare class Group<T extends GenericFieldsInterface = GenericFieldsInter
|
|
|
856
967
|
protected refreshPreviousValue(): void;
|
|
857
968
|
get valid(): boolean;
|
|
858
969
|
protected composeValid(): boolean;
|
|
970
|
+
get busy(): boolean;
|
|
971
|
+
protected composeBusy(): boolean;
|
|
859
972
|
validate(revalidate?: boolean): void;
|
|
860
973
|
bind(data?: GroupValueInput<T>, overrides?: IBindParams<GroupValueInput<T>, X>): Group<T, X>;
|
|
861
974
|
/**
|
|
@@ -865,8 +978,21 @@ export declare class Group<T extends GenericFieldsInterface = GenericFieldsInter
|
|
|
865
978
|
rebind(data: GroupValueInput<T>): this;
|
|
866
979
|
}
|
|
867
980
|
|
|
868
|
-
/** what Group
|
|
869
|
-
|
|
981
|
+
/** what a Group holds beyond the container slots: the names of its members, in the order it took them */
|
|
982
|
+
declare interface GroupSlots<T = any> extends ContainerSlots<T> {
|
|
983
|
+
/**
|
|
984
|
+
* The names the group holds its members under. The member map itself is a plain object beside the state, so
|
|
985
|
+
* this array is what a reader inside an effect depends on: a member added or removed re-runs the effect, and a
|
|
986
|
+
* rolled-back transaction puts the set back with the rest of the slots.
|
|
987
|
+
*/
|
|
988
|
+
fieldNames: string[];
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* What Group.value reads back: the values of the fields that serialize, or null when none does. Every key is
|
|
993
|
+
* optional, because a field that is disabled is left out of the object the group builds.
|
|
994
|
+
*/
|
|
995
|
+
export declare type GroupValue<T extends GenericFieldsInterface> = Partial<FieldsToValues<T>> | null;
|
|
870
996
|
|
|
871
997
|
/** what Group.value and the Group constructor accept: keys left out are simply not assigned */
|
|
872
998
|
export declare type GroupValueInput<T extends GenericFieldsInterface> = Partial<FieldsToValues<T>> | null;
|
|
@@ -887,7 +1013,7 @@ export declare interface IFieldConstructorActionsList {
|
|
|
887
1013
|
/**
|
|
888
1014
|
* Parameters accepted by field constructors and by bind overrides.
|
|
889
1015
|
*
|
|
890
|
-
* Only writable members are listed. valid, validating, fullValue and isChanged are getter-only, so assigning
|
|
1016
|
+
* Only writable members are listed. valid, validating, busy, fullValue and isChanged are getter-only, so assigning
|
|
891
1017
|
* them throws a TypeError. So are parent and fieldName: a container writes the slots behind them when it takes
|
|
892
1018
|
* an element, and nobody else can, which is why the type rejects them.
|
|
893
1019
|
*/
|
|
@@ -915,7 +1041,7 @@ export declare type IFieldConstructorParams<T = any> = {
|
|
|
915
1041
|
export declare type IFieldParams<T = any, X extends object = {}> = Partial<IFieldConstructorParams<T>> & Partial<NoInfer<X>>;
|
|
916
1042
|
|
|
917
1043
|
declare class InAllowedValues<T = any> extends Validator {
|
|
918
|
-
constructor(allowedValues: T
|
|
1044
|
+
constructor(allowedValues: AllowedValues<T>, message?: RenderContentRef);
|
|
919
1045
|
}
|
|
920
1046
|
|
|
921
1047
|
export declare function isCallableFunction(msg?: RenderContentRef): msg is RenderContentCallable;
|
|
@@ -927,7 +1053,7 @@ export declare function isCallableFunction(msg?: RenderContentRef): msg is Rende
|
|
|
927
1053
|
*/
|
|
928
1054
|
export declare function isSimpleComponentDef(msg?: RenderContentRef): msg is SimpleComponentDef;
|
|
929
1055
|
|
|
930
|
-
declare class LengthInRange
|
|
1056
|
+
declare class LengthInRange extends Validator {
|
|
931
1057
|
constructor(minLength: number, maxLength: number, message?: RenderContentRef);
|
|
932
1058
|
}
|
|
933
1059
|
|
|
@@ -949,9 +1075,13 @@ export declare class List<T extends GenericFieldsInterface = GenericFieldsInterf
|
|
|
949
1075
|
* own values, or an empty group when the list has no template.
|
|
950
1076
|
*/
|
|
951
1077
|
private createPaddingItem;
|
|
1078
|
+
/** Records that the set of rows changed, so `items` rebuilds the frozen array it hands out at the next read. */
|
|
1079
|
+
private rowsChanged;
|
|
1080
|
+
/** True where `next` is a different set of rows than `previous`: another count, or another row at a position. */
|
|
1081
|
+
private static rowsDiffer;
|
|
952
1082
|
private setValueInternal;
|
|
953
1083
|
get value(): ListValue;
|
|
954
|
-
set value(newValue:
|
|
1084
|
+
set value(newValue: ListValue);
|
|
955
1085
|
/**
|
|
956
1086
|
* The rows are taken from `source` where the caller supplied no value of its own, so a list nested in a row that
|
|
957
1087
|
* a whole-list assignment reuses ends up holding what the template gives it rather than what it held before.
|
|
@@ -974,8 +1104,31 @@ export declare class List<T extends GenericFieldsInterface = GenericFieldsInterf
|
|
|
974
1104
|
protected refreshPreviousValue(): void;
|
|
975
1105
|
get valid(): boolean;
|
|
976
1106
|
protected composeValid(): boolean;
|
|
1107
|
+
get busy(): boolean;
|
|
1108
|
+
protected composeBusy(): boolean;
|
|
977
1109
|
validate(revalidate?: boolean): void;
|
|
978
1110
|
protected get members(): FieldBase[];
|
|
1111
|
+
/**
|
|
1112
|
+
* How many rows this list holds. The read is tracked, so a template rendering off it re-renders as rows come and
|
|
1113
|
+
* go.
|
|
1114
|
+
*/
|
|
1115
|
+
/**
|
|
1116
|
+
* Every row of this list, each one built from all of its fields. Where `value` states what the list serializes -
|
|
1117
|
+
* rows composed of the fields that are enabled, and null where the list is empty - this states what the list
|
|
1118
|
+
* holds: the disabled fields are in it too, and an empty list reads back as an empty array rather than as null.
|
|
1119
|
+
*/
|
|
1120
|
+
get fullValue(): FieldsToFullValues<T>[];
|
|
1121
|
+
get length(): number;
|
|
1122
|
+
/**
|
|
1123
|
+
* The rows this list holds, oldest position first. The array is a frozen copy: it states what the list held at
|
|
1124
|
+
* the read and nothing writes back through it - `push`, `insert`, `remove` and `clear` are what change the set -
|
|
1125
|
+
* so a caller may hold on to it. The rows in it are the live elements, so reading one reports what it holds now.
|
|
1126
|
+
*
|
|
1127
|
+
* The copy is built once per change of the set and handed to every reader until the next one: a write inside a
|
|
1128
|
+
* row changes what the list serializes without changing which rows it holds, and the array a reader took stays
|
|
1129
|
+
* the same one across such a write.
|
|
1130
|
+
*/
|
|
1131
|
+
get items(): readonly Group<T>[];
|
|
979
1132
|
get(index: number): Group<T> | undefined;
|
|
980
1133
|
push(item: any): number;
|
|
981
1134
|
pop(): Group<T> | undefined;
|
|
@@ -1001,12 +1154,20 @@ export declare class ListItemRemovedAction extends FieldActionBase {
|
|
|
1001
1154
|
/** what a List holds beyond the container slots: the rows themselves */
|
|
1002
1155
|
declare interface ListSlots<T extends GenericFieldsInterface = GenericFieldsInterface> extends ContainerSlots<ListValue> {
|
|
1003
1156
|
rows: Group<T>[] | null;
|
|
1157
|
+
/**
|
|
1158
|
+
* Counts the changes to the set of rows. `items` rebuilds the frozen array it hands out when it moves, and
|
|
1159
|
+
* only then: a write inside a row changes what the list serializes without changing which rows it holds.
|
|
1160
|
+
*/
|
|
1161
|
+
rowsVersion: number;
|
|
1162
|
+
/** the frozen array `items` last handed out, together with the rows version it was built from */
|
|
1163
|
+
cachedItems: readonly Group<T>[] | null;
|
|
1164
|
+
cachedItemsVersion: number;
|
|
1004
1165
|
}
|
|
1005
1166
|
|
|
1006
1167
|
/** what List.value reads back: one plain object per item, or null when the list is empty */
|
|
1007
1168
|
export declare type ListValue = Record<string, any>[] | null;
|
|
1008
1169
|
|
|
1009
|
-
declare class MaxLength
|
|
1170
|
+
declare class MaxLength extends Validator {
|
|
1010
1171
|
constructor(maxLength: number, message?: RenderContentRef);
|
|
1011
1172
|
}
|
|
1012
1173
|
|
|
@@ -1025,7 +1186,7 @@ export declare class MdString extends String {
|
|
|
1025
1186
|
|
|
1026
1187
|
export declare const MessagesWidget: DefineComponent<Props, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
|
|
1027
1188
|
|
|
1028
|
-
declare class MinLength
|
|
1189
|
+
declare class MinLength extends Validator {
|
|
1029
1190
|
constructor(minLength: number, message?: RenderContentRef);
|
|
1030
1191
|
}
|
|
1031
1192
|
|
|
@@ -1073,7 +1234,7 @@ export declare namespace Operator {
|
|
|
1073
1234
|
export function isLogicOperator(operator: Operator): boolean;
|
|
1074
1235
|
}
|
|
1075
1236
|
|
|
1076
|
-
declare class Pattern
|
|
1237
|
+
declare class Pattern extends Validator {
|
|
1077
1238
|
constructor(pattern: RegExp, message?: RenderContentRef);
|
|
1078
1239
|
}
|
|
1079
1240
|
|
|
@@ -1102,10 +1263,23 @@ export declare type RenderContentNonCallable = string | MdString | SimpleCompone
|
|
|
1102
1263
|
*/
|
|
1103
1264
|
export declare type RenderContentRef = RenderContent | Ref<RenderContent>;
|
|
1104
1265
|
|
|
1105
|
-
declare class Required_2
|
|
1106
|
-
constructor(
|
|
1266
|
+
declare class Required_2 extends Validator {
|
|
1267
|
+
constructor(options?: RequiredOptions);
|
|
1268
|
+
constructor(message?: RenderContentRef, options?: RequiredOptions);
|
|
1107
1269
|
}
|
|
1108
1270
|
|
|
1271
|
+
/** How the value is read before its length is taken. */
|
|
1272
|
+
declare interface RequiredOptions {
|
|
1273
|
+
/**
|
|
1274
|
+
* Whether a string is trimmed before it is measured, so that whitespace alone is no value. Defaults to true.
|
|
1275
|
+
* Set it to false where the spaces are part of what the field holds.
|
|
1276
|
+
*/
|
|
1277
|
+
trim?: boolean;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/** Writes the members `newConfig` names and leaves the rest as they stand. */
|
|
1281
|
+
export declare function setConfig(newConfig: Partial<FormsConfig>): void;
|
|
1282
|
+
|
|
1109
1283
|
/**
|
|
1110
1284
|
* Interface for custom component content definition
|
|
1111
1285
|
*/
|
|
@@ -1119,6 +1293,14 @@ export declare class Statement {
|
|
|
1119
1293
|
private readonly operand1;
|
|
1120
1294
|
private readonly operator;
|
|
1121
1295
|
private readonly operand2;
|
|
1296
|
+
/**
|
|
1297
|
+
* An operand is a nested `Statement`, a `FieldBase` whose value is compared, or a literal. `undefined` and a
|
|
1298
|
+
* function are neither: they are what a misspelled field name and a field accessor handed over uncalled answer
|
|
1299
|
+
* with, and a statement built from one compares nothing and never fires. The position names which of the two
|
|
1300
|
+
* operands is at fault, so the error points at the place the name was written.
|
|
1301
|
+
*/
|
|
1302
|
+
private static validateOperand;
|
|
1303
|
+
/** @throws TypeError where an operand the statement reads is undefined or a function. */
|
|
1122
1304
|
constructor(operand1: OperandType, operator: Operator, operand2: OperandType);
|
|
1123
1305
|
/**
|
|
1124
1306
|
* The value an operand contributes. A field operand names a field of the record the statement is evaluated in:
|
|
@@ -1145,6 +1327,8 @@ declare class Transaction {
|
|
|
1145
1327
|
private readonly participants;
|
|
1146
1328
|
/** what a rollback has to put back beyond the state slots, newest last */
|
|
1147
1329
|
private readonly undo;
|
|
1330
|
+
/** what the commit has to do once the change stands, in the order it was handed in */
|
|
1331
|
+
private readonly settled;
|
|
1148
1332
|
/** true once the transaction has been unwound, which is what tells a run started inside it to say nothing */
|
|
1149
1333
|
private unwound;
|
|
1150
1334
|
/** the depth the announcement in progress is working at, -1 while none is */
|
|
@@ -1177,6 +1361,12 @@ declare class Transaction {
|
|
|
1177
1361
|
* hands its own undo in here rather than making every snapshot carry the thing.
|
|
1178
1362
|
*/
|
|
1179
1363
|
whenRolledBack(work: () => void): void;
|
|
1364
|
+
/**
|
|
1365
|
+
* Registers work that runs once the transaction has committed, and not at all where it is rolled back. It is
|
|
1366
|
+
* how an operation defers a step that cannot be taken back - cancelling work in flight, for one - until the
|
|
1367
|
+
* change it belongs to actually stands.
|
|
1368
|
+
*/
|
|
1369
|
+
whenCommitted(work: () => void): void;
|
|
1180
1370
|
/**
|
|
1181
1371
|
* Notes that an element at or below the depth the pass in progress is working at has been enrolled. The pass
|
|
1182
1372
|
* then gives up the rest of its batch and is started again over what is now owed, so the newcomer is reached
|
|
@@ -1286,6 +1476,13 @@ declare interface TxStructuralEvent {
|
|
|
1286
1476
|
* Base validation error class with component rendering capabilities
|
|
1287
1477
|
*/
|
|
1288
1478
|
export declare class ValidationError {
|
|
1479
|
+
code?: string | undefined;
|
|
1480
|
+
/**
|
|
1481
|
+
* Machine-readable identifier of what failed, in kebab-case. Every validator this library ships states one, so
|
|
1482
|
+
* code that reacts to a particular failure does not have to match the message text, which is translated and
|
|
1483
|
+
* configurable. It is optional: an error built by hand carries whatever its author gives it, or nothing.
|
|
1484
|
+
*/
|
|
1485
|
+
constructor(code?: string | undefined);
|
|
1289
1486
|
get componentName(): string;
|
|
1290
1487
|
get componentBindings(): {};
|
|
1291
1488
|
get componentBody(): string;
|
|
@@ -1299,7 +1496,7 @@ export declare class ValidationErrorRenderContent extends ValidationError {
|
|
|
1299
1496
|
classes: ClassTypes;
|
|
1300
1497
|
private text;
|
|
1301
1498
|
private textType;
|
|
1302
|
-
constructor(text: RenderContentRef, classes?: ClassTypes);
|
|
1499
|
+
constructor(text: RenderContentRef, classes?: ClassTypes, code?: string);
|
|
1303
1500
|
get resolvedText(): RenderContentNonCallable;
|
|
1304
1501
|
get getTextType(): "string" | "md" | "component";
|
|
1305
1502
|
get componentName(): string;
|
|
@@ -1314,13 +1511,19 @@ export declare class ValidationErrorRenderContent extends ValidationError {
|
|
|
1314
1511
|
export declare class ValidationErrorText extends ValidationError {
|
|
1315
1512
|
text: string;
|
|
1316
1513
|
classes: ClassTypes;
|
|
1317
|
-
constructor(text: string, classes?: ClassTypes);
|
|
1514
|
+
constructor(text: string, classes?: ClassTypes, code?: string);
|
|
1318
1515
|
get componentName(): string;
|
|
1319
1516
|
get componentBody(): string;
|
|
1320
1517
|
get extraClasses(): ClassTypes;
|
|
1321
1518
|
}
|
|
1322
1519
|
|
|
1323
|
-
|
|
1520
|
+
/**
|
|
1521
|
+
* What a validator does with a value. `signal` aborts when the verdict this call would reach stops counting - a
|
|
1522
|
+
* newer run over the same field, a field the validator was taken off once that removal stands, a transaction that
|
|
1523
|
+
* was unwound - so an asynchronous check hands it to the work it commissions and stops paying for an answer nobody
|
|
1524
|
+
* will read. A function that has nothing to cancel ignores it.
|
|
1525
|
+
*/
|
|
1526
|
+
export declare type ValidationFunction<T = any> = (newValue: T, oldValue: T, field: FieldBase<T>, signal: AbortSignal) => ValidationFunctionResult | Promise<ValidationFunctionResult>;
|
|
1324
1527
|
|
|
1325
1528
|
export declare type ValidationFunctionResult = ValidationError[] | null;
|
|
1326
1529
|
|
|
@@ -1349,8 +1552,19 @@ export declare class Validator<T = any> extends ValueChangedAction {
|
|
|
1349
1552
|
* Takes this validator off `field`: the errors it put there are withdrawn and the verdict is re-formed over what
|
|
1350
1553
|
* the validators the field still holds have to say. Withdrawing them is what keeps a field from staying invalid
|
|
1351
1554
|
* on an error no validator is left to take back.
|
|
1555
|
+
*
|
|
1556
|
+
* A run in flight over the field is cancelled once the removal stands, and not before: a transaction that
|
|
1557
|
+
* unwinds puts the validator and its epoch back, and the run it never cancelled goes on to reach the verdict the
|
|
1558
|
+
* field is then owed. Cancelling it as the removal is made would leave the field reporting itself valid over a
|
|
1559
|
+
* value nothing checked, on the strength of a transaction that never happened.
|
|
1352
1560
|
*/
|
|
1353
1561
|
unregisterFrom(field: FieldBase): void;
|
|
1562
|
+
/**
|
|
1563
|
+
* Cancels the asynchronous run in flight over `field`, where there is one. It is called once the field has
|
|
1564
|
+
* stopped holding this validator: the field's validation epoch has moved on by then, so the run's verdict is
|
|
1565
|
+
* already dropped and the signal it handed its check aborts with it.
|
|
1566
|
+
*/
|
|
1567
|
+
protected abandonRun(field: FieldBase): void;
|
|
1354
1568
|
/**
|
|
1355
1569
|
* What this validator remembers about one of the fields it validates. One instance may be registered on several
|
|
1356
1570
|
* fields - every row of a list carries the instances the item template carries - so each field has a record of
|
|
@@ -1370,6 +1584,8 @@ export declare class Validator<T = any> extends ValueChangedAction {
|
|
|
1370
1584
|
/** What a validator remembers about one field it is registered on; a subclass widens it. */
|
|
1371
1585
|
export declare interface ValidatorBindingState {
|
|
1372
1586
|
run: number;
|
|
1587
|
+
/** cancels the asynchronous run in flight over the field, absent while no run is waiting for its verdict */
|
|
1588
|
+
abandon?: () => void;
|
|
1373
1589
|
}
|
|
1374
1590
|
|
|
1375
1591
|
export declare namespace Validators {
|
|
@@ -1377,7 +1593,9 @@ export declare namespace Validators {
|
|
|
1377
1593
|
CompareTo,
|
|
1378
1594
|
CompareToTarget,
|
|
1379
1595
|
InAllowedValues,
|
|
1596
|
+
AllowedValues,
|
|
1380
1597
|
Required_2 as Required,
|
|
1598
|
+
RequiredOptions,
|
|
1381
1599
|
Pattern,
|
|
1382
1600
|
ValidationFunctionResult,
|
|
1383
1601
|
ValidationFunction,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamicforms/vue-forms",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A lightweight Vue.js library for form state management with built-in validation, conditional logic, nested structures, and a rich event system. UI-agnostic and TypeScript-first.",
|
|
7
7
|
"author": "Jure Erznožnik",
|
package/readme.md
CHANGED
|
@@ -22,19 +22,25 @@ mechanism applies at every level of a nested form.
|
|
|
22
22
|
|
|
23
23
|
## Features
|
|
24
24
|
|
|
25
|
-
- **UI-agnostic**:
|
|
25
|
+
- **UI-agnostic**: a logic layer for form state, validation and dynamic behaviour. Any Vue components render it,
|
|
26
|
+
your own included
|
|
27
|
+
- **Transactional**: every mutating operation is atomic — events are announced once, over the net change, and a
|
|
28
|
+
handler that throws leaves the form exactly as it was. `transaction()` makes several writes one operation, and
|
|
29
|
+
`tx.rollback()` withdraws one without an error
|
|
30
|
+
- **Lists that scale**: a `List` is meant to hold thousands of rows. Writing one field of one row costs that row
|
|
31
|
+
and the depth it sits at, a `push()` costs one row, and reading `value` or `valid` again costs nothing
|
|
32
|
+
- **Declared once, bound per record**: the `Group` handed to `new List(template)` is the declaration every row is
|
|
33
|
+
built from. One validator instance and one conditional rule serve every row, and each row answers for itself
|
|
26
34
|
- **Reactive**: every member of a field, group or list is a tracked read — assign a property directly, with no
|
|
27
35
|
`ref` to unwrap and no computed mirror to keep in sync
|
|
28
|
-
- **Nested structures**:
|
|
29
|
-
- **Event system**:
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
- **
|
|
36
|
+
- **Nested structures**: fields, groups and lists compose recursively, at every level
|
|
37
|
+
- **Event system**: every change travels through an action pipeline; a handler may pass it on, reshape it or stop it
|
|
38
|
+
- **Validation**: built-in validators, custom synchronous and asynchronous rules, cross-field comparisons, and
|
|
39
|
+
errors that carry a `code` and render as text, markdown or a component of your own
|
|
40
|
+
- **Conditional logic**: visibility, enablement and values declared as statements over other fields
|
|
41
|
+
- **Display modes**: control field visibility with different display modes (Full, Hidden, Invisible, Suppress)
|
|
42
|
+
- **TypeScript support**: full type definitions, and a group's value type inferred from the fields it holds
|
|
33
43
|
- **Lightweight**: `vue` (^3.5.2) is the only peer dependency and `lodash-es` the only runtime one
|
|
34
|
-
- **Field types**: Core field types (Field, Action, Group, List) to represent any data structure
|
|
35
|
-
- **Validation**: Comprehensive validation system with built-in validators and extensible error handling
|
|
36
|
-
- **Conditional logic**: Dynamic form behavior based on field values and conditions
|
|
37
|
-
- **Display modes**: Control field visibility with different display modes (Full, Hidden, Invisible, Suppress)
|
|
38
44
|
|
|
39
45
|
## Installation
|
|
40
46
|
|
|
@@ -63,6 +69,10 @@ The import must be a named one — the default export is a namespace of the libr
|
|
|
63
69
|
(`MdString`). Rendering those requires a globally registered `vue-markdown` component; set the option to `false` if you
|
|
64
70
|
want plain text instead.
|
|
65
71
|
|
|
72
|
+
The configuration is module-global rather than per app, and `getConfig()`, `setConfig()` and the `FormsConfig` type
|
|
73
|
+
are exported beside the plugin, so the options can be read and written where there is no app to install a plugin
|
|
74
|
+
on.
|
|
75
|
+
|
|
66
76
|
## Basic Usage Example
|
|
67
77
|
|
|
68
78
|
Every form element is created with the constructor: `new Field({ ... })`, `new Action({ ... })`,
|
|
@@ -211,13 +221,22 @@ const validatedForm = new Group({
|
|
|
211
221
|
```
|
|
212
222
|
|
|
213
223
|
Validators run eagerly — a field is validated the moment it is created, so a form built from empty required fields is
|
|
214
|
-
invalid immediately. Use `field.touched` to decide when to show the errors in the UI.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
224
|
+
invalid immediately. Use `field.touched` to decide when to show the errors in the UI. `Required` trims a string
|
|
225
|
+
before it measures it, so a value of spaces alone is no value; `new Validators.Required({ trim: false })` keeps the
|
|
226
|
+
spaces where they are part of what the field holds.
|
|
227
|
+
|
|
228
|
+
Every error a built-in validator produces carries a `code` — `required`, `pattern`, `min-length`, … — so a program
|
|
229
|
+
reacting to one particular failure need not match message text that is translated and configurable.
|
|
230
|
+
|
|
231
|
+
A validation function may return a `Promise`. `field.validating` is `true` while such a run is pending — on the
|
|
232
|
+
field and on every container above it, so a form answers for the whole tree — and the verdict applied to the field
|
|
233
|
+
is always the one belonging to the newest run, so a slow check cannot overwrite a faster one started after it. The
|
|
234
|
+
function receives an `AbortSignal` as its fourth argument, which aborts the moment the run's verdict stops
|
|
235
|
+
counting, so the request behind it can be called off. `form.busy` is the same question with the `Action.execute()`
|
|
236
|
+
runs below it included — what a submit button binds to. A validator message given as a `Ref` or `computed` is
|
|
237
|
+
resolved when the message is rendered, so a translated message follows a locale switch without revalidating.
|
|
238
|
+
`field.clearValidators()` drops the validators, empties the errors and cancels whatever validation is still in
|
|
239
|
+
flight.
|
|
221
240
|
|
|
222
241
|
## Messages Widget Component
|
|
223
242
|
|
|
@@ -326,7 +345,8 @@ const contactsList = new List(contactTemplate);
|
|
|
326
345
|
contactsList.push({ name: 'John Doe', email: 'john@example.com', phone: '123-456-7890' });
|
|
327
346
|
contactsList.push({ name: 'Jane Doe', email: 'jane@example.com', phone: '987-654-3210' });
|
|
328
347
|
|
|
329
|
-
// Access list items: get()
|
|
348
|
+
// Access list items: items hands out the rows, get() answers undefined for an invalid index
|
|
349
|
+
console.log(contactsList.length); // 2
|
|
330
350
|
const firstContact = contactsList.get(0)!;
|
|
331
351
|
console.log(firstContact.fields.name.value); // 'John Doe'
|
|
332
352
|
|
|
@@ -337,8 +357,9 @@ firstContact.fields.email.value = 'john.doe@example.com';
|
|
|
337
357
|
contactsList.remove(1);
|
|
338
358
|
```
|
|
339
359
|
|
|
340
|
-
Every list mutation is tracked, so a `v-for` over `contactsList.
|
|
341
|
-
`remove()`, `pop()` and `clear()` without any extra wiring.
|
|
360
|
+
Every list mutation is tracked, so a `v-for` over `contactsList.items` re-renders on `push()`, `insert()`,
|
|
361
|
+
`remove()`, `pop()` and `clear()` without any extra wiring. `items` hands out a frozen array of the live rows,
|
|
362
|
+
rebuilt once per change of the set.
|
|
342
363
|
|
|
343
364
|
## TypeScript Support
|
|
344
365
|
|
|
@@ -388,10 +409,11 @@ const email: string = userForm.fields.email.value;
|
|
|
388
409
|
const age: number = userForm.fields.age.value;
|
|
389
410
|
const darkMode: boolean = userForm.fields.preferences.fields.darkMode.value;
|
|
390
411
|
|
|
391
|
-
// The serialized value is typed too, member by member
|
|
412
|
+
// The serialized value is typed too, member by member. Every member is optional, because a disabled field is
|
|
413
|
+
// left out of the object the group builds
|
|
392
414
|
const values = userForm.value!;
|
|
393
|
-
const emailFromValue: string = values.email;
|
|
394
|
-
const prefs: { darkMode
|
|
415
|
+
const emailFromValue: string | undefined = values.email;
|
|
416
|
+
const prefs: { darkMode?: boolean; notifications?: boolean } | null | undefined = values.preferences;
|
|
395
417
|
|
|
396
418
|
// Type safety prevents errors
|
|
397
419
|
// userForm.fields.age.value = 'not a number'; // Error: Type 'string' is not assignable to type 'number'
|
|
@@ -443,8 +465,8 @@ For more detailed documentation and examples, check out the [documentation](http
|
|
|
443
465
|
declarations, transactions, where validity comes from and how a `List` builds its rows.
|
|
444
466
|
|
|
445
467
|
Upgrading an existing project? The
|
|
446
|
-
[migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) walks the journey from 0.6.1
|
|
447
|
-
|
|
468
|
+
[migration guide](https://docs.velis.si/dynamicforms/vue-forms/guide/migration) walks the journey from 0.6.1
|
|
469
|
+
onwards, silent breaks first, and keeps a section per release for a project crossing one.
|
|
448
470
|
|
|
449
471
|
## Conclusion
|
|
450
472
|
|