declarative-forms 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -147
- package/dist/index.cjs +119 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -1
- package/dist/index.d.ts +106 -1
- package/dist/index.js +119 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/styles/declarative-forms.css +14 -0
- package/CHANGELOG.md +0 -148
package/dist/index.d.cts
CHANGED
|
@@ -262,6 +262,21 @@ interface ButtonDescriptor {
|
|
|
262
262
|
type ButtonMap = Record<string, ButtonDescriptor>;
|
|
263
263
|
interface DeclarativeFormOptions {
|
|
264
264
|
fields: readonly FieldDescriptor[];
|
|
265
|
+
/**
|
|
266
|
+
* The record to open the form with, keyed by field name — how you edit an
|
|
267
|
+
* existing object rather than asking for a new one.
|
|
268
|
+
*
|
|
269
|
+
* ```ts
|
|
270
|
+
* const updated = await ask(userFields, { defaultValues: user });
|
|
271
|
+
* ```
|
|
272
|
+
*
|
|
273
|
+
* Each entry wins over that field's own `defaultValue`, so the same field
|
|
274
|
+
* list serves both the "create" and the "edit" call. Keys naming no field are
|
|
275
|
+
* ignored — pass a whole domain object without filtering it first — as are
|
|
276
|
+
* keys whose value is `undefined`, which fall back to the field's
|
|
277
|
+
* `defaultValue`. An `array` field takes its list of records here directly.
|
|
278
|
+
*/
|
|
279
|
+
defaultValues?: FormValues;
|
|
265
280
|
/**
|
|
266
281
|
* Buttons for the dialog's lower bar, keyed by label.
|
|
267
282
|
* Defaults to a single `OK` button running {@link onConfirm}.
|
|
@@ -507,6 +522,16 @@ declare abstract class Field<D extends BaseFieldDescriptor = FieldDescriptor> im
|
|
|
507
522
|
* resolved before being applied.
|
|
508
523
|
*/
|
|
509
524
|
applyDefaultValue(): Promise<void>;
|
|
525
|
+
/**
|
|
526
|
+
* Apply a plainly declared default value straight away.
|
|
527
|
+
*
|
|
528
|
+
* {@link applyDefaultValue} is awaited during initialisation, so a value it
|
|
529
|
+
* applies cannot be seen by the first layout pass. A default that is neither a
|
|
530
|
+
* function nor a promise needs no awaiting, so it is applied at build time and
|
|
531
|
+
* `isActive` predicates reading it are right from the first paint. The async
|
|
532
|
+
* pass runs afterwards regardless and simply sets the same value again.
|
|
533
|
+
*/
|
|
534
|
+
applyStaticDefaultValue(): void;
|
|
510
535
|
/**
|
|
511
536
|
* Hook for setup that cannot happen in {@link build} — loading select
|
|
512
537
|
* options, or a first render that needs the form context. May be synchronous.
|
|
@@ -671,7 +696,31 @@ declare class DeclarativeForm implements SubForm, StackableDialog {
|
|
|
671
696
|
constructor(options: DeclarativeFormOptions, internal?: InternalOptions);
|
|
672
697
|
private resolveButtons;
|
|
673
698
|
private buildFields;
|
|
699
|
+
/**
|
|
700
|
+
* Decide field visibility and build the tab strip synchronously, before the
|
|
701
|
+
* caller gets the form back.
|
|
702
|
+
*
|
|
703
|
+
* {@link initialize} is async — it awaits option loads and default values —
|
|
704
|
+
* but `openInModal()` normally runs in the same tick as the constructor.
|
|
705
|
+
* Without this pass the dialog paints every field of every tab, then collapses
|
|
706
|
+
* to the active tab once that first async update lands, which is a visible
|
|
707
|
+
* layout shift as soon as any field loads its options over the network.
|
|
708
|
+
*
|
|
709
|
+
* Only visibility is decided here: no `onFormChange`, no `onFormUpdate`, and
|
|
710
|
+
* the value model is left untouched, so the regular update cycle still sees
|
|
711
|
+
* the first real change and refreshes the buttons.
|
|
712
|
+
*/
|
|
713
|
+
private applyInitialLayout;
|
|
674
714
|
private initialize;
|
|
715
|
+
/**
|
|
716
|
+
* Seed one field from {@link DeclarativeFormOptions.defaultValues}.
|
|
717
|
+
*
|
|
718
|
+
* Run twice: once at build time so the first paint and the `isActive`
|
|
719
|
+
* predicates behind it already see the record being edited, and once after
|
|
720
|
+
* the `defaultValue` pass — which is async, and would otherwise overwrite the
|
|
721
|
+
* seeded value with the field's own default for the create case.
|
|
722
|
+
*/
|
|
723
|
+
private applySeedValue;
|
|
675
724
|
/** Resolves once initial options and default values have been applied. */
|
|
676
725
|
whenReady(): Promise<void>;
|
|
677
726
|
private createFieldContext;
|
|
@@ -761,6 +810,46 @@ declare class DeclarativeForm implements SubForm, StackableDialog {
|
|
|
761
810
|
resetTooltips(fieldNames: Iterable<string>): void;
|
|
762
811
|
}
|
|
763
812
|
|
|
813
|
+
/**
|
|
814
|
+
* Everything {@link DeclarativeForm} accepts, minus the three things `ask`
|
|
815
|
+
* owns: the fields it is given, and the two callbacks it turns into a promise.
|
|
816
|
+
*/
|
|
817
|
+
interface AskOptions extends Omit<DeclarativeFormOptions, 'fields' | 'onConfirm' | 'onCancel'> {
|
|
818
|
+
/** Class names for the dialog chrome, as passed to `openInModal`. */
|
|
819
|
+
modal?: OpenInModalOptions;
|
|
820
|
+
/**
|
|
821
|
+
* Whether the dialog can be dismissed with Esc or the close button.
|
|
822
|
+
*
|
|
823
|
+
* Defaults to `true`. When `false` no close button is rendered and the
|
|
824
|
+
* promise can only settle with values — so only turn it off when a button of
|
|
825
|
+
* yours is guaranteed to close the dialog.
|
|
826
|
+
*/
|
|
827
|
+
dismissable?: boolean;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Ask the user for a record, the way `prompt()` asks for a string.
|
|
831
|
+
*
|
|
832
|
+
* Opens the fields as a modal dialog and resolves with its values, or with
|
|
833
|
+
* `undefined` when the user dismisses it.
|
|
834
|
+
*
|
|
835
|
+
* ```ts
|
|
836
|
+
* const release = await ask([
|
|
837
|
+
* { name: 'title', displayName: 'Release title' },
|
|
838
|
+
* { name: 'notes', kind: 'textarea', displayName: 'What changed' },
|
|
839
|
+
* ]);
|
|
840
|
+
*
|
|
841
|
+
* if (release) publish(release);
|
|
842
|
+
* ```
|
|
843
|
+
*
|
|
844
|
+
* With custom {@link AskOptions.buttons | buttons}, the promise resolves when
|
|
845
|
+
* any button that closes the dialog has run its action; a `doNotCloseModal`
|
|
846
|
+
* button (a wizard's "Next") leaves it pending, as it leaves the dialog open.
|
|
847
|
+
*
|
|
848
|
+
* Use `new DeclarativeForm(…)` directly when you need the form object itself —
|
|
849
|
+
* to embed it in a page, subscribe to its values, or close it from elsewhere.
|
|
850
|
+
*/
|
|
851
|
+
declare function ask(fields: readonly FieldDescriptor[], options?: AskOptions): Promise<FormValues | undefined>;
|
|
852
|
+
|
|
764
853
|
/**
|
|
765
854
|
* A repeating list of sub-records, each edited in its own nested dialog.
|
|
766
855
|
*
|
|
@@ -971,6 +1060,22 @@ declare class DlOption extends CustomElementBase {
|
|
|
971
1060
|
* `document.body` while open so it can escape a scrolling container.
|
|
972
1061
|
*/
|
|
973
1062
|
declare class DlSelect extends CustomElementBase {
|
|
1063
|
+
/**
|
|
1064
|
+
* How long a load may run before the skeleton is shown at all. A load that
|
|
1065
|
+
* finishes inside this window never flashes one.
|
|
1066
|
+
*/
|
|
1067
|
+
static showLoadingAfterMs: number;
|
|
1068
|
+
/**
|
|
1069
|
+
* Once the skeleton *has* been shown, it is held for at least this long, so
|
|
1070
|
+
* it cannot appear and vanish in the same instant.
|
|
1071
|
+
*
|
|
1072
|
+
* Both are statics so a test suite can set them to 0. They are deliberate
|
|
1073
|
+
* pauses for human eyes; to a test they are pure latency, and they are why
|
|
1074
|
+
* suites end up sleeping around these selects instead of waiting on a
|
|
1075
|
+
* condition. Wait on `isLoading` rather than the `dl-select-loading` class:
|
|
1076
|
+
* `isLoading` flips synchronously, the class lags by `showLoadingAfterMs`.
|
|
1077
|
+
*/
|
|
1078
|
+
static minLoadingMs: number;
|
|
974
1079
|
readonly optionsWrapper: HTMLDivElement;
|
|
975
1080
|
readonly inputWrapper: HTMLSpanElement;
|
|
976
1081
|
readonly noMatchesHint: HTMLSpanElement;
|
|
@@ -1232,4 +1337,4 @@ declare class NativeTooltipProvider implements TooltipProvider {
|
|
|
1232
1337
|
*/
|
|
1233
1338
|
declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
1234
1339
|
|
|
1235
|
-
export { ArrayField, type ArrayFieldDescriptor, type BaseFieldDescriptor, ButtonBar, type ButtonContext, type ButtonDescriptor, type ButtonMap, CardsField, type CardsFieldDescriptor, CheckboxField, type CheckboxFieldDescriptor, ComputedField, type ComputedFieldDescriptor, CustomField, type CustomFieldDescriptor, DL_SELECT_STYLES, DeclarativeForm, type DeclarativeFormOptions, DlOption, DlSelect, Field, type FieldConstructor, type FieldContext, type FieldDescriptor, type FieldHandle, type FieldKind, type FieldMessage, FieldRegistry, FileField, type FileFieldDescriptor, type FormChangeContext, type FormContext, FormModel, type FormValues, IdGenerator, MessageField, type MessageFieldDescriptor, ModalStack, ModalView, NativeTooltipProvider, type OpenInModalOptions, type OptionsErrorContext, type Reactive, type RenderContext, SafeHtml, SelectField, type SelectFieldDescriptor, type SelectOption, type StackableDialog, type SubForm, type SubFormOptions, TabBar, type TabSpec, TextAreaField, type TextAreaFieldDescriptor, TextField, type TextFieldDescriptor, type TextOrHtml, TooltipController, type TooltipDescriptor, type TooltipProvider, UpdateScheduler, deepEqual, defaultFieldRegistry, defineDlSelect, escapeHtml, globalModalStack, html, injectStyles, isSafeHtml };
|
|
1340
|
+
export { ArrayField, type ArrayFieldDescriptor, type AskOptions, type BaseFieldDescriptor, ButtonBar, type ButtonContext, type ButtonDescriptor, type ButtonMap, CardsField, type CardsFieldDescriptor, CheckboxField, type CheckboxFieldDescriptor, ComputedField, type ComputedFieldDescriptor, CustomField, type CustomFieldDescriptor, DL_SELECT_STYLES, DeclarativeForm, type DeclarativeFormOptions, DlOption, DlSelect, Field, type FieldConstructor, type FieldContext, type FieldDescriptor, type FieldHandle, type FieldKind, type FieldMessage, FieldRegistry, FileField, type FileFieldDescriptor, type FormChangeContext, type FormContext, FormModel, type FormValues, IdGenerator, MessageField, type MessageFieldDescriptor, ModalStack, ModalView, NativeTooltipProvider, type OpenInModalOptions, type OptionsErrorContext, type Reactive, type RenderContext, SafeHtml, SelectField, type SelectFieldDescriptor, type SelectOption, type StackableDialog, type SubForm, type SubFormOptions, TabBar, type TabSpec, TextAreaField, type TextAreaFieldDescriptor, TextField, type TextFieldDescriptor, type TextOrHtml, TooltipController, type TooltipDescriptor, type TooltipProvider, UpdateScheduler, ask, deepEqual, defaultFieldRegistry, defineDlSelect, escapeHtml, globalModalStack, html, injectStyles, isSafeHtml };
|
package/dist/index.d.ts
CHANGED
|
@@ -262,6 +262,21 @@ interface ButtonDescriptor {
|
|
|
262
262
|
type ButtonMap = Record<string, ButtonDescriptor>;
|
|
263
263
|
interface DeclarativeFormOptions {
|
|
264
264
|
fields: readonly FieldDescriptor[];
|
|
265
|
+
/**
|
|
266
|
+
* The record to open the form with, keyed by field name — how you edit an
|
|
267
|
+
* existing object rather than asking for a new one.
|
|
268
|
+
*
|
|
269
|
+
* ```ts
|
|
270
|
+
* const updated = await ask(userFields, { defaultValues: user });
|
|
271
|
+
* ```
|
|
272
|
+
*
|
|
273
|
+
* Each entry wins over that field's own `defaultValue`, so the same field
|
|
274
|
+
* list serves both the "create" and the "edit" call. Keys naming no field are
|
|
275
|
+
* ignored — pass a whole domain object without filtering it first — as are
|
|
276
|
+
* keys whose value is `undefined`, which fall back to the field's
|
|
277
|
+
* `defaultValue`. An `array` field takes its list of records here directly.
|
|
278
|
+
*/
|
|
279
|
+
defaultValues?: FormValues;
|
|
265
280
|
/**
|
|
266
281
|
* Buttons for the dialog's lower bar, keyed by label.
|
|
267
282
|
* Defaults to a single `OK` button running {@link onConfirm}.
|
|
@@ -507,6 +522,16 @@ declare abstract class Field<D extends BaseFieldDescriptor = FieldDescriptor> im
|
|
|
507
522
|
* resolved before being applied.
|
|
508
523
|
*/
|
|
509
524
|
applyDefaultValue(): Promise<void>;
|
|
525
|
+
/**
|
|
526
|
+
* Apply a plainly declared default value straight away.
|
|
527
|
+
*
|
|
528
|
+
* {@link applyDefaultValue} is awaited during initialisation, so a value it
|
|
529
|
+
* applies cannot be seen by the first layout pass. A default that is neither a
|
|
530
|
+
* function nor a promise needs no awaiting, so it is applied at build time and
|
|
531
|
+
* `isActive` predicates reading it are right from the first paint. The async
|
|
532
|
+
* pass runs afterwards regardless and simply sets the same value again.
|
|
533
|
+
*/
|
|
534
|
+
applyStaticDefaultValue(): void;
|
|
510
535
|
/**
|
|
511
536
|
* Hook for setup that cannot happen in {@link build} — loading select
|
|
512
537
|
* options, or a first render that needs the form context. May be synchronous.
|
|
@@ -671,7 +696,31 @@ declare class DeclarativeForm implements SubForm, StackableDialog {
|
|
|
671
696
|
constructor(options: DeclarativeFormOptions, internal?: InternalOptions);
|
|
672
697
|
private resolveButtons;
|
|
673
698
|
private buildFields;
|
|
699
|
+
/**
|
|
700
|
+
* Decide field visibility and build the tab strip synchronously, before the
|
|
701
|
+
* caller gets the form back.
|
|
702
|
+
*
|
|
703
|
+
* {@link initialize} is async — it awaits option loads and default values —
|
|
704
|
+
* but `openInModal()` normally runs in the same tick as the constructor.
|
|
705
|
+
* Without this pass the dialog paints every field of every tab, then collapses
|
|
706
|
+
* to the active tab once that first async update lands, which is a visible
|
|
707
|
+
* layout shift as soon as any field loads its options over the network.
|
|
708
|
+
*
|
|
709
|
+
* Only visibility is decided here: no `onFormChange`, no `onFormUpdate`, and
|
|
710
|
+
* the value model is left untouched, so the regular update cycle still sees
|
|
711
|
+
* the first real change and refreshes the buttons.
|
|
712
|
+
*/
|
|
713
|
+
private applyInitialLayout;
|
|
674
714
|
private initialize;
|
|
715
|
+
/**
|
|
716
|
+
* Seed one field from {@link DeclarativeFormOptions.defaultValues}.
|
|
717
|
+
*
|
|
718
|
+
* Run twice: once at build time so the first paint and the `isActive`
|
|
719
|
+
* predicates behind it already see the record being edited, and once after
|
|
720
|
+
* the `defaultValue` pass — which is async, and would otherwise overwrite the
|
|
721
|
+
* seeded value with the field's own default for the create case.
|
|
722
|
+
*/
|
|
723
|
+
private applySeedValue;
|
|
675
724
|
/** Resolves once initial options and default values have been applied. */
|
|
676
725
|
whenReady(): Promise<void>;
|
|
677
726
|
private createFieldContext;
|
|
@@ -761,6 +810,46 @@ declare class DeclarativeForm implements SubForm, StackableDialog {
|
|
|
761
810
|
resetTooltips(fieldNames: Iterable<string>): void;
|
|
762
811
|
}
|
|
763
812
|
|
|
813
|
+
/**
|
|
814
|
+
* Everything {@link DeclarativeForm} accepts, minus the three things `ask`
|
|
815
|
+
* owns: the fields it is given, and the two callbacks it turns into a promise.
|
|
816
|
+
*/
|
|
817
|
+
interface AskOptions extends Omit<DeclarativeFormOptions, 'fields' | 'onConfirm' | 'onCancel'> {
|
|
818
|
+
/** Class names for the dialog chrome, as passed to `openInModal`. */
|
|
819
|
+
modal?: OpenInModalOptions;
|
|
820
|
+
/**
|
|
821
|
+
* Whether the dialog can be dismissed with Esc or the close button.
|
|
822
|
+
*
|
|
823
|
+
* Defaults to `true`. When `false` no close button is rendered and the
|
|
824
|
+
* promise can only settle with values — so only turn it off when a button of
|
|
825
|
+
* yours is guaranteed to close the dialog.
|
|
826
|
+
*/
|
|
827
|
+
dismissable?: boolean;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Ask the user for a record, the way `prompt()` asks for a string.
|
|
831
|
+
*
|
|
832
|
+
* Opens the fields as a modal dialog and resolves with its values, or with
|
|
833
|
+
* `undefined` when the user dismisses it.
|
|
834
|
+
*
|
|
835
|
+
* ```ts
|
|
836
|
+
* const release = await ask([
|
|
837
|
+
* { name: 'title', displayName: 'Release title' },
|
|
838
|
+
* { name: 'notes', kind: 'textarea', displayName: 'What changed' },
|
|
839
|
+
* ]);
|
|
840
|
+
*
|
|
841
|
+
* if (release) publish(release);
|
|
842
|
+
* ```
|
|
843
|
+
*
|
|
844
|
+
* With custom {@link AskOptions.buttons | buttons}, the promise resolves when
|
|
845
|
+
* any button that closes the dialog has run its action; a `doNotCloseModal`
|
|
846
|
+
* button (a wizard's "Next") leaves it pending, as it leaves the dialog open.
|
|
847
|
+
*
|
|
848
|
+
* Use `new DeclarativeForm(…)` directly when you need the form object itself —
|
|
849
|
+
* to embed it in a page, subscribe to its values, or close it from elsewhere.
|
|
850
|
+
*/
|
|
851
|
+
declare function ask(fields: readonly FieldDescriptor[], options?: AskOptions): Promise<FormValues | undefined>;
|
|
852
|
+
|
|
764
853
|
/**
|
|
765
854
|
* A repeating list of sub-records, each edited in its own nested dialog.
|
|
766
855
|
*
|
|
@@ -971,6 +1060,22 @@ declare class DlOption extends CustomElementBase {
|
|
|
971
1060
|
* `document.body` while open so it can escape a scrolling container.
|
|
972
1061
|
*/
|
|
973
1062
|
declare class DlSelect extends CustomElementBase {
|
|
1063
|
+
/**
|
|
1064
|
+
* How long a load may run before the skeleton is shown at all. A load that
|
|
1065
|
+
* finishes inside this window never flashes one.
|
|
1066
|
+
*/
|
|
1067
|
+
static showLoadingAfterMs: number;
|
|
1068
|
+
/**
|
|
1069
|
+
* Once the skeleton *has* been shown, it is held for at least this long, so
|
|
1070
|
+
* it cannot appear and vanish in the same instant.
|
|
1071
|
+
*
|
|
1072
|
+
* Both are statics so a test suite can set them to 0. They are deliberate
|
|
1073
|
+
* pauses for human eyes; to a test they are pure latency, and they are why
|
|
1074
|
+
* suites end up sleeping around these selects instead of waiting on a
|
|
1075
|
+
* condition. Wait on `isLoading` rather than the `dl-select-loading` class:
|
|
1076
|
+
* `isLoading` flips synchronously, the class lags by `showLoadingAfterMs`.
|
|
1077
|
+
*/
|
|
1078
|
+
static minLoadingMs: number;
|
|
974
1079
|
readonly optionsWrapper: HTMLDivElement;
|
|
975
1080
|
readonly inputWrapper: HTMLSpanElement;
|
|
976
1081
|
readonly noMatchesHint: HTMLSpanElement;
|
|
@@ -1232,4 +1337,4 @@ declare class NativeTooltipProvider implements TooltipProvider {
|
|
|
1232
1337
|
*/
|
|
1233
1338
|
declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
1234
1339
|
|
|
1235
|
-
export { ArrayField, type ArrayFieldDescriptor, type BaseFieldDescriptor, ButtonBar, type ButtonContext, type ButtonDescriptor, type ButtonMap, CardsField, type CardsFieldDescriptor, CheckboxField, type CheckboxFieldDescriptor, ComputedField, type ComputedFieldDescriptor, CustomField, type CustomFieldDescriptor, DL_SELECT_STYLES, DeclarativeForm, type DeclarativeFormOptions, DlOption, DlSelect, Field, type FieldConstructor, type FieldContext, type FieldDescriptor, type FieldHandle, type FieldKind, type FieldMessage, FieldRegistry, FileField, type FileFieldDescriptor, type FormChangeContext, type FormContext, FormModel, type FormValues, IdGenerator, MessageField, type MessageFieldDescriptor, ModalStack, ModalView, NativeTooltipProvider, type OpenInModalOptions, type OptionsErrorContext, type Reactive, type RenderContext, SafeHtml, SelectField, type SelectFieldDescriptor, type SelectOption, type StackableDialog, type SubForm, type SubFormOptions, TabBar, type TabSpec, TextAreaField, type TextAreaFieldDescriptor, TextField, type TextFieldDescriptor, type TextOrHtml, TooltipController, type TooltipDescriptor, type TooltipProvider, UpdateScheduler, deepEqual, defaultFieldRegistry, defineDlSelect, escapeHtml, globalModalStack, html, injectStyles, isSafeHtml };
|
|
1340
|
+
export { ArrayField, type ArrayFieldDescriptor, type AskOptions, type BaseFieldDescriptor, ButtonBar, type ButtonContext, type ButtonDescriptor, type ButtonMap, CardsField, type CardsFieldDescriptor, CheckboxField, type CheckboxFieldDescriptor, ComputedField, type ComputedFieldDescriptor, CustomField, type CustomFieldDescriptor, DL_SELECT_STYLES, DeclarativeForm, type DeclarativeFormOptions, DlOption, DlSelect, Field, type FieldConstructor, type FieldContext, type FieldDescriptor, type FieldHandle, type FieldKind, type FieldMessage, FieldRegistry, FileField, type FileFieldDescriptor, type FormChangeContext, type FormContext, FormModel, type FormValues, IdGenerator, MessageField, type MessageFieldDescriptor, ModalStack, ModalView, NativeTooltipProvider, type OpenInModalOptions, type OptionsErrorContext, type Reactive, type RenderContext, SafeHtml, SelectField, type SelectFieldDescriptor, type SelectOption, type StackableDialog, type SubForm, type SubFormOptions, TabBar, type TabSpec, TextAreaField, type TextAreaFieldDescriptor, TextField, type TextFieldDescriptor, type TextOrHtml, TooltipController, type TooltipDescriptor, type TooltipProvider, UpdateScheduler, ask, deepEqual, defaultFieldRegistry, defineDlSelect, escapeHtml, globalModalStack, html, injectStyles, isSafeHtml };
|
package/dist/index.js
CHANGED
|
@@ -193,6 +193,21 @@ var Field = class {
|
|
|
193
193
|
);
|
|
194
194
|
if (value !== void 0) this.setValue(value);
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Apply a plainly declared default value straight away.
|
|
198
|
+
*
|
|
199
|
+
* {@link applyDefaultValue} is awaited during initialisation, so a value it
|
|
200
|
+
* applies cannot be seen by the first layout pass. A default that is neither a
|
|
201
|
+
* function nor a promise needs no awaiting, so it is applied at build time and
|
|
202
|
+
* `isActive` predicates reading it are right from the first paint. The async
|
|
203
|
+
* pass runs afterwards regardless and simply sets the same value again.
|
|
204
|
+
*/
|
|
205
|
+
applyStaticDefaultValue() {
|
|
206
|
+
const value = this.descriptor.defaultValue;
|
|
207
|
+
if (value === void 0 || typeof value === "function") return;
|
|
208
|
+
if (value instanceof Promise) return;
|
|
209
|
+
this.setValue(value);
|
|
210
|
+
}
|
|
196
211
|
/**
|
|
197
212
|
* Hook for setup that cannot happen in {@link build} — loading select
|
|
198
213
|
* options, or a first render that needs the form context. May be synchronous.
|
|
@@ -927,8 +942,6 @@ function injectStyles() {
|
|
|
927
942
|
// src/components/DlSelect.ts
|
|
928
943
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
929
944
|
var ARROW_PATH = "M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z";
|
|
930
|
-
var MIN_LOADING_MS = 1500;
|
|
931
|
-
var LOADING_DELAY_MS = 100;
|
|
932
945
|
var CustomElementBase = typeof HTMLElement === "undefined" ? class {
|
|
933
946
|
} : HTMLElement;
|
|
934
947
|
var DlOption = class extends CustomElementBase {
|
|
@@ -941,7 +954,23 @@ var DlOption = class extends CustomElementBase {
|
|
|
941
954
|
return this.getAttribute("displayWhenSelected") ?? this.innerText;
|
|
942
955
|
}
|
|
943
956
|
};
|
|
944
|
-
var DlSelect = class extends CustomElementBase {
|
|
957
|
+
var DlSelect = class _DlSelect extends CustomElementBase {
|
|
958
|
+
/**
|
|
959
|
+
* How long a load may run before the skeleton is shown at all. A load that
|
|
960
|
+
* finishes inside this window never flashes one.
|
|
961
|
+
*/
|
|
962
|
+
static showLoadingAfterMs = 100;
|
|
963
|
+
/**
|
|
964
|
+
* Once the skeleton *has* been shown, it is held for at least this long, so
|
|
965
|
+
* it cannot appear and vanish in the same instant.
|
|
966
|
+
*
|
|
967
|
+
* Both are statics so a test suite can set them to 0. They are deliberate
|
|
968
|
+
* pauses for human eyes; to a test they are pure latency, and they are why
|
|
969
|
+
* suites end up sleeping around these selects instead of waiting on a
|
|
970
|
+
* condition. Wait on `isLoading` rather than the `dl-select-loading` class:
|
|
971
|
+
* `isLoading` flips synchronously, the class lags by `showLoadingAfterMs`.
|
|
972
|
+
*/
|
|
973
|
+
static minLoadingMs = 1500;
|
|
945
974
|
optionsWrapper;
|
|
946
975
|
inputWrapper;
|
|
947
976
|
noMatchesHint;
|
|
@@ -1050,14 +1079,14 @@ var DlSelect = class extends CustomElementBase {
|
|
|
1050
1079
|
this.classList.add("dl-select-loading");
|
|
1051
1080
|
this.optionsWrapper.remove();
|
|
1052
1081
|
this.loadingStartedAt = Date.now();
|
|
1053
|
-
},
|
|
1082
|
+
}, _DlSelect.showLoadingAfterMs)
|
|
1054
1083
|
);
|
|
1055
1084
|
}
|
|
1056
1085
|
unsetLoadingStatus() {
|
|
1057
1086
|
for (const timeout of this.loadingTimeouts) clearTimeout(timeout);
|
|
1058
1087
|
this.loadingTimeouts = [];
|
|
1059
1088
|
const elapsed = this.loadingStartedAt ? Date.now() - this.loadingStartedAt : 0;
|
|
1060
|
-
const delay = this.loadingStartedAt ? Math.max(0,
|
|
1089
|
+
const delay = this.loadingStartedAt ? Math.max(0, _DlSelect.minLoadingMs - elapsed) : 0;
|
|
1061
1090
|
const finish = () => {
|
|
1062
1091
|
this.appendChild(this.optionsWrapper);
|
|
1063
1092
|
this.classList.remove("dl-select-loading");
|
|
@@ -2104,6 +2133,7 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2104
2133
|
closeWith: (action) => this.close(action)
|
|
2105
2134
|
});
|
|
2106
2135
|
this.buildFields();
|
|
2136
|
+
this.applyInitialLayout();
|
|
2107
2137
|
this.ready = this.initialize();
|
|
2108
2138
|
}
|
|
2109
2139
|
// ------------------------------------------------------------------ setup
|
|
@@ -2125,12 +2155,41 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2125
2155
|
);
|
|
2126
2156
|
}
|
|
2127
2157
|
const field = this.registry.create(descriptor, context);
|
|
2158
|
+
field.applyStaticDefaultValue();
|
|
2159
|
+
this.applySeedValue(field);
|
|
2128
2160
|
this.fieldList.push(field);
|
|
2129
2161
|
this.fieldsByName.set(descriptor.name, field);
|
|
2130
2162
|
this.tooltips.register(descriptor.name, field.tooltipMarker);
|
|
2131
2163
|
this.formElement.appendChild(field.wrapper);
|
|
2132
2164
|
}
|
|
2133
2165
|
}
|
|
2166
|
+
/**
|
|
2167
|
+
* Decide field visibility and build the tab strip synchronously, before the
|
|
2168
|
+
* caller gets the form back.
|
|
2169
|
+
*
|
|
2170
|
+
* {@link initialize} is async — it awaits option loads and default values —
|
|
2171
|
+
* but `openInModal()` normally runs in the same tick as the constructor.
|
|
2172
|
+
* Without this pass the dialog paints every field of every tab, then collapses
|
|
2173
|
+
* to the active tab once that first async update lands, which is a visible
|
|
2174
|
+
* layout shift as soon as any field loads its options over the network.
|
|
2175
|
+
*
|
|
2176
|
+
* Only visibility is decided here: no `onFormChange`, no `onFormUpdate`, and
|
|
2177
|
+
* the value model is left untouched, so the regular update cycle still sees
|
|
2178
|
+
* the first real change and refreshes the buttons.
|
|
2179
|
+
*/
|
|
2180
|
+
applyInitialLayout() {
|
|
2181
|
+
const values = this.getValues();
|
|
2182
|
+
const activeTabs = /* @__PURE__ */ new Set();
|
|
2183
|
+
for (const field of this.fieldList) {
|
|
2184
|
+
const active = field.descriptor.isActive?.({
|
|
2185
|
+
...this.contextFor(field.descriptor),
|
|
2186
|
+
data: values
|
|
2187
|
+
}) ?? true;
|
|
2188
|
+
field.setActive(active);
|
|
2189
|
+
if (active) for (const tab of field.tabs()) activeTabs.add(tab);
|
|
2190
|
+
}
|
|
2191
|
+
this.renderTabs(activeTabs);
|
|
2192
|
+
}
|
|
2134
2193
|
async initialize() {
|
|
2135
2194
|
await Promise.allSettled(
|
|
2136
2195
|
this.fieldList.map(
|
|
@@ -2140,8 +2199,24 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2140
2199
|
await Promise.allSettled(
|
|
2141
2200
|
this.fieldList.map((field) => this.scheduler.track(field.applyDefaultValue()))
|
|
2142
2201
|
);
|
|
2202
|
+
for (const field of this.fieldList) this.applySeedValue(field);
|
|
2143
2203
|
await this.update();
|
|
2144
2204
|
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Seed one field from {@link DeclarativeFormOptions.defaultValues}.
|
|
2207
|
+
*
|
|
2208
|
+
* Run twice: once at build time so the first paint and the `isActive`
|
|
2209
|
+
* predicates behind it already see the record being edited, and once after
|
|
2210
|
+
* the `defaultValue` pass — which is async, and would otherwise overwrite the
|
|
2211
|
+
* seeded value with the field's own default for the create case.
|
|
2212
|
+
*/
|
|
2213
|
+
applySeedValue(field) {
|
|
2214
|
+
const values = this.options.defaultValues;
|
|
2215
|
+
if (!values) return;
|
|
2216
|
+
if (!Object.prototype.hasOwnProperty.call(values, field.name)) return;
|
|
2217
|
+
const value = values[field.name];
|
|
2218
|
+
if (value !== void 0) field.setValue(value);
|
|
2219
|
+
}
|
|
2145
2220
|
/** Resolves once initial options and default values have been applied. */
|
|
2146
2221
|
whenReady() {
|
|
2147
2222
|
return this.ready.then(() => this.scheduler.whenSettled());
|
|
@@ -2468,6 +2543,44 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2468
2543
|
}
|
|
2469
2544
|
};
|
|
2470
2545
|
|
|
2471
|
-
|
|
2546
|
+
// src/core/ask.ts
|
|
2547
|
+
function ask(fields, options = {}) {
|
|
2548
|
+
return askWith(DeclarativeForm, fields, options);
|
|
2549
|
+
}
|
|
2550
|
+
function askWith(FormClass, fields, options = {}) {
|
|
2551
|
+
const { modal, dismissable = true, buttons, ...formOptions } = options;
|
|
2552
|
+
return new Promise((resolve) => {
|
|
2553
|
+
let settled = false;
|
|
2554
|
+
const settle = (values) => {
|
|
2555
|
+
if (settled) return;
|
|
2556
|
+
settled = true;
|
|
2557
|
+
setTimeout(() => resolve(values), 0);
|
|
2558
|
+
};
|
|
2559
|
+
new FormClass({
|
|
2560
|
+
...formOptions,
|
|
2561
|
+
fields,
|
|
2562
|
+
buttons: buttons && settleOnClose(buttons, settle),
|
|
2563
|
+
onConfirm: (values) => settle(values),
|
|
2564
|
+
onCancel: dismissable ? () => settle(void 0) : void 0
|
|
2565
|
+
}).openInModal(modal);
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
function settleOnClose(buttons, settle) {
|
|
2569
|
+
return Object.fromEntries(
|
|
2570
|
+
Object.entries(buttons).map(([label, descriptor]) => [
|
|
2571
|
+
label,
|
|
2572
|
+
descriptor.doNotCloseModal ? descriptor : {
|
|
2573
|
+
...descriptor,
|
|
2574
|
+
action: async (values) => {
|
|
2575
|
+
const result = await descriptor.action?.(values);
|
|
2576
|
+
settle(values);
|
|
2577
|
+
return result;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
])
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
export { ArrayField, ButtonBar, CardsField, CheckboxField, ComputedField, CustomField, DL_SELECT_STYLES, DeclarativeForm, DlOption, DlSelect, Field, FieldRegistry, FileField, FormModel, IdGenerator, MessageField, ModalStack, ModalView, NativeTooltipProvider, SafeHtml, SelectField, TabBar, TextAreaField, TextField, TooltipController, UpdateScheduler, ask, deepEqual, defaultFieldRegistry, defineDlSelect, escapeHtml, globalModalStack, html, injectStyles, isSafeHtml };
|
|
2472
2585
|
//# sourceMappingURL=index.js.map
|
|
2473
2586
|
//# sourceMappingURL=index.js.map
|