declarative-forms 2.1.0 → 2.1.2
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 +8 -0
- package/dist/index.cjs +40 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +40 -8
- package/dist/index.js.map +1 -1
- package/package.json +10 -4
- package/styles/classic.css +15 -4
- package/styles/declarative-forms.css +14 -0
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}.
|
|
@@ -697,6 +712,15 @@ declare class DeclarativeForm implements SubForm, StackableDialog {
|
|
|
697
712
|
*/
|
|
698
713
|
private applyInitialLayout;
|
|
699
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;
|
|
700
724
|
/** Resolves once initial options and default values have been applied. */
|
|
701
725
|
whenReady(): Promise<void>;
|
|
702
726
|
private createFieldContext;
|
|
@@ -1036,6 +1060,22 @@ declare class DlOption extends CustomElementBase {
|
|
|
1036
1060
|
* `document.body` while open so it can escape a scrolling container.
|
|
1037
1061
|
*/
|
|
1038
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;
|
|
1039
1079
|
readonly optionsWrapper: HTMLDivElement;
|
|
1040
1080
|
readonly inputWrapper: HTMLSpanElement;
|
|
1041
1081
|
readonly noMatchesHint: HTMLSpanElement;
|
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}.
|
|
@@ -697,6 +712,15 @@ declare class DeclarativeForm implements SubForm, StackableDialog {
|
|
|
697
712
|
*/
|
|
698
713
|
private applyInitialLayout;
|
|
699
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;
|
|
700
724
|
/** Resolves once initial options and default values have been applied. */
|
|
701
725
|
whenReady(): Promise<void>;
|
|
702
726
|
private createFieldContext;
|
|
@@ -1036,6 +1060,22 @@ declare class DlOption extends CustomElementBase {
|
|
|
1036
1060
|
* `document.body` while open so it can escape a scrolling container.
|
|
1037
1061
|
*/
|
|
1038
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;
|
|
1039
1079
|
readonly optionsWrapper: HTMLDivElement;
|
|
1040
1080
|
readonly inputWrapper: HTMLSpanElement;
|
|
1041
1081
|
readonly noMatchesHint: HTMLSpanElement;
|
package/dist/index.js
CHANGED
|
@@ -942,8 +942,6 @@ function injectStyles() {
|
|
|
942
942
|
// src/components/DlSelect.ts
|
|
943
943
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
944
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";
|
|
945
|
-
var MIN_LOADING_MS = 1500;
|
|
946
|
-
var LOADING_DELAY_MS = 100;
|
|
947
945
|
var CustomElementBase = typeof HTMLElement === "undefined" ? class {
|
|
948
946
|
} : HTMLElement;
|
|
949
947
|
var DlOption = class extends CustomElementBase {
|
|
@@ -956,7 +954,23 @@ var DlOption = class extends CustomElementBase {
|
|
|
956
954
|
return this.getAttribute("displayWhenSelected") ?? this.innerText;
|
|
957
955
|
}
|
|
958
956
|
};
|
|
959
|
-
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;
|
|
960
974
|
optionsWrapper;
|
|
961
975
|
inputWrapper;
|
|
962
976
|
noMatchesHint;
|
|
@@ -1065,14 +1079,14 @@ var DlSelect = class extends CustomElementBase {
|
|
|
1065
1079
|
this.classList.add("dl-select-loading");
|
|
1066
1080
|
this.optionsWrapper.remove();
|
|
1067
1081
|
this.loadingStartedAt = Date.now();
|
|
1068
|
-
},
|
|
1082
|
+
}, _DlSelect.showLoadingAfterMs)
|
|
1069
1083
|
);
|
|
1070
1084
|
}
|
|
1071
1085
|
unsetLoadingStatus() {
|
|
1072
1086
|
for (const timeout of this.loadingTimeouts) clearTimeout(timeout);
|
|
1073
1087
|
this.loadingTimeouts = [];
|
|
1074
1088
|
const elapsed = this.loadingStartedAt ? Date.now() - this.loadingStartedAt : 0;
|
|
1075
|
-
const delay = this.loadingStartedAt ? Math.max(0,
|
|
1089
|
+
const delay = this.loadingStartedAt ? Math.max(0, _DlSelect.minLoadingMs - elapsed) : 0;
|
|
1076
1090
|
const finish = () => {
|
|
1077
1091
|
this.appendChild(this.optionsWrapper);
|
|
1078
1092
|
this.classList.remove("dl-select-loading");
|
|
@@ -1364,10 +1378,11 @@ var SelectField = class extends Field {
|
|
|
1364
1378
|
async loadOptions(opts = {}) {
|
|
1365
1379
|
const isCurrent = this.context.scheduler.claim(`options:${this.descriptor.name}`);
|
|
1366
1380
|
const source = this.descriptor.options;
|
|
1367
|
-
|
|
1368
|
-
const isAsync = requested instanceof Promise;
|
|
1369
|
-
if (isAsync) this.select.setLoadingStatus();
|
|
1381
|
+
let isAsync = false;
|
|
1370
1382
|
try {
|
|
1383
|
+
const requested = typeof source === "function" ? source(this.context.contextFor(this.descriptor)) : source;
|
|
1384
|
+
isAsync = requested instanceof Promise;
|
|
1385
|
+
if (isAsync) this.select.setLoadingStatus();
|
|
1371
1386
|
const options = await requested ?? [];
|
|
1372
1387
|
if (!isCurrent()) return;
|
|
1373
1388
|
if (opts.reload) this.context.tooltips.reset(this.descriptor.name);
|
|
@@ -2142,6 +2157,7 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2142
2157
|
}
|
|
2143
2158
|
const field = this.registry.create(descriptor, context);
|
|
2144
2159
|
field.applyStaticDefaultValue();
|
|
2160
|
+
this.applySeedValue(field);
|
|
2145
2161
|
this.fieldList.push(field);
|
|
2146
2162
|
this.fieldsByName.set(descriptor.name, field);
|
|
2147
2163
|
this.tooltips.register(descriptor.name, field.tooltipMarker);
|
|
@@ -2184,8 +2200,24 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2184
2200
|
await Promise.allSettled(
|
|
2185
2201
|
this.fieldList.map((field) => this.scheduler.track(field.applyDefaultValue()))
|
|
2186
2202
|
);
|
|
2203
|
+
for (const field of this.fieldList) this.applySeedValue(field);
|
|
2187
2204
|
await this.update();
|
|
2188
2205
|
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Seed one field from {@link DeclarativeFormOptions.defaultValues}.
|
|
2208
|
+
*
|
|
2209
|
+
* Run twice: once at build time so the first paint and the `isActive`
|
|
2210
|
+
* predicates behind it already see the record being edited, and once after
|
|
2211
|
+
* the `defaultValue` pass — which is async, and would otherwise overwrite the
|
|
2212
|
+
* seeded value with the field's own default for the create case.
|
|
2213
|
+
*/
|
|
2214
|
+
applySeedValue(field) {
|
|
2215
|
+
const values = this.options.defaultValues;
|
|
2216
|
+
if (!values) return;
|
|
2217
|
+
if (!Object.prototype.hasOwnProperty.call(values, field.name)) return;
|
|
2218
|
+
const value = values[field.name];
|
|
2219
|
+
if (value !== void 0) field.setValue(value);
|
|
2220
|
+
}
|
|
2189
2221
|
/** Resolves once initial options and default values have been applied. */
|
|
2190
2222
|
whenReady() {
|
|
2191
2223
|
return this.ready.then(() => this.scheduler.whenSettled());
|