declarative-forms 2.1.1 → 2.1.4
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.cjs +83 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -5
- package/dist/index.d.ts +52 -5
- package/dist/index.js +83 -15
- package/dist/index.js.map +1 -1
- package/package.json +10 -4
- package/styles/classic.css +15 -4
package/dist/index.d.cts
CHANGED
|
@@ -372,18 +372,45 @@ declare class TooltipController {
|
|
|
372
372
|
*/
|
|
373
373
|
declare class UpdateScheduler {
|
|
374
374
|
private pending;
|
|
375
|
+
private updates;
|
|
375
376
|
private generations;
|
|
376
377
|
/** Register async work and drop it from the set once it settles. */
|
|
377
378
|
track<T>(work: Promise<T>): Promise<T>;
|
|
379
|
+
/**
|
|
380
|
+
* Register an `update()` pass.
|
|
381
|
+
*
|
|
382
|
+
* Update passes are tracked separately from field work because `update()`
|
|
383
|
+
* waits for field work *as part of* running, so a single set would make it
|
|
384
|
+
* wait for itself. `whenSettled` drains both; `whenFieldWorkSettled`, which
|
|
385
|
+
* is what `update()` itself calls, drains only `pending`.
|
|
386
|
+
*
|
|
387
|
+
* Tracking these at all is the point: `requestUpdate` fires an update and
|
|
388
|
+
* returns, so before this existed `whenSettled()` could resolve with an
|
|
389
|
+
* update still running and the button bar not yet refreshed. `ButtonBar`
|
|
390
|
+
* awaits `whenSettled()` and then re-reads the primary button's `disabled`
|
|
391
|
+
* class to decide whether to accept a confirm -- against a stale class, that
|
|
392
|
+
* check passes and an invalid record is confirmed.
|
|
393
|
+
*/
|
|
394
|
+
trackUpdate<T>(work: Promise<T>): Promise<T>;
|
|
378
395
|
/** Number of currently in-flight operations. Test/diagnostics only. */
|
|
379
396
|
get pendingCount(): number;
|
|
380
397
|
/**
|
|
381
|
-
* Resolve once nothing is in flight
|
|
398
|
+
* Resolve once nothing is in flight: no field work, and no update pass that
|
|
399
|
+
* might still be about to act on it.
|
|
400
|
+
*/
|
|
401
|
+
whenSettled(): Promise<void>;
|
|
402
|
+
/**
|
|
403
|
+
* Resolve once field work is done, ignoring update passes.
|
|
382
404
|
*
|
|
405
|
+
* For `update()`'s own use. It cannot wait on `whenSettled`, because it is
|
|
406
|
+
* itself one of the update passes that would be waited for.
|
|
407
|
+
*/
|
|
408
|
+
whenFieldWorkSettled(): Promise<void>;
|
|
409
|
+
/**
|
|
383
410
|
* Loops because settling one batch may schedule another (an options load
|
|
384
411
|
* whose completion triggers a dependent field's reload).
|
|
385
412
|
*/
|
|
386
|
-
|
|
413
|
+
private drain;
|
|
387
414
|
/**
|
|
388
415
|
* Claim the next generation for `key` and return a predicate that reports
|
|
389
416
|
* whether that claim is still the newest.
|
|
@@ -425,10 +452,12 @@ interface FormContext {
|
|
|
425
452
|
/**
|
|
426
453
|
* Build a nested form that knows this form as its parent.
|
|
427
454
|
*
|
|
428
|
-
* Lets `ArrayField` open entry dialogs without importing `DeclarativeForm
|
|
429
|
-
* keeping the field layer free of a cycle back into the core.
|
|
455
|
+
* Lets `ArrayField` open entry dialogs without importing `DeclarativeForm` at
|
|
456
|
+
* runtime, keeping the field layer free of a cycle back into the core. The
|
|
457
|
+
* return type is the full form so the dialog can be handed to descriptor
|
|
458
|
+
* callbacks as `ctx.form`.
|
|
430
459
|
*/
|
|
431
|
-
createSubForm(options: SubFormOptions):
|
|
460
|
+
createSubForm(options: SubFormOptions): DeclarativeForm;
|
|
432
461
|
}
|
|
433
462
|
interface SubFormOptions {
|
|
434
463
|
fields: readonly FieldDescriptor[];
|
|
@@ -749,6 +778,7 @@ declare class DeclarativeForm implements SubForm, StackableDialog {
|
|
|
749
778
|
* @param includeTab treat a tab switch as a change
|
|
750
779
|
*/
|
|
751
780
|
update(source?: FieldHandle, force?: boolean, includeTab?: boolean): Promise<void>;
|
|
781
|
+
private runUpdate;
|
|
752
782
|
private buttonContext;
|
|
753
783
|
/** Recompute every `computed` field. Runs before a button action fires. */
|
|
754
784
|
updateComputedFields(): Promise<void>;
|
|
@@ -877,6 +907,15 @@ declare class ArrayField extends Field<ArrayFieldDescriptor> {
|
|
|
877
907
|
private openEntryDialog;
|
|
878
908
|
/** Seed each field with the entry's current value, then apply `mapFieldsOnEdit`. */
|
|
879
909
|
private editFields;
|
|
910
|
+
/**
|
|
911
|
+
* Run `isValidRecord` against the entry dialog.
|
|
912
|
+
*
|
|
913
|
+
* `ctx.form` is the entry dialog rather than the form owning the array field:
|
|
914
|
+
* the entry's fields — and their tooltip markers — live in the dialog, and
|
|
915
|
+
* tooltips are scoped per form, so `ctx.form.setTooltipError(…)` on the outer
|
|
916
|
+
* form was a silent no-op. The rest of the context still describes the outer
|
|
917
|
+
* field.
|
|
918
|
+
*/
|
|
880
919
|
private validateEntry;
|
|
881
920
|
private notifyChange;
|
|
882
921
|
}
|
|
@@ -989,6 +1028,8 @@ declare class MessageField extends Field<MessageFieldDescriptor> {
|
|
|
989
1028
|
/** A `<dl-select>` combobox, single or multiple. */
|
|
990
1029
|
declare class SelectField extends Field<SelectFieldDescriptor> {
|
|
991
1030
|
private select;
|
|
1031
|
+
/** True while the tooltip shows a message from {@link reportOptionsError}. */
|
|
1032
|
+
private showsOptionsError;
|
|
992
1033
|
protected get wrapperExtraClass(): string;
|
|
993
1034
|
protected createControl(): HTMLElement;
|
|
994
1035
|
initialize(): Promise<void>;
|
|
@@ -1179,6 +1220,12 @@ declare function injectStyles(): void;
|
|
|
1179
1220
|
interface ButtonBarCallbacks {
|
|
1180
1221
|
/** Resolve once all pending form work has settled. */
|
|
1181
1222
|
whenSettled(): Promise<void>;
|
|
1223
|
+
/**
|
|
1224
|
+
* Claim the newest generation for `key`; the returned predicate reports
|
|
1225
|
+
* whether that claim is still current. Used to discard a stale async
|
|
1226
|
+
* predicate result -- see `refresh`.
|
|
1227
|
+
*/
|
|
1228
|
+
claim(key: string): () => boolean;
|
|
1182
1229
|
/** Recompute `computed` fields before an action runs. */
|
|
1183
1230
|
updateComputedFields(): Promise<void>;
|
|
1184
1231
|
getValues(): Record<string, unknown>;
|
package/dist/index.d.ts
CHANGED
|
@@ -372,18 +372,45 @@ declare class TooltipController {
|
|
|
372
372
|
*/
|
|
373
373
|
declare class UpdateScheduler {
|
|
374
374
|
private pending;
|
|
375
|
+
private updates;
|
|
375
376
|
private generations;
|
|
376
377
|
/** Register async work and drop it from the set once it settles. */
|
|
377
378
|
track<T>(work: Promise<T>): Promise<T>;
|
|
379
|
+
/**
|
|
380
|
+
* Register an `update()` pass.
|
|
381
|
+
*
|
|
382
|
+
* Update passes are tracked separately from field work because `update()`
|
|
383
|
+
* waits for field work *as part of* running, so a single set would make it
|
|
384
|
+
* wait for itself. `whenSettled` drains both; `whenFieldWorkSettled`, which
|
|
385
|
+
* is what `update()` itself calls, drains only `pending`.
|
|
386
|
+
*
|
|
387
|
+
* Tracking these at all is the point: `requestUpdate` fires an update and
|
|
388
|
+
* returns, so before this existed `whenSettled()` could resolve with an
|
|
389
|
+
* update still running and the button bar not yet refreshed. `ButtonBar`
|
|
390
|
+
* awaits `whenSettled()` and then re-reads the primary button's `disabled`
|
|
391
|
+
* class to decide whether to accept a confirm -- against a stale class, that
|
|
392
|
+
* check passes and an invalid record is confirmed.
|
|
393
|
+
*/
|
|
394
|
+
trackUpdate<T>(work: Promise<T>): Promise<T>;
|
|
378
395
|
/** Number of currently in-flight operations. Test/diagnostics only. */
|
|
379
396
|
get pendingCount(): number;
|
|
380
397
|
/**
|
|
381
|
-
* Resolve once nothing is in flight
|
|
398
|
+
* Resolve once nothing is in flight: no field work, and no update pass that
|
|
399
|
+
* might still be about to act on it.
|
|
400
|
+
*/
|
|
401
|
+
whenSettled(): Promise<void>;
|
|
402
|
+
/**
|
|
403
|
+
* Resolve once field work is done, ignoring update passes.
|
|
382
404
|
*
|
|
405
|
+
* For `update()`'s own use. It cannot wait on `whenSettled`, because it is
|
|
406
|
+
* itself one of the update passes that would be waited for.
|
|
407
|
+
*/
|
|
408
|
+
whenFieldWorkSettled(): Promise<void>;
|
|
409
|
+
/**
|
|
383
410
|
* Loops because settling one batch may schedule another (an options load
|
|
384
411
|
* whose completion triggers a dependent field's reload).
|
|
385
412
|
*/
|
|
386
|
-
|
|
413
|
+
private drain;
|
|
387
414
|
/**
|
|
388
415
|
* Claim the next generation for `key` and return a predicate that reports
|
|
389
416
|
* whether that claim is still the newest.
|
|
@@ -425,10 +452,12 @@ interface FormContext {
|
|
|
425
452
|
/**
|
|
426
453
|
* Build a nested form that knows this form as its parent.
|
|
427
454
|
*
|
|
428
|
-
* Lets `ArrayField` open entry dialogs without importing `DeclarativeForm
|
|
429
|
-
* keeping the field layer free of a cycle back into the core.
|
|
455
|
+
* Lets `ArrayField` open entry dialogs without importing `DeclarativeForm` at
|
|
456
|
+
* runtime, keeping the field layer free of a cycle back into the core. The
|
|
457
|
+
* return type is the full form so the dialog can be handed to descriptor
|
|
458
|
+
* callbacks as `ctx.form`.
|
|
430
459
|
*/
|
|
431
|
-
createSubForm(options: SubFormOptions):
|
|
460
|
+
createSubForm(options: SubFormOptions): DeclarativeForm;
|
|
432
461
|
}
|
|
433
462
|
interface SubFormOptions {
|
|
434
463
|
fields: readonly FieldDescriptor[];
|
|
@@ -749,6 +778,7 @@ declare class DeclarativeForm implements SubForm, StackableDialog {
|
|
|
749
778
|
* @param includeTab treat a tab switch as a change
|
|
750
779
|
*/
|
|
751
780
|
update(source?: FieldHandle, force?: boolean, includeTab?: boolean): Promise<void>;
|
|
781
|
+
private runUpdate;
|
|
752
782
|
private buttonContext;
|
|
753
783
|
/** Recompute every `computed` field. Runs before a button action fires. */
|
|
754
784
|
updateComputedFields(): Promise<void>;
|
|
@@ -877,6 +907,15 @@ declare class ArrayField extends Field<ArrayFieldDescriptor> {
|
|
|
877
907
|
private openEntryDialog;
|
|
878
908
|
/** Seed each field with the entry's current value, then apply `mapFieldsOnEdit`. */
|
|
879
909
|
private editFields;
|
|
910
|
+
/**
|
|
911
|
+
* Run `isValidRecord` against the entry dialog.
|
|
912
|
+
*
|
|
913
|
+
* `ctx.form` is the entry dialog rather than the form owning the array field:
|
|
914
|
+
* the entry's fields — and their tooltip markers — live in the dialog, and
|
|
915
|
+
* tooltips are scoped per form, so `ctx.form.setTooltipError(…)` on the outer
|
|
916
|
+
* form was a silent no-op. The rest of the context still describes the outer
|
|
917
|
+
* field.
|
|
918
|
+
*/
|
|
880
919
|
private validateEntry;
|
|
881
920
|
private notifyChange;
|
|
882
921
|
}
|
|
@@ -989,6 +1028,8 @@ declare class MessageField extends Field<MessageFieldDescriptor> {
|
|
|
989
1028
|
/** A `<dl-select>` combobox, single or multiple. */
|
|
990
1029
|
declare class SelectField extends Field<SelectFieldDescriptor> {
|
|
991
1030
|
private select;
|
|
1031
|
+
/** True while the tooltip shows a message from {@link reportOptionsError}. */
|
|
1032
|
+
private showsOptionsError;
|
|
992
1033
|
protected get wrapperExtraClass(): string;
|
|
993
1034
|
protected createControl(): HTMLElement;
|
|
994
1035
|
initialize(): Promise<void>;
|
|
@@ -1179,6 +1220,12 @@ declare function injectStyles(): void;
|
|
|
1179
1220
|
interface ButtonBarCallbacks {
|
|
1180
1221
|
/** Resolve once all pending form work has settled. */
|
|
1181
1222
|
whenSettled(): Promise<void>;
|
|
1223
|
+
/**
|
|
1224
|
+
* Claim the newest generation for `key`; the returned predicate reports
|
|
1225
|
+
* whether that claim is still current. Used to discard a stale async
|
|
1226
|
+
* predicate result -- see `refresh`.
|
|
1227
|
+
*/
|
|
1228
|
+
claim(key: string): () => boolean;
|
|
1182
1229
|
/** Recompute `computed` fields before an action runs. */
|
|
1183
1230
|
updateComputedFields(): Promise<void>;
|
|
1184
1231
|
getValues(): Record<string, unknown>;
|
package/dist/index.js
CHANGED
|
@@ -486,7 +486,7 @@ var ArrayField = class extends Field {
|
|
|
486
486
|
buttons: {
|
|
487
487
|
OK: {
|
|
488
488
|
id: confirmId,
|
|
489
|
-
isActive: ({ data }) => this.validateEntry(data),
|
|
489
|
+
isActive: ({ data }) => this.validateEntry(data, form),
|
|
490
490
|
action: (values) => {
|
|
491
491
|
if (editing && index !== void 0) this.entries[index] = values;
|
|
492
492
|
else this.entries.push(values);
|
|
@@ -507,9 +507,21 @@ var ArrayField = class extends Field {
|
|
|
507
507
|
}));
|
|
508
508
|
return this.descriptor.mapFieldsOnEdit?.(seeded, entry) ?? seeded;
|
|
509
509
|
}
|
|
510
|
-
|
|
510
|
+
/**
|
|
511
|
+
* Run `isValidRecord` against the entry dialog.
|
|
512
|
+
*
|
|
513
|
+
* `ctx.form` is the entry dialog rather than the form owning the array field:
|
|
514
|
+
* the entry's fields — and their tooltip markers — live in the dialog, and
|
|
515
|
+
* tooltips are scoped per form, so `ctx.form.setTooltipError(…)` on the outer
|
|
516
|
+
* form was a silent no-op. The rest of the context still describes the outer
|
|
517
|
+
* field.
|
|
518
|
+
*/
|
|
519
|
+
validateEntry(entry, dialog) {
|
|
511
520
|
if (!this.descriptor.isValidRecord) return true;
|
|
512
|
-
return this.descriptor.isValidRecord(entry,
|
|
521
|
+
return this.descriptor.isValidRecord(entry, {
|
|
522
|
+
...this.context.contextFor(this.descriptor),
|
|
523
|
+
form: dialog
|
|
524
|
+
});
|
|
513
525
|
}
|
|
514
526
|
notifyChange() {
|
|
515
527
|
const onChange = this.descriptor.onChange;
|
|
@@ -1327,6 +1339,8 @@ function defineDlSelect() {
|
|
|
1327
1339
|
// src/fields/SelectField.ts
|
|
1328
1340
|
var SelectField = class extends Field {
|
|
1329
1341
|
select;
|
|
1342
|
+
/** True while the tooltip shows a message from {@link reportOptionsError}. */
|
|
1343
|
+
showsOptionsError = false;
|
|
1330
1344
|
get wrapperExtraClass() {
|
|
1331
1345
|
return "dl-select-wrapper";
|
|
1332
1346
|
}
|
|
@@ -1364,7 +1378,7 @@ var SelectField = class extends Field {
|
|
|
1364
1378
|
}
|
|
1365
1379
|
const dependsOnTrigger = triggerName !== void 0 && this.descriptor.reloadOnChangeOf?.includes(triggerName) === true;
|
|
1366
1380
|
if (dependsOnTrigger && typeof this.descriptor.options === "function") {
|
|
1367
|
-
void this.loadOptions(
|
|
1381
|
+
void this.loadOptions();
|
|
1368
1382
|
}
|
|
1369
1383
|
}
|
|
1370
1384
|
/**
|
|
@@ -1375,16 +1389,20 @@ var SelectField = class extends Field {
|
|
|
1375
1389
|
* fresher options. v1 had no such guard, so a slow response from an earlier
|
|
1376
1390
|
* keystroke could repopulate the select with outdated values.
|
|
1377
1391
|
*/
|
|
1378
|
-
async loadOptions(
|
|
1392
|
+
async loadOptions() {
|
|
1379
1393
|
const isCurrent = this.context.scheduler.claim(`options:${this.descriptor.name}`);
|
|
1380
1394
|
const source = this.descriptor.options;
|
|
1381
|
-
|
|
1382
|
-
const isAsync = requested instanceof Promise;
|
|
1383
|
-
if (isAsync) this.select.setLoadingStatus();
|
|
1395
|
+
let isAsync = false;
|
|
1384
1396
|
try {
|
|
1397
|
+
const requested = typeof source === "function" ? source(this.context.contextFor(this.descriptor)) : source;
|
|
1398
|
+
isAsync = requested instanceof Promise;
|
|
1399
|
+
if (isAsync) this.select.setLoadingStatus();
|
|
1385
1400
|
const options = await requested ?? [];
|
|
1386
1401
|
if (!isCurrent()) return;
|
|
1387
|
-
if (
|
|
1402
|
+
if (this.showsOptionsError) {
|
|
1403
|
+
this.context.tooltips.reset(this.descriptor.name);
|
|
1404
|
+
this.showsOptionsError = false;
|
|
1405
|
+
}
|
|
1388
1406
|
const previous = this.select.getValue();
|
|
1389
1407
|
this.select.removeAllOptions();
|
|
1390
1408
|
for (const option of options) this.select.addOption(this.createOption(option));
|
|
@@ -1418,6 +1436,7 @@ var SelectField = class extends Field {
|
|
|
1418
1436
|
});
|
|
1419
1437
|
if (message) {
|
|
1420
1438
|
this.context.tooltips.setMessage(this.descriptor.name, message.level, message.text);
|
|
1439
|
+
this.showsOptionsError = true;
|
|
1421
1440
|
}
|
|
1422
1441
|
}
|
|
1423
1442
|
};
|
|
@@ -1599,6 +1618,7 @@ var IdGenerator = class {
|
|
|
1599
1618
|
// src/core/UpdateScheduler.ts
|
|
1600
1619
|
var UpdateScheduler = class {
|
|
1601
1620
|
pending = /* @__PURE__ */ new Set();
|
|
1621
|
+
updates = /* @__PURE__ */ new Set();
|
|
1602
1622
|
generations = /* @__PURE__ */ new Map();
|
|
1603
1623
|
/** Register async work and drop it from the set once it settles. */
|
|
1604
1624
|
track(work) {
|
|
@@ -1609,25 +1629,64 @@ var UpdateScheduler = class {
|
|
|
1609
1629
|
work.then(forget, forget);
|
|
1610
1630
|
return work;
|
|
1611
1631
|
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Register an `update()` pass.
|
|
1634
|
+
*
|
|
1635
|
+
* Update passes are tracked separately from field work because `update()`
|
|
1636
|
+
* waits for field work *as part of* running, so a single set would make it
|
|
1637
|
+
* wait for itself. `whenSettled` drains both; `whenFieldWorkSettled`, which
|
|
1638
|
+
* is what `update()` itself calls, drains only `pending`.
|
|
1639
|
+
*
|
|
1640
|
+
* Tracking these at all is the point: `requestUpdate` fires an update and
|
|
1641
|
+
* returns, so before this existed `whenSettled()` could resolve with an
|
|
1642
|
+
* update still running and the button bar not yet refreshed. `ButtonBar`
|
|
1643
|
+
* awaits `whenSettled()` and then re-reads the primary button's `disabled`
|
|
1644
|
+
* class to decide whether to accept a confirm -- against a stale class, that
|
|
1645
|
+
* check passes and an invalid record is confirmed.
|
|
1646
|
+
*/
|
|
1647
|
+
trackUpdate(work) {
|
|
1648
|
+
this.updates.add(work);
|
|
1649
|
+
const forget = () => {
|
|
1650
|
+
this.updates.delete(work);
|
|
1651
|
+
};
|
|
1652
|
+
work.then(forget, forget);
|
|
1653
|
+
return work;
|
|
1654
|
+
}
|
|
1612
1655
|
/** Number of currently in-flight operations. Test/diagnostics only. */
|
|
1613
1656
|
get pendingCount() {
|
|
1614
|
-
return this.pending.size;
|
|
1657
|
+
return this.pending.size + this.updates.size;
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Resolve once nothing is in flight: no field work, and no update pass that
|
|
1661
|
+
* might still be about to act on it.
|
|
1662
|
+
*/
|
|
1663
|
+
async whenSettled() {
|
|
1664
|
+
await this.drain(() => [...this.pending, ...this.updates]);
|
|
1615
1665
|
}
|
|
1616
1666
|
/**
|
|
1617
|
-
* Resolve once
|
|
1667
|
+
* Resolve once field work is done, ignoring update passes.
|
|
1618
1668
|
*
|
|
1669
|
+
* For `update()`'s own use. It cannot wait on `whenSettled`, because it is
|
|
1670
|
+
* itself one of the update passes that would be waited for.
|
|
1671
|
+
*/
|
|
1672
|
+
async whenFieldWorkSettled() {
|
|
1673
|
+
await this.drain(() => [...this.pending]);
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1619
1676
|
* Loops because settling one batch may schedule another (an options load
|
|
1620
1677
|
* whose completion triggers a dependent field's reload).
|
|
1621
1678
|
*/
|
|
1622
|
-
async
|
|
1679
|
+
async drain(snapshot) {
|
|
1623
1680
|
let guard = 0;
|
|
1624
|
-
|
|
1681
|
+
let outstanding = snapshot();
|
|
1682
|
+
while (outstanding.length > 0) {
|
|
1625
1683
|
if (++guard > 100) {
|
|
1626
1684
|
throw new Error(
|
|
1627
1685
|
"declarative-forms: form updates did not settle after 100 rounds \u2014 a field callback is most likely scheduling work on every update."
|
|
1628
1686
|
);
|
|
1629
1687
|
}
|
|
1630
|
-
await Promise.allSettled(
|
|
1688
|
+
await Promise.allSettled(outstanding);
|
|
1689
|
+
outstanding = snapshot();
|
|
1631
1690
|
}
|
|
1632
1691
|
}
|
|
1633
1692
|
/**
|
|
@@ -1648,6 +1707,7 @@ var UpdateScheduler = class {
|
|
|
1648
1707
|
/** Invalidate every outstanding claim and forget all pending work. */
|
|
1649
1708
|
reset() {
|
|
1650
1709
|
this.pending.clear();
|
|
1710
|
+
this.updates.clear();
|
|
1651
1711
|
for (const key of this.generations.keys()) {
|
|
1652
1712
|
this.generations.set(key, (this.generations.get(key) ?? 0) + 1);
|
|
1653
1713
|
}
|
|
@@ -1722,16 +1782,20 @@ var ButtonBar = class {
|
|
|
1722
1782
|
for (const { descriptor, element } of this.buttons) {
|
|
1723
1783
|
if (!descriptor.id) continue;
|
|
1724
1784
|
if (descriptor.isActive) {
|
|
1785
|
+
const isCurrent = this.callbacks.claim(`button:isActive:${descriptor.id}`);
|
|
1725
1786
|
work.push(
|
|
1726
1787
|
this.applyPredicate(descriptor.isActive(ctx), (active) => {
|
|
1788
|
+
if (!isCurrent()) return;
|
|
1727
1789
|
element.classList.toggle("disabled", !active);
|
|
1728
1790
|
element.toggleAttribute("disabled", !active);
|
|
1729
1791
|
})
|
|
1730
1792
|
);
|
|
1731
1793
|
}
|
|
1732
1794
|
if (descriptor.isVisible) {
|
|
1795
|
+
const isCurrent = this.callbacks.claim(`button:isVisible:${descriptor.id}`);
|
|
1733
1796
|
work.push(
|
|
1734
1797
|
this.applyPredicate(descriptor.isVisible(ctx), (visible) => {
|
|
1798
|
+
if (!isCurrent()) return;
|
|
1735
1799
|
element.classList.toggle("invisible", !visible);
|
|
1736
1800
|
})
|
|
1737
1801
|
);
|
|
@@ -2128,6 +2192,7 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2128
2192
|
this.tabBar = new TabBar((tab) => this.setActiveTab(tab));
|
|
2129
2193
|
this.buttonBar = new ButtonBar(this.resolveButtons(), {
|
|
2130
2194
|
whenSettled: () => this.scheduler.whenSettled(),
|
|
2195
|
+
claim: (key) => this.scheduler.claim(key),
|
|
2131
2196
|
updateComputedFields: () => this.updateComputedFields(),
|
|
2132
2197
|
getValues: () => this.getValues(),
|
|
2133
2198
|
closeWith: (action) => this.close(action)
|
|
@@ -2324,6 +2389,9 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2324
2389
|
* @param includeTab treat a tab switch as a change
|
|
2325
2390
|
*/
|
|
2326
2391
|
async update(source, force = false, includeTab = false) {
|
|
2392
|
+
return this.scheduler.trackUpdate(this.runUpdate(source, force, includeTab));
|
|
2393
|
+
}
|
|
2394
|
+
async runUpdate(source, force = false, includeTab = false) {
|
|
2327
2395
|
const values = this.getValues();
|
|
2328
2396
|
const changed = this.model.hasChanged(values, includeTab);
|
|
2329
2397
|
if (!changed && !force) return;
|
|
@@ -2347,7 +2415,7 @@ var DeclarativeForm = class _DeclarativeForm {
|
|
|
2347
2415
|
field.onFormUpdate(ctx, triggerName);
|
|
2348
2416
|
}
|
|
2349
2417
|
this.renderTabs(activeTabs);
|
|
2350
|
-
await this.scheduler.
|
|
2418
|
+
await this.scheduler.whenFieldWorkSettled();
|
|
2351
2419
|
if (changed) this.model.notify(this.getValues());
|
|
2352
2420
|
await this.buttonBar.refresh(this.buttonContext());
|
|
2353
2421
|
}
|