@neeloong/form 0.7.0 → 0.8.0
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/index.d.mts +87 -33
- package/index.js +270 -53
- package/index.min.js +7 -7
- package/index.min.mjs +7 -7
- package/index.mjs +270 -53
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @neeloong/form v0.
|
|
2
|
+
* @neeloong/form v0.8.0
|
|
3
3
|
* (c) 2024-2025 Fierflame
|
|
4
4
|
* @license Apache-2.0
|
|
5
5
|
*/
|
|
@@ -13,7 +13,7 @@ export { Signal } from 'signal-polyfill';
|
|
|
13
13
|
*
|
|
14
14
|
* @param {Store} self
|
|
15
15
|
* @param {boolean?} [defState]
|
|
16
|
-
* @param {boolean | ((store: Store
|
|
16
|
+
* @param {boolean | ((store: Store) => boolean?) | null} [fn]
|
|
17
17
|
* @param {Signal.Computed<boolean>?} [parent]
|
|
18
18
|
* @returns {[Signal.State<boolean?>, Signal.Computed<boolean>]}
|
|
19
19
|
*/
|
|
@@ -23,7 +23,7 @@ const createBooleanStates = (self, defState, fn, parent) => {
|
|
|
23
23
|
/** @type {Signal.Computed<boolean>} */
|
|
24
24
|
let scriptState;
|
|
25
25
|
if (typeof fn === 'function') {
|
|
26
|
-
scriptState = new Signal.Computed(() => Boolean(fn(self
|
|
26
|
+
scriptState = new Signal.Computed(() => Boolean(fn(self)));
|
|
27
27
|
} else {
|
|
28
28
|
const def = Boolean(fn);
|
|
29
29
|
scriptState = new Signal.Computed(() => def);
|
|
@@ -46,6 +46,8 @@ const createBooleanStates = (self, defState, fn, parent) => {
|
|
|
46
46
|
const string = v => typeof v === 'string' && v || null;
|
|
47
47
|
/** @param {*} v */
|
|
48
48
|
const number = v => typeof v === 'number' && v || null;
|
|
49
|
+
/** @param {*} v */
|
|
50
|
+
const regex = v =>v instanceof RegExp ? v : null;
|
|
49
51
|
|
|
50
52
|
/** @type {(v: Schema.Value | Schema.Value.Group | null) => v is Schema.Value | Schema.Value.Group} */
|
|
51
53
|
const valueFilter = /** @type {*} */(Boolean);
|
|
@@ -85,7 +87,7 @@ const values = v => {
|
|
|
85
87
|
* @param {Store} self
|
|
86
88
|
* @param {(v: any) => any?} toValue
|
|
87
89
|
* @param {T?} [defState]
|
|
88
|
-
* @param {T | ((store: Store
|
|
90
|
+
* @param {T | ((store: Store) => T?) | null} [fn]
|
|
89
91
|
* @returns {[Signal.State<T?>, Signal.Computed<T?>]}
|
|
90
92
|
*/
|
|
91
93
|
function createState(self, toValue, defState, fn) {
|
|
@@ -94,8 +96,8 @@ function createState(self, toValue, defState, fn) {
|
|
|
94
96
|
/** @type {Signal.Computed<T>} */
|
|
95
97
|
let scriptState;
|
|
96
98
|
if (typeof fn === 'function') {
|
|
97
|
-
const f = /** @type {(store: Store
|
|
98
|
-
scriptState = new Signal.Computed(() => toValue(f(self
|
|
99
|
+
const f = /** @type {(store: Store) => T?} */(fn);
|
|
100
|
+
scriptState = new Signal.Computed(() => toValue(f(self)));
|
|
99
101
|
} else {
|
|
100
102
|
const def = toValue(fn);
|
|
101
103
|
scriptState = new Signal.Computed(() => def);
|
|
@@ -186,8 +188,99 @@ function setStore$1(type, Class) {
|
|
|
186
188
|
TypeStores[type] = Class;
|
|
187
189
|
}
|
|
188
190
|
|
|
191
|
+
/** @import Store from './Store.mjs' */
|
|
192
|
+
/** @import { AsyncValidator, Validator } from '../types.mjs' */
|
|
193
|
+
/**
|
|
194
|
+
*
|
|
195
|
+
* @param {*} v
|
|
196
|
+
*/
|
|
197
|
+
function toResult(v) {
|
|
198
|
+
if (!v) { return ''; }
|
|
199
|
+
if (v instanceof Error) { return v.message; }
|
|
200
|
+
if (typeof v !== 'string') { return ''; }
|
|
201
|
+
return v;
|
|
202
|
+
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
*
|
|
206
|
+
* @param {Store} store
|
|
207
|
+
* @param {...Validator | undefined | null | (Validator | undefined | null)[]} validators
|
|
208
|
+
* @returns
|
|
209
|
+
*/
|
|
210
|
+
function createValidator(store, ...validators) {
|
|
211
|
+
const allValidators = validators.flat().filter(v => typeof v === 'function');
|
|
212
|
+
if (!allValidators.length) {
|
|
213
|
+
return new Signal.Computed(() => /** @type {string[]} */([]));
|
|
214
|
+
}
|
|
215
|
+
return new Signal.Computed(() => {
|
|
216
|
+
const results = [];
|
|
217
|
+
for (const validator of allValidators) {
|
|
218
|
+
try {
|
|
219
|
+
results.push(validator(store));
|
|
220
|
+
} catch (e){
|
|
221
|
+
results.push(e);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return results.flat().map(toResult).filter(Boolean);
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
*
|
|
229
|
+
* @param {Store} store
|
|
230
|
+
* @param {...AsyncValidator | undefined | null | (AsyncValidator | undefined | null)[]} validators
|
|
231
|
+
* @returns {[exec: () => Promise<string[]>, state: Signal.Computed<string[]>, stop: () => void]}
|
|
232
|
+
*/
|
|
233
|
+
function createAsyncValidator(store, ...validators) {
|
|
234
|
+
const allValidators = validators.flat().filter(v => typeof v === 'function');
|
|
235
|
+
if (!allValidators.length) {
|
|
236
|
+
return [
|
|
237
|
+
()=>Promise.resolve([]),
|
|
238
|
+
new Signal.Computed(() => /** @type {string[]} */([])),
|
|
239
|
+
() => {}
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
const st = new Signal.State(/** @type {string[]} */([]));
|
|
243
|
+
/**
|
|
244
|
+
*
|
|
245
|
+
* @param {AsyncValidator} validator
|
|
246
|
+
* @param {AbortSignal} signal
|
|
247
|
+
*/
|
|
248
|
+
async function run(validator, signal) {
|
|
249
|
+
let results = [];
|
|
250
|
+
try {
|
|
251
|
+
results.push(await validator(store, signal));
|
|
252
|
+
} catch (e){
|
|
253
|
+
results.push(e);
|
|
254
|
+
}
|
|
255
|
+
const list = results.flat().map(toResult).filter(Boolean);
|
|
256
|
+
if (!signal.aborted && list.length) { st.set([...st.get(), ...list]); }
|
|
257
|
+
return list;
|
|
258
|
+
}
|
|
259
|
+
/** @type {AbortController?} */
|
|
260
|
+
let ac = null;
|
|
261
|
+
function exec() {
|
|
262
|
+
ac?.abort();
|
|
263
|
+
ac = new AbortController();
|
|
264
|
+
const signal = ac.signal;
|
|
265
|
+
st.set([]);
|
|
266
|
+
|
|
267
|
+
return Promise.all(allValidators.map(f => run(f, signal))).then(v => v.flat());
|
|
268
|
+
}
|
|
269
|
+
return [exec, new Signal.Computed(() => st.get()), () => {ac?.abort(); st.set([]);}]
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
*
|
|
274
|
+
* @param {...Signal.Computed<string[]>} v
|
|
275
|
+
* @returns
|
|
276
|
+
*/
|
|
277
|
+
function merge(...v) {
|
|
278
|
+
const list = /** @type {Signal.Computed<string[]>[]} */(v.filter(Boolean));
|
|
279
|
+
return new Signal.Computed(() => list.flatMap(v => v.get()));
|
|
280
|
+
}
|
|
281
|
+
|
|
189
282
|
/** @import { Ref } from './ref.mjs' */
|
|
190
|
-
/** @import { Schema } from '../types.mjs' */
|
|
283
|
+
/** @import { AsyncValidator, Schema, Validator } from '../types.mjs' */
|
|
191
284
|
|
|
192
285
|
/**
|
|
193
286
|
* @template [T=any]
|
|
@@ -272,7 +365,12 @@ class Store {
|
|
|
272
365
|
* @param {number} [options.min] 日期、时间、数字的最小值
|
|
273
366
|
* @param {number} [options.max] 日期、时间、数字的最大值
|
|
274
367
|
* @param {number} [options.step] 日期、时间、数字的步长
|
|
368
|
+
* @param {number} [options.minLength]
|
|
369
|
+
* @param {number} [options.maxLength]
|
|
370
|
+
* @param {RegExp} [options.pattern]
|
|
275
371
|
* @param {(Schema.Value.Group | Schema.Value | string | number)[]} [options.values] 可选值
|
|
372
|
+
* @param {Validator | Validator[] | null} [options.validator]
|
|
373
|
+
* @param {{[k in keyof Schema.Events]?: AsyncValidator | AsyncValidator[] | null}} [options.validators]
|
|
276
374
|
*
|
|
277
375
|
* @param {Ref?} [options.ref]
|
|
278
376
|
*
|
|
@@ -286,9 +384,10 @@ class Store {
|
|
|
286
384
|
constructor(schema, {
|
|
287
385
|
null: isNull, state, ref,
|
|
288
386
|
setValue, setState, convert, onUpdate, onUpdateState,
|
|
387
|
+
validator, validators,
|
|
289
388
|
index, length, new: isNew, parent: parentNode,
|
|
290
389
|
hidden, clearable, required, disabled, readonly,
|
|
291
|
-
label, description, placeholder, min, max, step, values: values$1
|
|
390
|
+
label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
|
|
292
391
|
} = {}) {
|
|
293
392
|
this.schema = schema;
|
|
294
393
|
this.#state.set(typeof state === 'object' && state || {});
|
|
@@ -320,7 +419,7 @@ class Store {
|
|
|
320
419
|
/** @type {Signal.Computed<boolean>} */
|
|
321
420
|
let readonlyScript;
|
|
322
421
|
if (typeof readonlyFn === 'function') {
|
|
323
|
-
readonlyScript = new Signal.Computed(() => Boolean(readonlyFn(this
|
|
422
|
+
readonlyScript = new Signal.Computed(() => Boolean(readonlyFn(this)));
|
|
324
423
|
} else {
|
|
325
424
|
const def = Boolean(readonlyFn);
|
|
326
425
|
readonlyScript = new Signal.Computed(() => def);
|
|
@@ -347,9 +446,25 @@ class Store {
|
|
|
347
446
|
[this.#selfMin, this.#min] = createState(this, number, min, schema.min);
|
|
348
447
|
[this.#selfMax, this.#max] = createState(this, number, max, schema.max);
|
|
349
448
|
[this.#selfStep, this.#step] = createState(this, number, step, schema.step);
|
|
449
|
+
[this.#selfMinLength, this.#minLength] = createState(this, number, minLength, schema.minLength);
|
|
450
|
+
[this.#selfMaxLength, this.#maxLength] = createState(this, number, maxLength, schema.maxLength);
|
|
451
|
+
[this.#selfPattern, this.#pattern] = createState(this, regex, pattern, schema.pattern);
|
|
350
452
|
// @ts-ignore
|
|
351
453
|
[this.#selfValues, this.#values] = createState(this, values, values$1, schema.values);
|
|
352
454
|
|
|
455
|
+
const validatorResult = createValidator(this, schema.validator, validator);
|
|
456
|
+
|
|
457
|
+
const [changed, changedResult, cancelChange] = createAsyncValidator(this, schema.validators?.change, validators?.change);
|
|
458
|
+
const [blurred, blurredResult, cancelBlur] = createAsyncValidator(this, schema.validators?.blur, validators?.blur);
|
|
459
|
+
this.listen('change', () => {changed();});
|
|
460
|
+
this.listen('blur', () => {blurred();});
|
|
461
|
+
this.#errors = merge(validatorResult, changedResult, blurredResult);
|
|
462
|
+
this.#validatorResult = validatorResult;
|
|
463
|
+
this.#changed = changed;
|
|
464
|
+
this.#blurred = blurred;
|
|
465
|
+
this.#cancelChange = cancelChange;
|
|
466
|
+
this.#cancelBlur = cancelBlur;
|
|
467
|
+
|
|
353
468
|
if (length instanceof Signal.State || length instanceof Signal.Computed) {
|
|
354
469
|
this.#length = length;
|
|
355
470
|
} else {
|
|
@@ -534,6 +649,33 @@ class Store {
|
|
|
534
649
|
get step() { return this.#step.get(); }
|
|
535
650
|
set step(v) { this.#selfStep.set(number(v)); }
|
|
536
651
|
|
|
652
|
+
/** @readonly @type {Signal.State<number?>} */
|
|
653
|
+
#selfMinLength
|
|
654
|
+
/** @readonly @type {Signal.Computed<number?>} */
|
|
655
|
+
#minLength
|
|
656
|
+
get selfMinLength() { return this.#selfMinLength.get(); }
|
|
657
|
+
set selfMinLength(v) { this.#selfMinLength.set(number(v)); }
|
|
658
|
+
get minLength() { return this.#minLength.get(); }
|
|
659
|
+
set minLength(v) { this.#selfMinLength.set(number(v)); }
|
|
660
|
+
|
|
661
|
+
/** @readonly @type {Signal.State<number?>} */
|
|
662
|
+
#selfMaxLength
|
|
663
|
+
/** @readonly @type {Signal.Computed<number?>} */
|
|
664
|
+
#maxLength
|
|
665
|
+
get selfMaxLength() { return this.#selfMaxLength.get(); }
|
|
666
|
+
set selfMaxLength(v) { this.#selfMaxLength.set(number(v)); }
|
|
667
|
+
get maxLength() { return this.#maxLength.get(); }
|
|
668
|
+
set maxLength(v) { this.#selfMaxLength.set(number(v)); }
|
|
669
|
+
|
|
670
|
+
/** @readonly @type {Signal.State<RegExp?>} */
|
|
671
|
+
#selfPattern
|
|
672
|
+
/** @readonly @type {Signal.Computed<RegExp?>} */
|
|
673
|
+
#pattern
|
|
674
|
+
get selfPattern() { return this.#selfPattern.get(); }
|
|
675
|
+
set selfPattern(v) { this.#selfPattern.set(regex(v)); }
|
|
676
|
+
get pattern() { return this.#pattern.get(); }
|
|
677
|
+
set pattern(v) { this.#selfPattern.set(regex(v)); }
|
|
678
|
+
|
|
537
679
|
|
|
538
680
|
/** @readonly @type {Signal.State<(Schema.Value.Group | Schema.Value)[] | null>} */
|
|
539
681
|
#selfValues
|
|
@@ -544,6 +686,22 @@ class Store {
|
|
|
544
686
|
get values() { return this.#values.get(); }
|
|
545
687
|
set values(v) { this.#selfValues.set(values(v)); }
|
|
546
688
|
|
|
689
|
+
|
|
690
|
+
/** @type {Signal.Computed<string[]>} */
|
|
691
|
+
#errors
|
|
692
|
+
/** @type {Signal.Computed<string[]>} */
|
|
693
|
+
#validatorResult
|
|
694
|
+
/** @type {() => Promise<string[]>} */
|
|
695
|
+
#changed
|
|
696
|
+
/** @type {() => Promise<string[]>} */
|
|
697
|
+
#blurred
|
|
698
|
+
/** @type {() => void} */
|
|
699
|
+
#cancelChange
|
|
700
|
+
/** @type {() => void} */
|
|
701
|
+
#cancelBlur
|
|
702
|
+
get errors() { return this.#errors.get(); }
|
|
703
|
+
get error() { return this.#errors.get()[0]; }
|
|
704
|
+
|
|
547
705
|
/** @returns {IterableIterator<[key: string | number, value: Store]>} */
|
|
548
706
|
*[Symbol.iterator]() {}
|
|
549
707
|
/**
|
|
@@ -564,7 +722,8 @@ class Store {
|
|
|
564
722
|
|
|
565
723
|
get value() { return this.#value.get(); }
|
|
566
724
|
set value(v) {
|
|
567
|
-
const
|
|
725
|
+
const newValue = this.#setValue?.(v);
|
|
726
|
+
const val = newValue === undefined ? v : newValue;
|
|
568
727
|
this.#value.set(val);
|
|
569
728
|
if (!this.#set) {
|
|
570
729
|
this.#initValue.set(val);
|
|
@@ -575,7 +734,8 @@ class Store {
|
|
|
575
734
|
|
|
576
735
|
get state() { return this.#state.get(); }
|
|
577
736
|
set state(v) {
|
|
578
|
-
const
|
|
737
|
+
const newState = this.#setState?.(v);
|
|
738
|
+
const sta = newState === undefined ? v : newState;
|
|
579
739
|
this.#state.set(sta);
|
|
580
740
|
this.#onUpdateState?.(sta, this.#index.get(), this);
|
|
581
741
|
this.#requestUpdate();
|
|
@@ -594,21 +754,31 @@ class Store {
|
|
|
594
754
|
}
|
|
595
755
|
/**
|
|
596
756
|
*
|
|
597
|
-
* @param {*}
|
|
757
|
+
* @param {*} v
|
|
598
758
|
* @returns
|
|
599
759
|
*/
|
|
600
|
-
#reset(
|
|
601
|
-
this.#
|
|
602
|
-
|
|
760
|
+
#reset(v) {
|
|
761
|
+
const newValue = this.#setValue?.(v);
|
|
762
|
+
const value = newValue === undefined ? v : newValue;
|
|
763
|
+
this.#cancelChange();
|
|
764
|
+
this.#cancelBlur();
|
|
765
|
+
this.#set = true;
|
|
603
766
|
if (!value || typeof value !== 'object') {
|
|
604
767
|
for (const [, field] of this) {
|
|
605
768
|
field.#reset(null);
|
|
606
769
|
}
|
|
607
|
-
|
|
770
|
+
this.#value.set(value);
|
|
771
|
+
this.#initValue.set(value);
|
|
772
|
+
return value;
|
|
608
773
|
}
|
|
774
|
+
/** @type {*} */
|
|
775
|
+
const newValues = Array.isArray(value) ? [...value] : {...value};
|
|
609
776
|
for (const [key, field] of this) {
|
|
610
|
-
field.#reset(
|
|
777
|
+
newValues[key] = field.#reset(newValues[key]);
|
|
611
778
|
}
|
|
779
|
+
this.#value.set(newValues);
|
|
780
|
+
this.#initValue.set(newValues);
|
|
781
|
+
return newValues;
|
|
612
782
|
}
|
|
613
783
|
|
|
614
784
|
|
|
@@ -670,7 +840,39 @@ class Store {
|
|
|
670
840
|
}
|
|
671
841
|
return [val, sta];
|
|
672
842
|
}
|
|
673
|
-
|
|
843
|
+
/**
|
|
844
|
+
*
|
|
845
|
+
* @overload
|
|
846
|
+
* @param {null} [path]
|
|
847
|
+
* @returns {Promise<string[] | null>}
|
|
848
|
+
*/
|
|
849
|
+
/**
|
|
850
|
+
* @overload
|
|
851
|
+
* @param {(string | number)[]} path
|
|
852
|
+
* @returns {Promise<{ path: (string | number)[]; store: Store; errors: string[]}[]>}
|
|
853
|
+
*/
|
|
854
|
+
/**
|
|
855
|
+
*
|
|
856
|
+
* @param {(string | number)[]?} [path]
|
|
857
|
+
* @returns {Promise<string[] | { path: (string | number)[]; store: Store; errors: string[] | null;}[] | null>}
|
|
858
|
+
*/
|
|
859
|
+
validate(path) {
|
|
860
|
+
if (!Array.isArray(path)) {
|
|
861
|
+
return Promise.all([this.#validatorResult.get(), this.#changed(), this.#blurred()])
|
|
862
|
+
.then(v => {
|
|
863
|
+
const errors = v.flat();
|
|
864
|
+
return errors.length ? errors : null
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
const list = [this.validate().then(errors => {
|
|
868
|
+
if (!errors?.length) {return [];}
|
|
869
|
+
return [{path: [...path], store: /** @type {Store} */(this), errors}]
|
|
870
|
+
})];
|
|
871
|
+
for (const [key, field] of this) {
|
|
872
|
+
list.push(field.validate([...path, key]));
|
|
873
|
+
}
|
|
874
|
+
return Promise.all(list).then(v => v.flat())
|
|
875
|
+
}
|
|
674
876
|
}
|
|
675
877
|
|
|
676
878
|
/** @import { Schema } from '../types.mjs' */
|
|
@@ -1913,12 +2115,18 @@ const bindable = {
|
|
|
1913
2115
|
min: true,
|
|
1914
2116
|
max: true,
|
|
1915
2117
|
step: true,
|
|
2118
|
+
minLength: true,
|
|
2119
|
+
maxLength: true,
|
|
2120
|
+
pattern: true,
|
|
1916
2121
|
values: true,
|
|
1917
2122
|
|
|
1918
2123
|
null: true,
|
|
1919
2124
|
index: true,
|
|
1920
2125
|
no: true,
|
|
1921
2126
|
length: true,
|
|
2127
|
+
|
|
2128
|
+
error: true,
|
|
2129
|
+
errors: true,
|
|
1922
2130
|
};
|
|
1923
2131
|
/** @type {Set<keyof typeof bindable>} */
|
|
1924
2132
|
// @ts-ignore
|
|
@@ -1940,6 +2148,8 @@ function *toItem(val, key = '', sign = '$') {
|
|
|
1940
2148
|
yield [`${key}${sign}value`, {get: () => val.value, set: v => val.value = v}];
|
|
1941
2149
|
yield [`${key}${sign}state`, {get: () => val.state, set: v => val.state = v}];
|
|
1942
2150
|
yield [`${key}${sign}reset`, {exec: () => val.reset()}];
|
|
2151
|
+
// @ts-ignore
|
|
2152
|
+
yield [`${key}${sign}validate`, {exec: v => val.validate(v ? [] : null)}];
|
|
1943
2153
|
if (!(val instanceof ArrayStore)) { return; }
|
|
1944
2154
|
yield [`${key}${sign}insert`, {exec: (index, value) => val.insert(index, value)}];
|
|
1945
2155
|
yield [`${key}${sign}add`, {exec: (v) => val.add(v)}];
|
|
@@ -2210,6 +2420,9 @@ class Environment {
|
|
|
2210
2420
|
'$click': v => {store.emit('click', v); },
|
|
2211
2421
|
'$focus': v => {store.emit('focus', v); },
|
|
2212
2422
|
'$blur': v => {store.emit('blur', v); },
|
|
2423
|
+
'$reset': v => {store.reset(); },
|
|
2424
|
+
// @ts-ignore
|
|
2425
|
+
'$validate': v => {store.validate(v ? [] : null); },
|
|
2213
2426
|
}
|
|
2214
2427
|
}
|
|
2215
2428
|
|
|
@@ -2820,6 +3033,7 @@ class EventEmitter {
|
|
|
2820
3033
|
}
|
|
2821
3034
|
|
|
2822
3035
|
/** @import { Component } from '../types.mjs' */
|
|
3036
|
+
/** @import Store from '../Store/index.mjs' */
|
|
2823
3037
|
/** @import Environment from './Environment/index.mjs' */
|
|
2824
3038
|
|
|
2825
3039
|
|
|
@@ -2944,9 +3158,10 @@ const eventFilters = {
|
|
|
2944
3158
|
*
|
|
2945
3159
|
* @param {Component | string} component
|
|
2946
3160
|
* @param {Environment} env
|
|
3161
|
+
* @param {((store: Store, el: Element) => () => void)?} [relate]
|
|
2947
3162
|
* @returns
|
|
2948
3163
|
*/
|
|
2949
|
-
function createContext(component, env) {
|
|
3164
|
+
function createContext(component, env, relate) {
|
|
2950
3165
|
const tag = typeof component === 'string' ? component : component.tag;
|
|
2951
3166
|
const { attrs, events } = typeof component !== 'string' && component || { attrs: null, events: null };
|
|
2952
3167
|
|
|
@@ -2958,7 +3173,8 @@ function createContext(component, env) {
|
|
|
2958
3173
|
const attrStates = Object.create(null);
|
|
2959
3174
|
const tagAttrs = Object.create(null);
|
|
2960
3175
|
|
|
2961
|
-
|
|
3176
|
+
/** @type {Set<() => void>} */
|
|
3177
|
+
const cancelFns = new Set();
|
|
2962
3178
|
|
|
2963
3179
|
/** @type {[string, ($event: any) => void, AddEventListenerOptions][]} */
|
|
2964
3180
|
const allEvents = [];
|
|
@@ -2978,13 +3194,28 @@ function createContext(component, env) {
|
|
|
2978
3194
|
old = v;
|
|
2979
3195
|
fn(v, o, name);
|
|
2980
3196
|
}, true);
|
|
2981
|
-
|
|
3197
|
+
cancelFns.add(w);
|
|
2982
3198
|
|
|
2983
3199
|
return () => {
|
|
2984
|
-
|
|
3200
|
+
cancelFns.delete(w);
|
|
2985
3201
|
w();
|
|
2986
3202
|
};
|
|
2987
3203
|
},
|
|
3204
|
+
relate(el) {
|
|
3205
|
+
if (!relate || destroyed) { return () => { }; }
|
|
3206
|
+
try {
|
|
3207
|
+
const w = relate(env.store, el);
|
|
3208
|
+
if (typeof w !== 'function') { return () => { }; }
|
|
3209
|
+
cancelFns.add(w);
|
|
3210
|
+
return () => {
|
|
3211
|
+
cancelFns.delete(w);
|
|
3212
|
+
w();
|
|
3213
|
+
};
|
|
3214
|
+
} catch {
|
|
3215
|
+
return () => { };
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
},
|
|
2988
3219
|
get destroyed() { return destroyedState.get(); },
|
|
2989
3220
|
get init() { return mountedState.get(); },
|
|
2990
3221
|
listen(name, listener) { return stateEmitter.listen(name, listener); },
|
|
@@ -3053,7 +3284,7 @@ function createContext(component, env) {
|
|
|
3053
3284
|
if (destroyed) { return; }
|
|
3054
3285
|
destroyed = true;
|
|
3055
3286
|
destroyedState.set(true);
|
|
3056
|
-
for (const w of
|
|
3287
|
+
for (const w of cancelFns) {
|
|
3057
3288
|
w();
|
|
3058
3289
|
}
|
|
3059
3290
|
stateEmitter.emit('destroy');
|
|
@@ -3703,9 +3934,10 @@ function bindBase(handler, env, bind) {
|
|
|
3703
3934
|
* @param {Environment} env
|
|
3704
3935
|
* @param {Record<string, [Layout.Node, Environment]>} templates
|
|
3705
3936
|
* @param {string[]} componentPath
|
|
3937
|
+
* @param {((store: Store, el: Element) => () => void)?} [relate]
|
|
3706
3938
|
* @param {Component.Getter?} [getComponent]
|
|
3707
3939
|
*/
|
|
3708
|
-
function renderItem(layout, parent, next, env, templates, componentPath, getComponent) {
|
|
3940
|
+
function renderItem(layout, parent, next, env, templates, componentPath, relate, getComponent) {
|
|
3709
3941
|
env = env.set(layout.aliases, layout.vars);
|
|
3710
3942
|
const bind = layout.directives.bind;
|
|
3711
3943
|
const fragment = layout.directives.fragment;
|
|
@@ -3714,18 +3946,18 @@ function renderItem(layout, parent, next, env, templates, componentPath, getComp
|
|
|
3714
3946
|
if (!template) { return () => {}; }
|
|
3715
3947
|
const [templateLayout, templateEnv] = template;
|
|
3716
3948
|
const newEnv = templateEnv.params(templateLayout, layout, env, bind);
|
|
3717
|
-
return render(templateLayout, parent, next, newEnv, templates, componentPath, getComponent);
|
|
3949
|
+
return render(templateLayout, parent, next, newEnv, templates, componentPath, relate, getComponent);
|
|
3718
3950
|
}
|
|
3719
3951
|
if (!layout.name || layout.directives.fragment) {
|
|
3720
3952
|
return renderFillDirectives(parent, next, env, layout.directives) ||
|
|
3721
3953
|
renderList(layout.children || [], parent, next, env, templates, (layout, templates) => {
|
|
3722
|
-
return render(layout, parent, next, env, templates, componentPath, getComponent);
|
|
3954
|
+
return render(layout, parent, next, env, templates, componentPath, relate, getComponent);
|
|
3723
3955
|
});
|
|
3724
3956
|
}
|
|
3725
3957
|
const path = [...componentPath, layout.name];
|
|
3726
3958
|
const component = getComponent?.(path);
|
|
3727
3959
|
if (getComponent && !component) { return () => { }; }
|
|
3728
|
-
const { context, handler } = createContext(component ? component : layout.name, env);
|
|
3960
|
+
const { context, handler } = createContext(component ? component : layout.name, env, relate);
|
|
3729
3961
|
|
|
3730
3962
|
|
|
3731
3963
|
const componentAttrs = component?.attrs;
|
|
@@ -3752,7 +3984,7 @@ function renderItem(layout, parent, next, env, templates, componentPath, getComp
|
|
|
3752
3984
|
const children = slot ?
|
|
3753
3985
|
renderFillDirectives(slot, null, env, layout.directives)
|
|
3754
3986
|
|| renderList(layout.children || [], slot, null, env, templates, (layout, templates) => {
|
|
3755
|
-
return render(layout, slot, null, env, templates, componentPath, getComponent);
|
|
3987
|
+
return render(layout, slot, null, env, templates, componentPath, relate, getComponent);
|
|
3756
3988
|
}) : () => {};
|
|
3757
3989
|
|
|
3758
3990
|
|
|
@@ -3777,16 +4009,17 @@ function renderItem(layout, parent, next, env, templates, componentPath, getComp
|
|
|
3777
4009
|
* @param {Environment} env
|
|
3778
4010
|
* @param {Record<string, [Layout.Node, Environment]>} templates
|
|
3779
4011
|
* @param {string[]} componentPath
|
|
4012
|
+
* @param {((store: Store, el: Element) => () => void)?} [relate]
|
|
3780
4013
|
* @param {Component.Getter?} [getComponent]
|
|
3781
4014
|
* @returns {() => void}
|
|
3782
4015
|
*/
|
|
3783
|
-
function render(layout, parent, next, env, templates, componentPath, getComponent) {
|
|
4016
|
+
function render(layout, parent, next, env, templates, componentPath, relate, getComponent) {
|
|
3784
4017
|
const { directives } = layout;
|
|
3785
4018
|
const newEnv = env.child(directives.value);
|
|
3786
4019
|
if (!newEnv) { return () => {}; }
|
|
3787
4020
|
const list = newEnv.enum(directives.enum);
|
|
3788
4021
|
/** @type {(next: Node | null, env: any) => () => void} */
|
|
3789
|
-
const r = (next, env) => renderItem(layout, parent, next, env, templates, componentPath, getComponent);
|
|
4022
|
+
const r = (next, env) => renderItem(layout, parent, next, env, templates, componentPath, relate, getComponent);
|
|
3790
4023
|
if (list === true) {
|
|
3791
4024
|
return r(next, newEnv);
|
|
3792
4025
|
}
|
|
@@ -3803,37 +4036,21 @@ function render(layout, parent, next, env, templates, componentPath, getComponen
|
|
|
3803
4036
|
}
|
|
3804
4037
|
|
|
3805
4038
|
/**
|
|
3806
|
-
* @overload
|
|
3807
4039
|
* @param {Store} store
|
|
3808
4040
|
* @param {(Layout.Node | string)[]} layouts
|
|
3809
4041
|
* @param {Element} parent
|
|
3810
|
-
* @param {
|
|
3811
|
-
* @param {(
|
|
3812
|
-
* @
|
|
3813
|
-
|
|
3814
|
-
/**
|
|
3815
|
-
* @overload
|
|
3816
|
-
* @param {Store} store
|
|
3817
|
-
* @param {(Layout.Node | string)[]} layouts
|
|
3818
|
-
* @param {Element} parent
|
|
3819
|
-
* @param {Component.Getter?} [components]
|
|
4042
|
+
* @param {object} [options]
|
|
4043
|
+
* @param {Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>} [options.global]
|
|
4044
|
+
* @param {(path: string[]) => Component?} [options.component]
|
|
4045
|
+
* @param {(store: Store, el: Element) => () => void} [options.relate]
|
|
3820
4046
|
* @returns {() => void}
|
|
3821
4047
|
*/
|
|
3822
|
-
|
|
3823
|
-
* @param {Store} store
|
|
3824
|
-
* @param {(Layout.Node | string)[]} layouts
|
|
3825
|
-
* @param {Element} parent
|
|
3826
|
-
* @param {Component.Getter | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt1]
|
|
3827
|
-
* @param {Component.Getter | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt2]
|
|
3828
|
-
*/
|
|
3829
|
-
function index (store, layouts, parent, opt1, opt2) {
|
|
3830
|
-
const options = [opt1, opt2];
|
|
3831
|
-
const components = options.find(v => typeof v === 'function');
|
|
3832
|
-
const global = options.find(v => typeof v === 'object');
|
|
4048
|
+
function index (store, layouts, parent, {component, global, relate} = {}) {
|
|
3833
4049
|
const env = new Environment(store, global);
|
|
3834
4050
|
const templates = Object.create(null);
|
|
4051
|
+
const relateFn = typeof relate === 'function' ? relate : null;
|
|
3835
4052
|
return renderList(layouts, parent, null, env, templates, (layout, templates) => {
|
|
3836
|
-
return render(layout, parent, null, env, templates, [],
|
|
4053
|
+
return render(layout, parent, null, env, templates, [], relateFn, component);
|
|
3837
4054
|
});
|
|
3838
4055
|
}
|
|
3839
4056
|
|