@optionfactory/fml 9.0.0-rc19 → 9.0.0-rc20
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/custom-elements.json +21 -0
- package/dist/fml.css +2 -2
- package/dist/fml.css.map +1 -1
- package/dist/fml.iife.js +264 -3
- package/dist/fml.iife.js.map +1 -1
- package/dist/fml.iife.min.js +1 -1
- package/dist/fml.iife.min.js.map +1 -1
- package/dist/ful.css +2 -2
- package/dist/ful.css.map +1 -1
- package/dist/ful.d.mts +63 -4
- package/dist/ful.iife.js +263 -3
- package/dist/ful.iife.js.map +1 -1
- package/dist/ful.iife.min.js +1 -1
- package/dist/ful.iife.min.js.map +1 -1
- package/dist/ful.min.mjs +1 -1
- package/dist/ful.min.mjs.map +1 -1
- package/dist/ful.mjs +264 -5
- package/dist/ful.mjs.map +1 -1
- package/dist/vscode.html-custom-data.json +8 -0
- package/dist/web-types.json +21 -1
- package/package.json +1 -1
package/dist/ful.d.mts
CHANGED
|
@@ -123,6 +123,20 @@ declare class AsyncEvents {
|
|
|
123
123
|
*/
|
|
124
124
|
static mixInto(...classes: Function[]): void;
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Asks before the page is left, through the browser's own `beforeunload`
|
|
128
|
+
* prompt.
|
|
129
|
+
*/
|
|
130
|
+
declare class PageLeave {
|
|
131
|
+
/**
|
|
132
|
+
* Asks before the page is left while `isDirty()` answers true, until
|
|
133
|
+
* `signal` aborts. The browser words the prompt, and asks only once the
|
|
134
|
+
* user has interacted with the page.
|
|
135
|
+
* @param {() => boolean} isDirty
|
|
136
|
+
* @param {AbortSignal} [signal]
|
|
137
|
+
*/
|
|
138
|
+
static guard(isDirty: () => boolean, signal?: AbortSignal): void;
|
|
139
|
+
}
|
|
126
140
|
export type Claim = {
|
|
127
141
|
readonly stale: boolean;
|
|
128
142
|
};
|
|
@@ -722,6 +736,10 @@ declare class FormLoader {
|
|
|
722
736
|
*
|
|
723
737
|
* A native submit, by a button or by Enter in a field, is stopped and runs
|
|
724
738
|
* `submit` instead.
|
|
739
|
+
*
|
|
740
|
+
* `track-changes` keeps track of the edits not saved yet (see `dirty`), and
|
|
741
|
+
* `guard-leave`, which implies it, asks before the page is left while there
|
|
742
|
+
* are any.
|
|
725
743
|
*/
|
|
726
744
|
declare class Form extends ParsedElement {
|
|
727
745
|
#private;
|
|
@@ -731,6 +749,8 @@ declare class Form extends ParsedElement {
|
|
|
731
749
|
form: any;
|
|
732
750
|
/** Builds the native form and moves the element's children into it. */
|
|
733
751
|
render(): void;
|
|
752
|
+
connectedCallback(): void;
|
|
753
|
+
disconnectedCallback(): void;
|
|
734
754
|
/**
|
|
735
755
|
* Runs the submit pipeline, with the spinner up for its whole length:
|
|
736
756
|
*
|
|
@@ -747,7 +767,9 @@ declare class Form extends ParsedElement {
|
|
|
747
767
|
* loader's `submit` as its third argument. A listener may replace
|
|
748
768
|
* `request` on the detail
|
|
749
769
|
* 6. has the loader `submit` the request and `transform` the response
|
|
750
|
-
* 7.
|
|
770
|
+
* 7. under `track-changes`, commits the values extracted at step 2 (see
|
|
771
|
+
* `commit`): an edit made while the submit was in flight stays unsaved
|
|
772
|
+
* 8. fires `submit:success` with `{ submitter, values, request, response }`,
|
|
751
773
|
* `response` being the transformed one
|
|
752
774
|
*
|
|
753
775
|
* Anything thrown from step 1 on, a missing loader or a throwing mapper
|
|
@@ -763,8 +785,43 @@ declare class Form extends ParsedElement {
|
|
|
763
785
|
* @returns {Promise<void>} settling once the submit has finished, whatever its outcome
|
|
764
786
|
*/
|
|
765
787
|
submit(submitter?: HTMLElement): Promise<void>;
|
|
766
|
-
/**
|
|
788
|
+
/**
|
|
789
|
+
* Resets the native form, each field returning to its default value. Under
|
|
790
|
+
* `track-changes` every control counts as edited: the form is dirty if the
|
|
791
|
+
* defaults differ from what was last committed.
|
|
792
|
+
*/
|
|
767
793
|
reset(): void;
|
|
794
|
+
/**
|
|
795
|
+
* Whether, under `track-changes`, a control the user edited since the last
|
|
796
|
+
* commit holds a value other than the one committed. An edit is what the
|
|
797
|
+
* controls report as one: an `input` or a `change` bubbling from a control
|
|
798
|
+
* of this form (0019), so assigning `value`, a select's vocabulary dropping
|
|
799
|
+
* a key or a section delivering its fields is not one, and an edit undone
|
|
800
|
+
* is no longer dirty. A control the last commit did not see compares with
|
|
801
|
+
* its value when it was first focused. Always false without
|
|
802
|
+
* `track-changes`.
|
|
803
|
+
*
|
|
804
|
+
* While dirty, the host matches `:state(dirty)` and the submit buttons of
|
|
805
|
+
* this form carry `data-ful-dirty`; a change of the state dispatches
|
|
806
|
+
* `dirty:change` with `{ dirty }`. Both are refreshed at the next frame
|
|
807
|
+
* after an edit, while the property is exact at any time.
|
|
808
|
+
* @returns {boolean}
|
|
809
|
+
*/
|
|
810
|
+
get dirty(): boolean;
|
|
811
|
+
/**
|
|
812
|
+
* Records the values of the controls as saved, so that the form is no
|
|
813
|
+
* longer dirty. A submit commits by itself on success, and the form
|
|
814
|
+
* commits once its fields have rendered: a page calls this for a save of
|
|
815
|
+
* its own, or after writing the values it loaded. Does nothing without
|
|
816
|
+
* `track-changes`.
|
|
817
|
+
*/
|
|
818
|
+
commit(): void;
|
|
819
|
+
/**
|
|
820
|
+
* Counts a control as edited by the user, for a value the page writes on
|
|
821
|
+
* the user's behalf: assigning `value` reports nothing, as 0019 wants.
|
|
822
|
+
* @param {Element} el the control, or an element inside it
|
|
823
|
+
*/
|
|
824
|
+
edited(el: Element): void;
|
|
768
825
|
/**
|
|
769
826
|
* Marks the form busy or releases it. Calls nest: only the first
|
|
770
827
|
* `spinner(true)` and the matching last `spinner(false)` act, so a caller's
|
|
@@ -840,7 +897,9 @@ declare class Form extends ParsedElement {
|
|
|
840
897
|
* datetime-local, month or week control fires its native `change` while its
|
|
841
898
|
* segments are typed, as soon as they make a valid value; for those the field
|
|
842
899
|
* reports when the control is left or Enter is pressed, at once for a value
|
|
843
|
-
* picked from the browser's picker,
|
|
900
|
+
* picked from the browser's picker, never with the dates a year passes
|
|
901
|
+
* through while it is typed. A control left incomplete reads as `null`, as
|
|
902
|
+
* the native one does.
|
|
844
903
|
*
|
|
845
904
|
* A subclass template may render more than one control, as the compare filters
|
|
846
905
|
* render the two bounds of a range: the first is `_input`, the one the field is
|
|
@@ -3364,6 +3423,6 @@ declare class Plugin {
|
|
|
3364
3423
|
defineOverlay(...data: any[]): any;
|
|
3365
3424
|
}): void;
|
|
3366
3425
|
}
|
|
3367
|
-
export { Accordion, Anchors, AsyncEvents, Bindings, BooleanFilter, Checkbox, Claims, CompareFilter, Dialog, Drawer, Dropdown, Field, Form, FormLoader, InFilter, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, LocalDate, LocalDateFilter, LocalStorage, Menu, NumberFilter, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Table, TableLoader, TableSchemaParser, Tabs, TextFilter, Timing, Toasts, Tooltip, VersionedLocalStorage, VersionedSessionStorage, Wizard, describable };
|
|
3426
|
+
export { Accordion, Anchors, AsyncEvents, Bindings, BooleanFilter, Checkbox, Claims, CompareFilter, Dialog, Drawer, Dropdown, Field, Form, FormLoader, InFilter, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, LocalDate, LocalDateFilter, LocalStorage, Menu, NumberFilter, PageLeave, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Table, TableLoader, TableSchemaParser, Tabs, TextFilter, Timing, Toasts, Tooltip, VersionedLocalStorage, VersionedSessionStorage, Wizard, describable };
|
|
3368
3427
|
|
|
3369
3428
|
export as namespace ful;
|
package/dist/ful.iife.js
CHANGED
|
@@ -231,6 +231,31 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Asks before the page is left, through the browser's own `beforeunload`
|
|
236
|
+
* prompt.
|
|
237
|
+
*/
|
|
238
|
+
class PageLeave {
|
|
239
|
+
/**
|
|
240
|
+
* Asks before the page is left while `isDirty()` answers true, until
|
|
241
|
+
* `signal` aborts. The browser words the prompt, and asks only once the
|
|
242
|
+
* user has interacted with the page.
|
|
243
|
+
* @param {() => boolean} isDirty
|
|
244
|
+
* @param {AbortSignal} [signal]
|
|
245
|
+
*/
|
|
246
|
+
static guard(isDirty, signal) {
|
|
247
|
+
window.addEventListener(
|
|
248
|
+
'beforeunload',
|
|
249
|
+
(evt) => {
|
|
250
|
+
if (isDirty()) {
|
|
251
|
+
evt.preventDefault();
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
{ signal },
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
234
259
|
/**
|
|
235
260
|
* A hold on one generation of a `Claims`: `stale` turns true once a later
|
|
236
261
|
* `take()` or `invalidate()` on it starts a newer generation.
|
|
@@ -1318,6 +1343,10 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
1318
1343
|
*
|
|
1319
1344
|
* A native submit, by a button or by Enter in a field, is stopped and runs
|
|
1320
1345
|
* `submit` instead.
|
|
1346
|
+
*
|
|
1347
|
+
* `track-changes` keeps track of the edits not saved yet (see `dirty`), and
|
|
1348
|
+
* `guard-leave`, which implies it, asks before the page is left while there
|
|
1349
|
+
* are any.
|
|
1321
1350
|
*/
|
|
1322
1351
|
class Form extends index_mjs.ParsedElement {
|
|
1323
1352
|
/** Read once at the upgrade: changing one afterwards has no effect. */
|
|
@@ -1330,6 +1359,8 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
1330
1359
|
'clear-invalid-on-change:presence',
|
|
1331
1360
|
'scroll-on-error:presence',
|
|
1332
1361
|
'autocomplete',
|
|
1362
|
+
'track-changes:presence',
|
|
1363
|
+
'guard-leave:presence',
|
|
1333
1364
|
];
|
|
1334
1365
|
/** The native form holding the fields, created by the render. */
|
|
1335
1366
|
form;
|
|
@@ -1367,6 +1398,27 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
1367
1398
|
});
|
|
1368
1399
|
}
|
|
1369
1400
|
this.replaceChildren(form);
|
|
1401
|
+
this.#tracking = this.declared('track-changes') || this.declared('guard-leave');
|
|
1402
|
+
if (!this.#tracking) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
this.addEventListener('input', (evt) => this.#touch(evt.target));
|
|
1406
|
+
this.addEventListener('change', (evt) => this.#touch(evt.target));
|
|
1407
|
+
this.addEventListener('focusin', (evt) => this.#focus(evt.target));
|
|
1408
|
+
index_mjs.Rendering.waitForChildren(this)
|
|
1409
|
+
.catch(() => undefined)
|
|
1410
|
+
.then(() => this.commit());
|
|
1411
|
+
this.#guard();
|
|
1412
|
+
}
|
|
1413
|
+
connectedCallback() {
|
|
1414
|
+
super.connectedCallback();
|
|
1415
|
+
if (this.#tracking) {
|
|
1416
|
+
this.#guard();
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
disconnectedCallback() {
|
|
1420
|
+
this.#leave?.abort();
|
|
1421
|
+
this.#leave = null;
|
|
1370
1422
|
}
|
|
1371
1423
|
#submitting = false;
|
|
1372
1424
|
/**
|
|
@@ -1385,7 +1437,9 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
1385
1437
|
* loader's `submit` as its third argument. A listener may replace
|
|
1386
1438
|
* `request` on the detail
|
|
1387
1439
|
* 6. has the loader `submit` the request and `transform` the response
|
|
1388
|
-
* 7.
|
|
1440
|
+
* 7. under `track-changes`, commits the values extracted at step 2 (see
|
|
1441
|
+
* `commit`): an edit made while the submit was in flight stays unsaved
|
|
1442
|
+
* 8. fires `submit:success` with `{ submitter, values, request, response }`,
|
|
1389
1443
|
* `response` being the transformed one
|
|
1390
1444
|
*
|
|
1391
1445
|
* Anything thrown from step 1 on, a missing loader or a throwing mapper
|
|
@@ -1411,6 +1465,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
1411
1465
|
try {
|
|
1412
1466
|
const loader = this.component(this.declared('loader') ?? 'loaders:form').create(this);
|
|
1413
1467
|
values = Bindings.extractFrom(this.form, submitter);
|
|
1468
|
+
const submitted = this.#tracking ? { saved: this.#snapshot(), upTo: this.#sequence } : null;
|
|
1414
1469
|
request = await loader.prepare(values, this);
|
|
1415
1470
|
const se = new CustomEvent('submit', {
|
|
1416
1471
|
bubbles: true,
|
|
@@ -1431,6 +1486,9 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
1431
1486
|
|
|
1432
1487
|
response = await loader.submit(request, this, response);
|
|
1433
1488
|
const mapped = await loader.transform(response, this);
|
|
1489
|
+
if (submitted) {
|
|
1490
|
+
this.#commitFrom(submitted.saved, submitted.upTo);
|
|
1491
|
+
}
|
|
1434
1492
|
this.dispatchEvent(
|
|
1435
1493
|
new CustomEvent('submit:success', {
|
|
1436
1494
|
bubbles: true,
|
|
@@ -1456,9 +1514,208 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
1456
1514
|
this.spinner(false);
|
|
1457
1515
|
}
|
|
1458
1516
|
}
|
|
1459
|
-
/**
|
|
1517
|
+
/**
|
|
1518
|
+
* Resets the native form, each field returning to its default value. Under
|
|
1519
|
+
* `track-changes` every control counts as edited: the form is dirty if the
|
|
1520
|
+
* defaults differ from what was last committed.
|
|
1521
|
+
*/
|
|
1460
1522
|
reset() {
|
|
1461
1523
|
this.form.reset();
|
|
1524
|
+
if (!this.#tracking) {
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
for (const name of new Set([...this.#saved.keys(), ...this.#snapshot().keys()])) {
|
|
1528
|
+
this.#edits.set(name, ++this.#sequence);
|
|
1529
|
+
}
|
|
1530
|
+
this.#refresh();
|
|
1531
|
+
}
|
|
1532
|
+
/** name → the serialized values the last commit recorded */
|
|
1533
|
+
#saved = new Map();
|
|
1534
|
+
/** name → the serialized values when first focused, for a name the last commit did not see */
|
|
1535
|
+
#focused = new Map();
|
|
1536
|
+
/** name → the sequence number of its latest edit since the last commit */
|
|
1537
|
+
#edits = new Map();
|
|
1538
|
+
#sequence = 0;
|
|
1539
|
+
#wasDirty = false;
|
|
1540
|
+
#frame = 0;
|
|
1541
|
+
/** @type {AbortController|null} */
|
|
1542
|
+
#leave = null;
|
|
1543
|
+
#tracking = false;
|
|
1544
|
+
/**
|
|
1545
|
+
* Whether, under `track-changes`, a control the user edited since the last
|
|
1546
|
+
* commit holds a value other than the one committed. An edit is what the
|
|
1547
|
+
* controls report as one: an `input` or a `change` bubbling from a control
|
|
1548
|
+
* of this form (0019), so assigning `value`, a select's vocabulary dropping
|
|
1549
|
+
* a key or a section delivering its fields is not one, and an edit undone
|
|
1550
|
+
* is no longer dirty. A control the last commit did not see compares with
|
|
1551
|
+
* its value when it was first focused. Always false without
|
|
1552
|
+
* `track-changes`.
|
|
1553
|
+
*
|
|
1554
|
+
* While dirty, the host matches `:state(dirty)` and the submit buttons of
|
|
1555
|
+
* this form carry `data-ful-dirty`; a change of the state dispatches
|
|
1556
|
+
* `dirty:change` with `{ dirty }`. Both are refreshed at the next frame
|
|
1557
|
+
* after an edit, while the property is exact at any time.
|
|
1558
|
+
* @returns {boolean}
|
|
1559
|
+
*/
|
|
1560
|
+
get dirty() {
|
|
1561
|
+
if (!this.#tracking) {
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
for (const name of this.#edits.keys()) {
|
|
1565
|
+
const now = this.#valuesOf(name);
|
|
1566
|
+
if (!this.#saved.has(name) && now === '[]') {
|
|
1567
|
+
continue;
|
|
1568
|
+
}
|
|
1569
|
+
const was = this.#saved.get(name) ?? this.#focused.get(name);
|
|
1570
|
+
if (was !== now) {
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Records the values of the controls as saved, so that the form is no
|
|
1578
|
+
* longer dirty. A submit commits by itself on success, and the form
|
|
1579
|
+
* commits once its fields have rendered: a page calls this for a save of
|
|
1580
|
+
* its own, or after writing the values it loaded. Does nothing without
|
|
1581
|
+
* `track-changes`.
|
|
1582
|
+
*/
|
|
1583
|
+
commit() {
|
|
1584
|
+
if (this.#tracking) {
|
|
1585
|
+
this.#commitFrom(this.#snapshot(), this.#sequence);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Counts a control as edited by the user, for a value the page writes on
|
|
1590
|
+
* the user's behalf: assigning `value` reports nothing, as 0019 wants.
|
|
1591
|
+
* @param {Element} el the control, or an element inside it
|
|
1592
|
+
*/
|
|
1593
|
+
edited(el) {
|
|
1594
|
+
this.#touch(el);
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* @param {Map<string, string>} saved
|
|
1598
|
+
* @param {number} upTo the sequence number of the latest edit the values include
|
|
1599
|
+
*/
|
|
1600
|
+
#commitFrom(saved, upTo) {
|
|
1601
|
+
this.#saved = saved;
|
|
1602
|
+
for (const [name, sequence] of this.#edits) {
|
|
1603
|
+
if (sequence <= upTo) {
|
|
1604
|
+
this.#edits.delete(name);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
for (const name of this.#focused.keys()) {
|
|
1608
|
+
if (saved.has(name)) {
|
|
1609
|
+
this.#focused.delete(name);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
this.#refresh();
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* The named control of this form `target` is or sits in, if any: a field's
|
|
1616
|
+
* inner control is unnamed or carries `form=""`, a control of a form nested
|
|
1617
|
+
* in this one, like a table's filters, belongs to that form, and a
|
|
1618
|
+
* form-associated element with no `form` property answers its owner
|
|
1619
|
+
* through `internals.form`.
|
|
1620
|
+
* @param {any} target
|
|
1621
|
+
* @returns {string|null} its name
|
|
1622
|
+
*/
|
|
1623
|
+
#nameOf(target) {
|
|
1624
|
+
for (let el = target; el && el !== this; el = el.parentElement) {
|
|
1625
|
+
if ((el.form ?? el.internals?.form) === this.form && el.hasAttribute('name')) {
|
|
1626
|
+
return el.getAttribute('name');
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
/** @param {EventTarget|null} target */
|
|
1632
|
+
#touch(target) {
|
|
1633
|
+
if (!this.#tracking) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
const name = this.#nameOf(target);
|
|
1637
|
+
if (name === null) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
this.#edits.set(name, ++this.#sequence);
|
|
1641
|
+
if (this.#frame === 0) {
|
|
1642
|
+
this.#frame = requestAnimationFrame(() => this.#refresh());
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
/** @param {EventTarget|null} target */
|
|
1646
|
+
#focus(target) {
|
|
1647
|
+
const name = this.#nameOf(target);
|
|
1648
|
+
if (name !== null && !this.#saved.has(name) && !this.#focused.has(name)) {
|
|
1649
|
+
this.#focused.set(name, this.#valuesOf(name));
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* The controls that contribute to the values, as `Bindings.extractFrom`
|
|
1654
|
+
* reads them, with no submitter.
|
|
1655
|
+
* @returns {any[]}
|
|
1656
|
+
*/
|
|
1657
|
+
#contributing() {
|
|
1658
|
+
return Array.from(this.form.elements).filter(
|
|
1659
|
+
(el) =>
|
|
1660
|
+
el.hasAttribute('name') &&
|
|
1661
|
+
!['submit', 'reset', 'button'].includes(/** @type {any} */ (el).type) &&
|
|
1662
|
+
!el.matches(':disabled'),
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
/** @returns {Map<string, string>} each name to its serialized values */
|
|
1666
|
+
#snapshot() {
|
|
1667
|
+
/** @type {Map<string, any[]>} */
|
|
1668
|
+
const values = new Map();
|
|
1669
|
+
for (const el of this.#contributing()) {
|
|
1670
|
+
const name = el.getAttribute('name');
|
|
1671
|
+
values.set(name, [...(values.get(name) ?? []), Bindings.extract(el)]);
|
|
1672
|
+
}
|
|
1673
|
+
return new Map(Array.from(values, ([name, vs]) => [name, JSON.stringify(vs)]));
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* @param {string} name
|
|
1677
|
+
* @returns {string} the serialized values of the controls carrying it
|
|
1678
|
+
*/
|
|
1679
|
+
#valuesOf(name) {
|
|
1680
|
+
return JSON.stringify(
|
|
1681
|
+
this.#contributing()
|
|
1682
|
+
.filter((el) => el.getAttribute('name') === name)
|
|
1683
|
+
.map((el) => Bindings.extract(el)),
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
#refresh() {
|
|
1687
|
+
cancelAnimationFrame(this.#frame);
|
|
1688
|
+
this.#frame = 0;
|
|
1689
|
+
const dirty = this.dirty;
|
|
1690
|
+
this.querySelectorAll('button').forEach((el) => {
|
|
1691
|
+
const button = /** @type HTMLButtonElement */ (el);
|
|
1692
|
+
if (button.form === this.form && button.type === 'submit') {
|
|
1693
|
+
button.toggleAttribute('data-ful-dirty', dirty);
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
if (dirty === this.#wasDirty) {
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
this.#wasDirty = dirty;
|
|
1700
|
+
if (dirty) {
|
|
1701
|
+
this.internals.states.add('dirty');
|
|
1702
|
+
} else {
|
|
1703
|
+
this.internals.states.delete('dirty');
|
|
1704
|
+
}
|
|
1705
|
+
this.dispatchEvent(
|
|
1706
|
+
new CustomEvent('dirty:change', {
|
|
1707
|
+
bubbles: true,
|
|
1708
|
+
cancelable: false,
|
|
1709
|
+
detail: { dirty },
|
|
1710
|
+
}),
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
#guard() {
|
|
1714
|
+
if (!this.declared('guard-leave') || this.#leave) {
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
this.#leave = new AbortController();
|
|
1718
|
+
PageLeave.guard(() => this.dirty, this.#leave.signal);
|
|
1462
1719
|
}
|
|
1463
1720
|
#spinning = 0;
|
|
1464
1721
|
/**
|
|
@@ -1646,7 +1903,9 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
1646
1903
|
* datetime-local, month or week control fires its native `change` while its
|
|
1647
1904
|
* segments are typed, as soon as they make a valid value; for those the field
|
|
1648
1905
|
* reports when the control is left or Enter is pressed, at once for a value
|
|
1649
|
-
* picked from the browser's picker,
|
|
1906
|
+
* picked from the browser's picker, never with the dates a year passes
|
|
1907
|
+
* through while it is typed. A control left incomplete reads as `null`, as
|
|
1908
|
+
* the native one does.
|
|
1650
1909
|
*
|
|
1651
1910
|
* A subclass template may render more than one control, as the compare filters
|
|
1652
1911
|
* render the two bounds of a range: the first is `_input`, the one the field is
|
|
@@ -8057,6 +8316,7 @@ var ful = (function (exports, index_mjs, index_mjs$1) {
|
|
|
8057
8316
|
exports.LocalStorage = LocalStorage;
|
|
8058
8317
|
exports.Menu = Menu;
|
|
8059
8318
|
exports.NumberFilter = NumberFilter;
|
|
8319
|
+
exports.PageLeave = PageLeave;
|
|
8060
8320
|
exports.Pagination = Pagination;
|
|
8061
8321
|
exports.Plugin = Plugin;
|
|
8062
8322
|
exports.RadioGroup = RadioGroup;
|