webspresso 0.0.110 → 0.0.112
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/core/client/adapter/datastar.js +190 -51
- package/core/client/api.js +23 -21
- package/core/client/controller.js +407 -40
- package/core/client/debug.js +12 -12
- package/core/client/devtool.js +124 -19
- package/core/client/forms.js +163 -29
- package/core/client/fragments.js +616 -118
- package/core/client/host.js +369 -0
- package/core/client/index.d.ts +18 -0
- package/core/client/index.js +154 -23
- package/core/client/lite.js +101 -4
- package/core/client/server-fragments.js +142 -22
- package/core/client/services.js +588 -0
- package/core/client/utils.js +188 -2
- package/index.d.ts +334 -29
- package/index.js +3 -0
- package/package.json +1 -1
- package/src/bundle/index.js +175 -14
- package/src/dev/banner.js +4 -1
- package/src/dev-reload/index.js +29 -1
- package/src/server.js +28 -0
package/core/client/forms.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { defaultStorage } = require('./storage');
|
|
4
|
+
const { isArray, setElementBusyState } = require('./utils');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Webspresso Native HTML Form Layer & Typed Form Handle
|
|
@@ -202,12 +203,12 @@ function parseFormElement(formEl, submitterOrOptions = null) {
|
|
|
202
203
|
function appendValue(normName, val, forceArray) {
|
|
203
204
|
if (val === undefined) return;
|
|
204
205
|
if (forceArray) {
|
|
205
|
-
if (!
|
|
206
|
+
if (!isArray(data[normName])) {
|
|
206
207
|
data[normName] = [];
|
|
207
208
|
}
|
|
208
209
|
data[normName].push(val);
|
|
209
210
|
} else if (normName in data) {
|
|
210
|
-
if (!
|
|
211
|
+
if (!isArray(data[normName])) {
|
|
211
212
|
data[normName] = [data[normName]];
|
|
212
213
|
}
|
|
213
214
|
data[normName].push(val);
|
|
@@ -326,7 +327,7 @@ function parseFormElement(formEl, submitterOrOptions = null) {
|
|
|
326
327
|
formData = new FormData();
|
|
327
328
|
for (const [key, val] of Object.entries(data)) {
|
|
328
329
|
if (val === undefined) continue;
|
|
329
|
-
if (
|
|
330
|
+
if (isArray(val)) {
|
|
330
331
|
for (const item of val) {
|
|
331
332
|
if (item === undefined) continue;
|
|
332
333
|
if (typeof Blob !== 'undefined' && item instanceof Blob) {
|
|
@@ -416,27 +417,44 @@ function computeFormDirty(formEl, initialSnapshot) {
|
|
|
416
417
|
*/
|
|
417
418
|
function createFormHandle(formEl, options = {}) {
|
|
418
419
|
if (formEl && formEl.__wFormHandle) {
|
|
420
|
+
if (options.services && typeof formEl.__wFormHandle.__setServices === 'function') {
|
|
421
|
+
formEl.__wFormHandle.__setServices(options.services);
|
|
422
|
+
}
|
|
419
423
|
return formEl.__wFormHandle;
|
|
420
424
|
}
|
|
421
425
|
|
|
422
426
|
const api = options.api;
|
|
423
427
|
const debug = options.debug || null;
|
|
428
|
+
let services = options.services || null;
|
|
424
429
|
const formName =
|
|
425
430
|
(formEl && formEl.getAttribute && formEl.getAttribute('data-w-form')) ||
|
|
426
431
|
(formEl && formEl.getAttribute && formEl.getAttribute('name')) ||
|
|
427
432
|
'form';
|
|
428
433
|
|
|
429
434
|
let initialSnapshot = captureInitialFormSnapshot(formEl);
|
|
435
|
+
let baselineValues = parseFormElement(formEl).data;
|
|
430
436
|
let pending = false;
|
|
431
437
|
let dirty = false;
|
|
438
|
+
let inputRevision = 0;
|
|
432
439
|
let fieldErrors = {};
|
|
433
440
|
let formError = null;
|
|
441
|
+
let lastError = null;
|
|
434
442
|
let lastData = null;
|
|
443
|
+
let draftDebounceTimer = null;
|
|
444
|
+
let resetTimer = null;
|
|
435
445
|
|
|
446
|
+
const beforeSubmitListeners = new Set();
|
|
436
447
|
const successListeners = new Set();
|
|
437
448
|
const errorListeners = new Set();
|
|
438
449
|
const stateListeners = new Set();
|
|
439
450
|
|
|
451
|
+
function cancelDraftDebounce() {
|
|
452
|
+
if (draftDebounceTimer) {
|
|
453
|
+
clearTimeout(draftDebounceTimer);
|
|
454
|
+
draftDebounceTimer = null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
440
458
|
// Form draft persistence setup (sessionStorage by default, or localStorage)
|
|
441
459
|
const rawDraftAttr = formEl
|
|
442
460
|
? formEl.getAttribute('data-w-persist-draft') || formEl.getAttribute('data-w-draft')
|
|
@@ -453,11 +471,18 @@ function createFormHandle(formEl, options = {}) {
|
|
|
453
471
|
draftStorageType = 'local';
|
|
454
472
|
}
|
|
455
473
|
|
|
456
|
-
const
|
|
474
|
+
const draftScope =
|
|
475
|
+
options.draftScope ||
|
|
476
|
+
(formEl && formEl.getAttribute && formEl.getAttribute('data-w-draft-scope')) ||
|
|
477
|
+
'';
|
|
478
|
+
|
|
479
|
+
const baseDraftKey =
|
|
457
480
|
options.draftKey ||
|
|
458
481
|
(formEl && (formEl.getAttribute('data-w-draft-key') || formEl.getAttribute('data-w-persist-key'))) ||
|
|
459
482
|
`form_draft:${formName || 'anon'}`;
|
|
460
483
|
|
|
484
|
+
let draftKey = draftScope ? `${baseDraftKey}:${draftScope}` : baseDraftKey;
|
|
485
|
+
|
|
461
486
|
const draftStorage =
|
|
462
487
|
(options.storage && options.storage[draftStorageType]) ||
|
|
463
488
|
(draftStorageType === 'local' ? defaultStorage.local : defaultStorage.session);
|
|
@@ -480,7 +505,22 @@ function createFormHandle(formEl, options = {}) {
|
|
|
480
505
|
return safeData;
|
|
481
506
|
}
|
|
482
507
|
|
|
508
|
+
function sanitizeDraftObject(raw) {
|
|
509
|
+
if (!raw || typeof raw !== 'object' || isArray(raw)) return null;
|
|
510
|
+
const clean = {};
|
|
511
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
512
|
+
if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
|
|
513
|
+
if (v === null || v === undefined || typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
|
|
514
|
+
clean[k] = v;
|
|
515
|
+
} else if (isArray(v) && v.every((item) => typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean')) {
|
|
516
|
+
clean[k] = v;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return Object.keys(clean).length > 0 ? clean : null;
|
|
520
|
+
}
|
|
521
|
+
|
|
483
522
|
function saveDraft() {
|
|
523
|
+
cancelDraftDebounce();
|
|
484
524
|
if (!formEl || !isDraftEnabled) return false;
|
|
485
525
|
const draftPayload = extractDraftValues(formEl);
|
|
486
526
|
return draftStorage.set(draftKey, draftPayload);
|
|
@@ -488,9 +528,15 @@ function createFormHandle(formEl, options = {}) {
|
|
|
488
528
|
|
|
489
529
|
function restoreDraft() {
|
|
490
530
|
if (!formEl || !isDraftEnabled) return false;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
531
|
+
let saved = null;
|
|
532
|
+
try {
|
|
533
|
+
saved = draftStorage.get(draftKey, null);
|
|
534
|
+
} catch (_e) {
|
|
535
|
+
saved = null;
|
|
536
|
+
}
|
|
537
|
+
const cleanDraft = sanitizeDraftObject(saved);
|
|
538
|
+
if (cleanDraft) {
|
|
539
|
+
fill(cleanDraft, { baseline: false });
|
|
494
540
|
if (typeof formEl.dispatchEvent === 'function') {
|
|
495
541
|
const CustomEventCtor =
|
|
496
542
|
(formEl.ownerDocument && formEl.ownerDocument.defaultView && formEl.ownerDocument.defaultView.CustomEvent) ||
|
|
@@ -500,7 +546,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
500
546
|
new CustomEventCtor('w-form:draft-restored', {
|
|
501
547
|
bubbles: true,
|
|
502
548
|
cancelable: true,
|
|
503
|
-
detail: { form: handle, draft:
|
|
549
|
+
detail: { form: handle, draft: cleanDraft, key: draftKey },
|
|
504
550
|
})
|
|
505
551
|
);
|
|
506
552
|
}
|
|
@@ -511,6 +557,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
511
557
|
}
|
|
512
558
|
|
|
513
559
|
function clearDraft() {
|
|
560
|
+
cancelDraftDebounce();
|
|
514
561
|
if (!isDraftEnabled) return false;
|
|
515
562
|
return draftStorage.remove(draftKey);
|
|
516
563
|
}
|
|
@@ -520,6 +567,20 @@ function createFormHandle(formEl, options = {}) {
|
|
|
520
567
|
return draftStorage.has(draftKey);
|
|
521
568
|
}
|
|
522
569
|
|
|
570
|
+
function setDraftKey(nextKey, keyOpts = {}) {
|
|
571
|
+
cancelDraftDebounce();
|
|
572
|
+
if (typeof nextKey === 'string' && nextKey.trim()) {
|
|
573
|
+
draftKey = nextKey.trim();
|
|
574
|
+
if (formEl && typeof formEl.setAttribute === 'function') {
|
|
575
|
+
formEl.setAttribute('data-w-draft-key', draftKey);
|
|
576
|
+
}
|
|
577
|
+
if (keyOpts && keyOpts.restore === true && isDraftEnabled) {
|
|
578
|
+
restoreDraft();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return handle;
|
|
582
|
+
}
|
|
583
|
+
|
|
523
584
|
function notifyStateChange() {
|
|
524
585
|
for (const fn of stateListeners) {
|
|
525
586
|
try {
|
|
@@ -615,32 +676,32 @@ function createFormHandle(formEl, options = {}) {
|
|
|
615
676
|
* Toggle pending state on the form and submit buttons to prevent double submissions.
|
|
616
677
|
*/
|
|
617
678
|
function setPendingState(nextPending, activeSubmitter = null) {
|
|
618
|
-
|
|
679
|
+
const next = Boolean(nextPending);
|
|
680
|
+
if (pending === next) return;
|
|
681
|
+
pending = next;
|
|
619
682
|
if (!formEl) return;
|
|
620
683
|
|
|
621
684
|
if (pending) {
|
|
622
685
|
formEl.setAttribute('aria-busy', 'true');
|
|
623
686
|
formEl.setAttribute('data-w-pending', 'true');
|
|
687
|
+
formEl.setAttribute('data-w-busy', 'true');
|
|
624
688
|
} else {
|
|
625
689
|
formEl.removeAttribute('aria-busy');
|
|
626
690
|
formEl.removeAttribute('data-w-pending');
|
|
691
|
+
formEl.removeAttribute('data-w-busy');
|
|
627
692
|
}
|
|
628
693
|
|
|
629
694
|
const submitButtons = formEl.querySelectorAll
|
|
630
695
|
? formEl.querySelectorAll('button[type="submit"], button:not([type]), input[type="submit"]')
|
|
631
696
|
: [];
|
|
697
|
+
let submitterHandled = false;
|
|
632
698
|
for (let i = 0; i < submitButtons.length; i++) {
|
|
633
699
|
const btn = submitButtons[i];
|
|
634
|
-
if (btn
|
|
635
|
-
|
|
636
|
-
btn.__wWasDisabled = Boolean(btn.disabled);
|
|
637
|
-
btn.disabled = true;
|
|
638
|
-
} else if (!btn.__wWasDisabled) {
|
|
639
|
-
btn.disabled = false;
|
|
640
|
-
}
|
|
700
|
+
if (btn === activeSubmitter) submitterHandled = true;
|
|
701
|
+
setElementBusyState(btn, pending);
|
|
641
702
|
}
|
|
642
|
-
if (activeSubmitter && !
|
|
643
|
-
activeSubmitter
|
|
703
|
+
if (activeSubmitter && !submitterHandled) {
|
|
704
|
+
setElementBusyState(activeSubmitter, pending);
|
|
644
705
|
}
|
|
645
706
|
notifyStateChange();
|
|
646
707
|
}
|
|
@@ -649,6 +710,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
649
710
|
* Reset form controls back to their initial default values (NOT emptying non-empty defaults).
|
|
650
711
|
*/
|
|
651
712
|
function reset() {
|
|
713
|
+
cancelDraftDebounce();
|
|
652
714
|
if (!formEl) return;
|
|
653
715
|
if (typeof formEl.reset === 'function') {
|
|
654
716
|
formEl.reset();
|
|
@@ -678,6 +740,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
678
740
|
* Completely clear all user-editable controls to empty values (distinct from `reset()`).
|
|
679
741
|
*/
|
|
680
742
|
function clear() {
|
|
743
|
+
cancelDraftDebounce();
|
|
681
744
|
if (!formEl || typeof formEl.querySelectorAll !== 'function') return;
|
|
682
745
|
const controls = formEl.querySelectorAll('input, select, textarea');
|
|
683
746
|
for (let i = 0; i < controls.length; i++) {
|
|
@@ -714,11 +777,14 @@ function createFormHandle(formEl, options = {}) {
|
|
|
714
777
|
* snapshot, clears `dirty`, and clears previous validation errors.
|
|
715
778
|
*/
|
|
716
779
|
function fill(values = {}, options = {}) {
|
|
717
|
-
if (!formEl || typeof formEl.querySelectorAll !== 'function' || !values || typeof values !== 'object') {
|
|
780
|
+
if (!formEl || typeof formEl.querySelectorAll !== 'function' || !values || typeof values !== 'object' || isArray(values)) {
|
|
718
781
|
return handle;
|
|
719
782
|
}
|
|
720
783
|
|
|
721
784
|
const isBaseline = Boolean(options.baseline);
|
|
785
|
+
if (isBaseline) {
|
|
786
|
+
cancelDraftDebounce();
|
|
787
|
+
}
|
|
722
788
|
|
|
723
789
|
for (const [key, val] of Object.entries(values)) {
|
|
724
790
|
if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
|
|
@@ -734,7 +800,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
734
800
|
if (type === 'file') continue; // Browser security prevents setting file input value
|
|
735
801
|
|
|
736
802
|
if (type === 'checkbox') {
|
|
737
|
-
if (
|
|
803
|
+
if (isArray(val)) {
|
|
738
804
|
ctrl.checked = val.map(String).includes(String(ctrl.value));
|
|
739
805
|
} else {
|
|
740
806
|
ctrl.checked = Boolean(val);
|
|
@@ -743,7 +809,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
743
809
|
ctrl.checked = String(val) === String(ctrl.value);
|
|
744
810
|
} else if (ctrl.tagName === 'SELECT') {
|
|
745
811
|
if (ctrl.multiple) {
|
|
746
|
-
const arr =
|
|
812
|
+
const arr = isArray(val) ? val.map(String) : [String(val)];
|
|
747
813
|
const opts = ctrl.options || [];
|
|
748
814
|
for (let j = 0; j < opts.length; j++) {
|
|
749
815
|
opts[j].selected = arr.includes(String(opts[j].value));
|
|
@@ -752,6 +818,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
752
818
|
ctrl.value = val === null || val === undefined ? '' : String(val);
|
|
753
819
|
}
|
|
754
820
|
} else {
|
|
821
|
+
if (val !== null && typeof val === 'object') continue;
|
|
755
822
|
ctrl.value = val === null || val === undefined ? '' : String(val);
|
|
756
823
|
}
|
|
757
824
|
|
|
@@ -763,9 +830,11 @@ function createFormHandle(formEl, options = {}) {
|
|
|
763
830
|
|
|
764
831
|
if (isBaseline) {
|
|
765
832
|
initialSnapshot = captureInitialFormSnapshot(formEl);
|
|
833
|
+
baselineValues = parseFormElement(formEl).data;
|
|
766
834
|
dirty = false;
|
|
767
835
|
fieldErrors = {};
|
|
768
836
|
formError = null;
|
|
837
|
+
lastError = null;
|
|
769
838
|
syncErrorsToDom(false);
|
|
770
839
|
} else {
|
|
771
840
|
dirty = computeFormDirty(formEl, initialSnapshot);
|
|
@@ -778,7 +847,9 @@ function createFormHandle(formEl, options = {}) {
|
|
|
778
847
|
|
|
779
848
|
function setAction(url) {
|
|
780
849
|
if (formEl && url) {
|
|
781
|
-
|
|
850
|
+
const nextAction = String(url);
|
|
851
|
+
formEl.setAttribute('action', nextAction);
|
|
852
|
+
formEl.__wCustomAction = nextAction;
|
|
782
853
|
}
|
|
783
854
|
return handle;
|
|
784
855
|
}
|
|
@@ -791,6 +862,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
791
862
|
action = action.replace(new RegExp(`:${k}\\b`, 'g'), encodeURIComponent(String(v)));
|
|
792
863
|
}
|
|
793
864
|
formEl.setAttribute('action', action);
|
|
865
|
+
formEl.__wCustomAction = action;
|
|
794
866
|
return handle;
|
|
795
867
|
}
|
|
796
868
|
|
|
@@ -865,9 +937,23 @@ function createFormHandle(formEl, options = {}) {
|
|
|
865
937
|
|
|
866
938
|
const { data: payloadData, formData, isMultipart } = parseFormElement(formEl, submitter);
|
|
867
939
|
|
|
940
|
+
// Execute beforeSubmit hooks before initiating network request
|
|
941
|
+
for (const guardFn of beforeSubmitListeners) {
|
|
942
|
+
let allowed = true;
|
|
943
|
+
try {
|
|
944
|
+
allowed = await guardFn(payloadData, { form: handle, submitter, action: actionUrl, method });
|
|
945
|
+
} catch (guardErr) {
|
|
946
|
+
return { ok: false, submitted: false, canceled: true, reason: 'before-submit-error', error: guardErr };
|
|
947
|
+
}
|
|
948
|
+
if (allowed === false) {
|
|
949
|
+
return { ok: false, submitted: false, canceled: true, reason: 'before-submit' };
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
868
953
|
// Clear previous errors before request starts
|
|
869
954
|
fieldErrors = {};
|
|
870
955
|
formError = null;
|
|
956
|
+
lastError = null;
|
|
871
957
|
syncErrorsToDom(false);
|
|
872
958
|
setPendingState(true, submitter);
|
|
873
959
|
|
|
@@ -916,6 +1002,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
916
1002
|
} else {
|
|
917
1003
|
// Current state becomes the new clean baseline after successful save
|
|
918
1004
|
initialSnapshot = captureInitialFormSnapshot(formEl);
|
|
1005
|
+
baselineValues = parseFormElement(formEl).data;
|
|
919
1006
|
}
|
|
920
1007
|
|
|
921
1008
|
if (isDraftEnabled) {
|
|
@@ -923,9 +1010,18 @@ function createFormHandle(formEl, options = {}) {
|
|
|
923
1010
|
}
|
|
924
1011
|
|
|
925
1012
|
// Invoke registered success hooks. Note: Any background fragment refresh failure
|
|
926
|
-
//
|
|
1013
|
+
// or side-effect service error in onSuccess NEVER puts a succeeded form into validation error
|
|
1014
|
+
// state or makes the completed mutation appear rolled back!
|
|
1015
|
+
let hookError = null;
|
|
927
1016
|
for (const listener of successListeners) {
|
|
928
|
-
|
|
1017
|
+
try {
|
|
1018
|
+
await listener(responseData, { values: payloadData, form: handle, services });
|
|
1019
|
+
} catch (listenerErr) {
|
|
1020
|
+
if (!hookError) hookError = listenerErr;
|
|
1021
|
+
if (debug && typeof debug.trackCleanupError === 'function') {
|
|
1022
|
+
debug.trackCleanupError(`form_${formName}`, formName, 'form:onSuccess', listenerErr);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
929
1025
|
}
|
|
930
1026
|
|
|
931
1027
|
// Explicit redirect behavior
|
|
@@ -951,8 +1047,9 @@ function createFormHandle(formEl, options = {}) {
|
|
|
951
1047
|
}
|
|
952
1048
|
}
|
|
953
1049
|
|
|
954
|
-
return { ok: true, submitted: true, data: responseData, refreshOk, refreshError };
|
|
1050
|
+
return { ok: true, submitted: true, data: responseData, refreshOk, refreshError, hookError };
|
|
955
1051
|
} catch (err) {
|
|
1052
|
+
lastError = err || null;
|
|
956
1053
|
if (err && err.kind === 'validation') {
|
|
957
1054
|
fieldErrors = { ...(err.fieldErrors || {}) };
|
|
958
1055
|
formError = err.formError || (Object.keys(fieldErrors).length === 0 ? err.message : null);
|
|
@@ -966,7 +1063,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
966
1063
|
|
|
967
1064
|
for (const listener of errorListeners) {
|
|
968
1065
|
try {
|
|
969
|
-
await listener(err, { values: payloadData, form: handle });
|
|
1066
|
+
await listener(err, { values: payloadData, form: handle, services });
|
|
970
1067
|
} catch (_e) {
|
|
971
1068
|
// Ignore
|
|
972
1069
|
}
|
|
@@ -982,8 +1079,8 @@ function createFormHandle(formEl, options = {}) {
|
|
|
982
1079
|
}
|
|
983
1080
|
|
|
984
1081
|
// Listen to native submit and input/change events on `formEl`
|
|
985
|
-
let draftDebounceTimer = null;
|
|
986
1082
|
const onDomInput = (evt) => {
|
|
1083
|
+
inputRevision += 1;
|
|
987
1084
|
dirty = computeFormDirty(formEl, initialSnapshot);
|
|
988
1085
|
const target = evt && evt.target;
|
|
989
1086
|
if (target && target.getAttribute) {
|
|
@@ -998,8 +1095,9 @@ function createFormHandle(formEl, options = {}) {
|
|
|
998
1095
|
}
|
|
999
1096
|
}
|
|
1000
1097
|
if (isDraftEnabled) {
|
|
1001
|
-
|
|
1098
|
+
cancelDraftDebounce();
|
|
1002
1099
|
draftDebounceTimer = setTimeout(() => {
|
|
1100
|
+
draftDebounceTimer = null;
|
|
1003
1101
|
saveDraft();
|
|
1004
1102
|
}, 150);
|
|
1005
1103
|
}
|
|
@@ -1011,7 +1109,10 @@ function createFormHandle(formEl, options = {}) {
|
|
|
1011
1109
|
};
|
|
1012
1110
|
|
|
1013
1111
|
const onDomReset = () => {
|
|
1014
|
-
|
|
1112
|
+
cancelDraftDebounce();
|
|
1113
|
+
if (resetTimer) clearTimeout(resetTimer);
|
|
1114
|
+
resetTimer = setTimeout(() => {
|
|
1115
|
+
resetTimer = null;
|
|
1015
1116
|
dirty = false;
|
|
1016
1117
|
fieldErrors = {};
|
|
1017
1118
|
formError = null;
|
|
@@ -1035,6 +1136,12 @@ function createFormHandle(formEl, options = {}) {
|
|
|
1035
1136
|
handle = {
|
|
1036
1137
|
name: formName,
|
|
1037
1138
|
el: formEl,
|
|
1139
|
+
get services() {
|
|
1140
|
+
return services;
|
|
1141
|
+
},
|
|
1142
|
+
__setServices(nextServices) {
|
|
1143
|
+
if (nextServices) services = nextServices;
|
|
1144
|
+
},
|
|
1038
1145
|
get pending() {
|
|
1039
1146
|
return pending;
|
|
1040
1147
|
},
|
|
@@ -1042,9 +1149,18 @@ function createFormHandle(formEl, options = {}) {
|
|
|
1042
1149
|
dirty = computeFormDirty(formEl, initialSnapshot);
|
|
1043
1150
|
return dirty;
|
|
1044
1151
|
},
|
|
1152
|
+
get inputRevision() {
|
|
1153
|
+
return inputRevision;
|
|
1154
|
+
},
|
|
1155
|
+
get draftKey() {
|
|
1156
|
+
return draftKey;
|
|
1157
|
+
},
|
|
1045
1158
|
get values() {
|
|
1046
1159
|
return parseFormElement(formEl).data;
|
|
1047
1160
|
},
|
|
1161
|
+
get baseline() {
|
|
1162
|
+
return { ...(baselineValues || {}) };
|
|
1163
|
+
},
|
|
1048
1164
|
get errors() {
|
|
1049
1165
|
return { ...fieldErrors };
|
|
1050
1166
|
},
|
|
@@ -1054,6 +1170,9 @@ function createFormHandle(formEl, options = {}) {
|
|
|
1054
1170
|
get formError() {
|
|
1055
1171
|
return formError;
|
|
1056
1172
|
},
|
|
1173
|
+
get lastError() {
|
|
1174
|
+
return lastError;
|
|
1175
|
+
},
|
|
1057
1176
|
get lastData() {
|
|
1058
1177
|
return lastData;
|
|
1059
1178
|
},
|
|
@@ -1073,6 +1192,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
1073
1192
|
saveDraft,
|
|
1074
1193
|
restoreDraft,
|
|
1075
1194
|
clearDraft,
|
|
1195
|
+
setDraftKey,
|
|
1076
1196
|
get hasDraft() {
|
|
1077
1197
|
return hasDraft();
|
|
1078
1198
|
},
|
|
@@ -1092,12 +1212,20 @@ function createFormHandle(formEl, options = {}) {
|
|
|
1092
1212
|
clearErrors() {
|
|
1093
1213
|
fieldErrors = {};
|
|
1094
1214
|
formError = null;
|
|
1215
|
+
lastError = null;
|
|
1095
1216
|
syncErrorsToDom(false);
|
|
1096
1217
|
notifyStateChange();
|
|
1097
1218
|
},
|
|
1098
1219
|
reset,
|
|
1099
1220
|
clear,
|
|
1100
1221
|
submit,
|
|
1222
|
+
beforeSubmit(fn) {
|
|
1223
|
+
if (typeof fn === 'function') {
|
|
1224
|
+
beforeSubmitListeners.add(fn);
|
|
1225
|
+
return () => beforeSubmitListeners.delete(fn);
|
|
1226
|
+
}
|
|
1227
|
+
return () => {};
|
|
1228
|
+
},
|
|
1101
1229
|
onSuccess(fn) {
|
|
1102
1230
|
if (typeof fn === 'function') {
|
|
1103
1231
|
successListeners.add(fn);
|
|
@@ -1120,6 +1248,11 @@ function createFormHandle(formEl, options = {}) {
|
|
|
1120
1248
|
return () => {};
|
|
1121
1249
|
},
|
|
1122
1250
|
destroy() {
|
|
1251
|
+
cancelDraftDebounce();
|
|
1252
|
+
if (resetTimer) {
|
|
1253
|
+
clearTimeout(resetTimer);
|
|
1254
|
+
resetTimer = null;
|
|
1255
|
+
}
|
|
1123
1256
|
if (formEl && typeof formEl.removeEventListener === 'function') {
|
|
1124
1257
|
formEl.removeEventListener('input', onDomInput);
|
|
1125
1258
|
formEl.removeEventListener('change', onDomInput);
|
|
@@ -1129,6 +1262,7 @@ function createFormHandle(formEl, options = {}) {
|
|
|
1129
1262
|
if (formEl) {
|
|
1130
1263
|
delete formEl.__wFormHandle;
|
|
1131
1264
|
}
|
|
1265
|
+
beforeSubmitListeners.clear();
|
|
1132
1266
|
successListeners.clear();
|
|
1133
1267
|
errorListeners.clear();
|
|
1134
1268
|
stateListeners.clear();
|