@neeloong/form 0.6.2 → 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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.6.2
2
+ * @neeloong/form v0.8.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -591,7 +591,7 @@
591
591
  *
592
592
  * @param {Store} self
593
593
  * @param {boolean?} [defState]
594
- * @param {boolean | ((store: Store, root: Store) => boolean?) | null} [fn]
594
+ * @param {boolean | ((store: Store) => boolean?) | null} [fn]
595
595
  * @param {Signal.Computed<boolean>?} [parent]
596
596
  * @returns {[Signal.State<boolean?>, Signal.Computed<boolean>]}
597
597
  */
@@ -601,7 +601,7 @@
601
601
  /** @type {Signal.Computed<boolean>} */
602
602
  let scriptState;
603
603
  if (typeof fn === 'function') {
604
- scriptState = new exports.Signal.Computed(() => Boolean(fn(self, self.root)));
604
+ scriptState = new exports.Signal.Computed(() => Boolean(fn(self)));
605
605
  } else {
606
606
  const def = Boolean(fn);
607
607
  scriptState = new exports.Signal.Computed(() => def);
@@ -624,6 +624,8 @@
624
624
  const string = v => typeof v === 'string' && v || null;
625
625
  /** @param {*} v */
626
626
  const number = v => typeof v === 'number' && v || null;
627
+ /** @param {*} v */
628
+ const regex = v =>v instanceof RegExp ? v : null;
627
629
 
628
630
  /** @type {(v: Schema.Value | Schema.Value.Group | null) => v is Schema.Value | Schema.Value.Group} */
629
631
  const valueFilter = /** @type {*} */(Boolean);
@@ -663,7 +665,7 @@
663
665
  * @param {Store} self
664
666
  * @param {(v: any) => any?} toValue
665
667
  * @param {T?} [defState]
666
- * @param {T | ((store: Store, root: Store) => T?) | null} [fn]
668
+ * @param {T | ((store: Store) => T?) | null} [fn]
667
669
  * @returns {[Signal.State<T?>, Signal.Computed<T?>]}
668
670
  */
669
671
  function createState(self, toValue, defState, fn) {
@@ -672,8 +674,8 @@
672
674
  /** @type {Signal.Computed<T>} */
673
675
  let scriptState;
674
676
  if (typeof fn === 'function') {
675
- const f = /** @type {(store: Store, root: Store) => T?} */(fn);
676
- scriptState = new exports.Signal.Computed(() => toValue(f(self, self.root)));
677
+ const f = /** @type {(store: Store) => T?} */(fn);
678
+ scriptState = new exports.Signal.Computed(() => toValue(f(self)));
677
679
  } else {
678
680
  const def = toValue(fn);
679
681
  scriptState = new exports.Signal.Computed(() => def);
@@ -688,7 +690,176 @@
688
690
 
689
691
  }
690
692
 
693
+ const ref = Symbol();
694
+ /** @import Store from './index.mjs' */
695
+ /** @typedef {{ [ref]: Store; [k: string]: Ref | undefined;}} Ref */
696
+
697
+ /**
698
+ * @param {Store} store
699
+ */
700
+ function createRef(store) {
701
+
702
+ /** @type{Ref} */
703
+ const r = { [ref]: store };
704
+ for (const [k, f] of store) {
705
+ Object.defineProperty(r, k, {
706
+ get() { return f.ref; },
707
+ configurable: false,
708
+ enumerable: true,
709
+ });
710
+ }
711
+ Object.defineProperty(r, ref, {
712
+ get() { return store; },
713
+ configurable: false,
714
+ enumerable: true,
715
+ });
716
+ return r;
717
+ }
718
+
691
719
  /** @import { Schema } from '../types.mjs' */
720
+
721
+
722
+ /** @type {{new(...p: ConstructorParameters<typeof Store>): Store}?} */
723
+ let ObjectStore$1 = null;
724
+ /** @type {{new(...p: ConstructorParameters<typeof Store>): Store}?} */
725
+ let ArrayStore$1 = null;
726
+ /** @type {Record<string, {new(...p: ConstructorParameters<typeof Store>): Store}?>} */
727
+ let TypeStores = Object.create(null);
728
+ /**
729
+ * @param {Schema.Field} schema
730
+ * @param {object} [options]
731
+ * @param {Store?} [options.parent]
732
+ * @param {string | number | null} [options.index]
733
+ * @param {boolean} [options.new]
734
+ * @param {(value: any, index: any, store: Store) => void} [options.onUpdate]
735
+ * @param {(value: any, index: any, store: Store) => void} [options.onUpdateState]
736
+ */
737
+ function create(schema, options) {
738
+ const type = schema.type;
739
+ /** @type {{new(...p: ConstructorParameters<typeof Store>): Store}} */
740
+ let Class = Store;
741
+ if (schema.array && !(ArrayStore$1 && options?.parent instanceof ArrayStore$1)) {
742
+ if (ArrayStore$1) { Class = ArrayStore$1; }
743
+ } else if (typeof type === 'string') {
744
+ const C = TypeStores[type];
745
+ if (C) { Class = C; }
746
+ } else if (type && typeof type === 'object') {
747
+ if (ObjectStore$1) { Class = ObjectStore$1; }
748
+ }
749
+ return new Class(schema, options);
750
+ }
751
+
752
+ /** @param {{new(...p: ConstructorParameters<typeof Store>): Store}} Class */
753
+ function setObjectStore(Class) {
754
+ ObjectStore$1 = Class;
755
+ }
756
+
757
+ /** @param {{new(...p: ConstructorParameters<typeof Store>): Store}} Class */
758
+ function setArrayStore(Class) {
759
+ ArrayStore$1 = Class;
760
+ }
761
+ /**
762
+ * @param {string} type
763
+ * @param {{new(...p: ConstructorParameters<typeof Store>): Store}} Class
764
+ */
765
+ function setStore$1(type, Class) {
766
+ TypeStores[type] = Class;
767
+ }
768
+
769
+ /** @import Store from './Store.mjs' */
770
+ /** @import { AsyncValidator, Validator } from '../types.mjs' */
771
+ /**
772
+ *
773
+ * @param {*} v
774
+ */
775
+ function toResult(v) {
776
+ if (!v) { return ''; }
777
+ if (v instanceof Error) { return v.message; }
778
+ if (typeof v !== 'string') { return ''; }
779
+ return v;
780
+
781
+ }
782
+ /**
783
+ *
784
+ * @param {Store} store
785
+ * @param {...Validator | undefined | null | (Validator | undefined | null)[]} validators
786
+ * @returns
787
+ */
788
+ function createValidator(store, ...validators) {
789
+ const allValidators = validators.flat().filter(v => typeof v === 'function');
790
+ if (!allValidators.length) {
791
+ return new exports.Signal.Computed(() => /** @type {string[]} */([]));
792
+ }
793
+ return new exports.Signal.Computed(() => {
794
+ const results = [];
795
+ for (const validator of allValidators) {
796
+ try {
797
+ results.push(validator(store));
798
+ } catch (e){
799
+ results.push(e);
800
+ }
801
+ }
802
+ return results.flat().map(toResult).filter(Boolean);
803
+ })
804
+ }
805
+ /**
806
+ *
807
+ * @param {Store} store
808
+ * @param {...AsyncValidator | undefined | null | (AsyncValidator | undefined | null)[]} validators
809
+ * @returns {[exec: () => Promise<string[]>, state: Signal.Computed<string[]>, stop: () => void]}
810
+ */
811
+ function createAsyncValidator(store, ...validators) {
812
+ const allValidators = validators.flat().filter(v => typeof v === 'function');
813
+ if (!allValidators.length) {
814
+ return [
815
+ ()=>Promise.resolve([]),
816
+ new exports.Signal.Computed(() => /** @type {string[]} */([])),
817
+ () => {}
818
+ ];
819
+ }
820
+ const st = new exports.Signal.State(/** @type {string[]} */([]));
821
+ /**
822
+ *
823
+ * @param {AsyncValidator} validator
824
+ * @param {AbortSignal} signal
825
+ */
826
+ async function run(validator, signal) {
827
+ let results = [];
828
+ try {
829
+ results.push(await validator(store, signal));
830
+ } catch (e){
831
+ results.push(e);
832
+ }
833
+ const list = results.flat().map(toResult).filter(Boolean);
834
+ if (!signal.aborted && list.length) { st.set([...st.get(), ...list]); }
835
+ return list;
836
+ }
837
+ /** @type {AbortController?} */
838
+ let ac = null;
839
+ function exec() {
840
+ ac?.abort();
841
+ ac = new AbortController();
842
+ const signal = ac.signal;
843
+ st.set([]);
844
+
845
+ return Promise.all(allValidators.map(f => run(f, signal))).then(v => v.flat());
846
+ }
847
+ return [exec, new exports.Signal.Computed(() => st.get()), () => {ac?.abort(); st.set([]);}]
848
+ }
849
+
850
+ /**
851
+ *
852
+ * @param {...Signal.Computed<string[]>} v
853
+ * @returns
854
+ */
855
+ function merge(...v) {
856
+ const list = /** @type {Signal.Computed<string[]>[]} */(v.filter(Boolean));
857
+ return new exports.Signal.Computed(() => list.flatMap(v => v.get()));
858
+ }
859
+
860
+ /** @import { Ref } from './ref.mjs' */
861
+ /** @import { AsyncValidator, Schema, Validator } from '../types.mjs' */
862
+
692
863
  /**
693
864
  * @template [T=any]
694
865
  */
@@ -736,14 +907,24 @@
736
907
  * @param {boolean} [options.new]
737
908
  */
738
909
  static create(schema, options = {}) {
739
- return new ObjectStore({type: schema}, { ...options, parent: null });
910
+ return create({type: schema}, { ...options, parent: null });
911
+ }
912
+ /**
913
+ * @param {string} type
914
+ * @param {{new(...p: ConstructorParameters<typeof Store>): Store}} Class
915
+ */
916
+ static setStore(type, Class) {
917
+ return setStore$1(type, Class);
740
918
  }
741
919
  #null = false;
742
920
  get null() { return this.#null; }
743
921
  get kind() { return ''; }
922
+ /** @type {Ref?} */
923
+ #ref = null;
924
+ get ref() { return this.#ref || createRef(this); }
744
925
  /**
745
926
  * @param {Schema.Field} schema
746
- * @param {object} options
927
+ * @param {object} [options]
747
928
  * @param {*} [options.parent]
748
929
  * @param {*} [options.state]
749
930
  * @param {number | string | null} [options.index]
@@ -762,21 +943,30 @@
762
943
  * @param {number} [options.min] 日期、时间、数字的最小值
763
944
  * @param {number} [options.max] 日期、时间、数字的最大值
764
945
  * @param {number} [options.step] 日期、时间、数字的步长
946
+ * @param {number} [options.minLength]
947
+ * @param {number} [options.maxLength]
948
+ * @param {RegExp} [options.pattern]
765
949
  * @param {(Schema.Value.Group | Schema.Value | string | number)[]} [options.values] 可选值
950
+ * @param {Validator | Validator[] | null} [options.validator]
951
+ * @param {{[k in keyof Schema.Events]?: AsyncValidator | AsyncValidator[] | null}} [options.validators]
952
+ *
953
+ * @param {Ref?} [options.ref]
766
954
  *
767
955
  * @param {((value: any) => any)?} [options.setValue]
768
956
  * @param {((value: any) => any)?} [options.setState]
769
957
  * @param {((value: any, state: any) => [value: any, state: any])?} [options.convert]
770
- * @param {((value: T?, index: any) => void)?} [options.onUpdate]
771
- * @param {((value: T?, index: any) => void)?} [options.onUpdateState]
958
+ *
959
+ * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdate]
960
+ * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdateState]
772
961
  */
773
962
  constructor(schema, {
774
- null: isNull, state,
963
+ null: isNull, state, ref,
775
964
  setValue, setState, convert, onUpdate, onUpdateState,
965
+ validator, validators,
776
966
  index, length, new: isNew, parent: parentNode,
777
967
  hidden, clearable, required, disabled, readonly,
778
- label, description, placeholder, min, max, step, values: values$1
779
- }) {
968
+ label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
969
+ } = {}) {
780
970
  this.schema = schema;
781
971
  this.#state.set(typeof state === 'object' && state || {});
782
972
  const parent = parentNode instanceof Store ? parentNode : null;
@@ -807,7 +997,7 @@
807
997
  /** @type {Signal.Computed<boolean>} */
808
998
  let readonlyScript;
809
999
  if (typeof readonlyFn === 'function') {
810
- readonlyScript = new exports.Signal.Computed(() => Boolean(readonlyFn(this, this.root)));
1000
+ readonlyScript = new exports.Signal.Computed(() => Boolean(readonlyFn(this)));
811
1001
  } else {
812
1002
  const def = Boolean(readonlyFn);
813
1003
  readonlyScript = new exports.Signal.Computed(() => def);
@@ -834,9 +1024,25 @@
834
1024
  [this.#selfMin, this.#min] = createState(this, number, min, schema.min);
835
1025
  [this.#selfMax, this.#max] = createState(this, number, max, schema.max);
836
1026
  [this.#selfStep, this.#step] = createState(this, number, step, schema.step);
1027
+ [this.#selfMinLength, this.#minLength] = createState(this, number, minLength, schema.minLength);
1028
+ [this.#selfMaxLength, this.#maxLength] = createState(this, number, maxLength, schema.maxLength);
1029
+ [this.#selfPattern, this.#pattern] = createState(this, regex, pattern, schema.pattern);
837
1030
  // @ts-ignore
838
1031
  [this.#selfValues, this.#values] = createState(this, values, values$1, schema.values);
839
1032
 
1033
+ const validatorResult = createValidator(this, schema.validator, validator);
1034
+
1035
+ const [changed, changedResult, cancelChange] = createAsyncValidator(this, schema.validators?.change, validators?.change);
1036
+ const [blurred, blurredResult, cancelBlur] = createAsyncValidator(this, schema.validators?.blur, validators?.blur);
1037
+ this.listen('change', () => {changed();});
1038
+ this.listen('blur', () => {blurred();});
1039
+ this.#errors = merge(validatorResult, changedResult, blurredResult);
1040
+ this.#validatorResult = validatorResult;
1041
+ this.#changed = changed;
1042
+ this.#blurred = blurred;
1043
+ this.#cancelChange = cancelChange;
1044
+ this.#cancelBlur = cancelBlur;
1045
+
840
1046
  if (length instanceof exports.Signal.State || length instanceof exports.Signal.Computed) {
841
1047
  this.#length = length;
842
1048
  } else {
@@ -845,8 +1051,10 @@
845
1051
 
846
1052
  if (isNull) {
847
1053
  this.#null = true;
1054
+ this.#ref = createRef(this);
848
1055
  return;
849
1056
  }
1057
+ this.#ref = ref || null;
850
1058
  this.#onUpdate = onUpdate || null;
851
1059
  this.#onUpdateState = onUpdateState || null;
852
1060
  this.#setValue = typeof setValue === 'function' ? setValue : null;
@@ -859,19 +1067,16 @@
859
1067
  // @ts-ignore
860
1068
  this.listen(k, f);
861
1069
  }
862
-
863
-
864
1070
  }
865
- #destroyed = false;
866
1071
  /** @type {((value: any) => any)?} */
867
1072
  #setValue = null
868
1073
  /** @type {((value: any) => any)?} */
869
1074
  #setState = null
870
1075
  /** @type {((value: any, state: any) => [value: any, state: any])?} */
871
1076
  #convert = null
872
- /** @type {((value: any, index: any) => void)?} */
1077
+ /** @type {((value: any, index: any, store: Store) => void)?} */
873
1078
  #onUpdate = null
874
- /** @type {((value: any, index: any) => void)?} */
1079
+ /** @type {((value: any, index: any, store: Store) => void)?} */
875
1080
  #onUpdateState = null
876
1081
  /** @readonly @type {Store?} */
877
1082
  #parent = null;
@@ -1022,6 +1227,33 @@
1022
1227
  get step() { return this.#step.get(); }
1023
1228
  set step(v) { this.#selfStep.set(number(v)); }
1024
1229
 
1230
+ /** @readonly @type {Signal.State<number?>} */
1231
+ #selfMinLength
1232
+ /** @readonly @type {Signal.Computed<number?>} */
1233
+ #minLength
1234
+ get selfMinLength() { return this.#selfMinLength.get(); }
1235
+ set selfMinLength(v) { this.#selfMinLength.set(number(v)); }
1236
+ get minLength() { return this.#minLength.get(); }
1237
+ set minLength(v) { this.#selfMinLength.set(number(v)); }
1238
+
1239
+ /** @readonly @type {Signal.State<number?>} */
1240
+ #selfMaxLength
1241
+ /** @readonly @type {Signal.Computed<number?>} */
1242
+ #maxLength
1243
+ get selfMaxLength() { return this.#selfMaxLength.get(); }
1244
+ set selfMaxLength(v) { this.#selfMaxLength.set(number(v)); }
1245
+ get maxLength() { return this.#maxLength.get(); }
1246
+ set maxLength(v) { this.#selfMaxLength.set(number(v)); }
1247
+
1248
+ /** @readonly @type {Signal.State<RegExp?>} */
1249
+ #selfPattern
1250
+ /** @readonly @type {Signal.Computed<RegExp?>} */
1251
+ #pattern
1252
+ get selfPattern() { return this.#selfPattern.get(); }
1253
+ set selfPattern(v) { this.#selfPattern.set(regex(v)); }
1254
+ get pattern() { return this.#pattern.get(); }
1255
+ set pattern(v) { this.#selfPattern.set(regex(v)); }
1256
+
1025
1257
 
1026
1258
  /** @readonly @type {Signal.State<(Schema.Value.Group | Schema.Value)[] | null>} */
1027
1259
  #selfValues
@@ -1033,8 +1265,20 @@
1033
1265
  set values(v) { this.#selfValues.set(values(v)); }
1034
1266
 
1035
1267
 
1036
-
1037
-
1268
+ /** @type {Signal.Computed<string[]>} */
1269
+ #errors
1270
+ /** @type {Signal.Computed<string[]>} */
1271
+ #validatorResult
1272
+ /** @type {() => Promise<string[]>} */
1273
+ #changed
1274
+ /** @type {() => Promise<string[]>} */
1275
+ #blurred
1276
+ /** @type {() => void} */
1277
+ #cancelChange
1278
+ /** @type {() => void} */
1279
+ #cancelBlur
1280
+ get errors() { return this.#errors.get(); }
1281
+ get error() { return this.#errors.get()[0]; }
1038
1282
 
1039
1283
  /** @returns {IterableIterator<[key: string | number, value: Store]>} */
1040
1284
  *[Symbol.iterator]() {}
@@ -1046,39 +1290,32 @@
1046
1290
  child(key) { return null; }
1047
1291
 
1048
1292
  #set = false;
1049
- /** @type {T?} */
1050
- #initValue = null;
1051
- #lastValue = this.#initValue;
1052
- #value = new exports.Signal.State(this.#initValue);
1293
+ #initValue = new exports.Signal.State(/** @type {T?} */(null));
1294
+ #value = new exports.Signal.State(this.#initValue.get());
1053
1295
 
1054
1296
 
1055
1297
  #state = new exports.Signal.State(/** @type {any} */(null));
1056
- #lastState = this.#state.get();
1057
-
1058
1298
 
1059
- get changed() { return this.#value.get() === this.#lastValue; }
1060
- get saved() { return this.#value.get() === this.#initValue; }
1299
+ get changed() { return this.#value.get() === this.#initValue.get(); }
1061
1300
 
1062
1301
  get value() { return this.#value.get(); }
1063
1302
  set value(v) {
1064
- if (this.#destroyed) { return; }
1065
- const val = this.#setValue?.(v) || v;
1303
+ const newValue = this.#setValue?.(v);
1304
+ const val = newValue === undefined ? v : newValue;
1066
1305
  this.#value.set(val);
1067
1306
  if (!this.#set) {
1068
- this.#set = true;
1069
- this.#initValue = v;
1307
+ this.#initValue.set(val);
1070
1308
  }
1071
- this.#onUpdate?.(this.#value.get(), this.#index.get());
1309
+ this.#onUpdate?.(val, this.#index.get(), this);
1072
1310
  this.#requestUpdate();
1073
1311
  }
1074
1312
 
1075
1313
  get state() { return this.#state.get(); }
1076
1314
  set state(v) {
1077
- if (this.#destroyed) { return; }
1078
- const sta = this.#setState?.(v) || v;
1315
+ const newState = this.#setState?.(v);
1316
+ const sta = newState === undefined ? v : newState;
1079
1317
  this.#state.set(sta);
1080
- this.#set = true;
1081
- this.#onUpdateState?.(this.#state.get(), this.#index.get());
1318
+ this.#onUpdateState?.(sta, this.#index.get(), this);
1082
1319
  this.#requestUpdate();
1083
1320
  }
1084
1321
  #requestUpdate() {
@@ -1087,9 +1324,40 @@
1087
1324
  queueMicrotask(() => {
1088
1325
  const oldValue = this.#value.get();
1089
1326
  const oldState = this.#state.get();
1090
- return this.#runUpdate(oldValue, oldState);
1327
+ this.#runUpdate(oldValue, oldState);
1091
1328
  });
1092
1329
  }
1330
+ reset(value = this.#initValue.get()) {
1331
+ this.#reset(value);
1332
+ }
1333
+ /**
1334
+ *
1335
+ * @param {*} v
1336
+ * @returns
1337
+ */
1338
+ #reset(v) {
1339
+ const newValue = this.#setValue?.(v);
1340
+ const value = newValue === undefined ? v : newValue;
1341
+ this.#cancelChange();
1342
+ this.#cancelBlur();
1343
+ this.#set = true;
1344
+ if (!value || typeof value !== 'object') {
1345
+ for (const [, field] of this) {
1346
+ field.#reset(null);
1347
+ }
1348
+ this.#value.set(value);
1349
+ this.#initValue.set(value);
1350
+ return value;
1351
+ }
1352
+ /** @type {*} */
1353
+ const newValues = Array.isArray(value) ? [...value] : {...value};
1354
+ for (const [key, field] of this) {
1355
+ newValues[key] = field.#reset(newValues[key]);
1356
+ }
1357
+ this.#value.set(newValues);
1358
+ this.#initValue.set(newValues);
1359
+ return newValues;
1360
+ }
1093
1361
 
1094
1362
 
1095
1363
  #needUpdate = false;
@@ -1100,26 +1368,21 @@
1100
1368
  * @returns
1101
1369
  */
1102
1370
  #toUpdate(value, state) {
1103
- if (this.#destroyed) { return value; }
1104
1371
  const [val,sta] = this.#convert?.(value, state) || [value, state];
1105
1372
  if(this.#value.get() === val && this.#state.get() === sta) { return [val,sta] }
1106
1373
  this.#value.set(val);
1107
1374
  this.#state.set(sta);
1108
- if (!this.#set) {
1109
- this.#set = true;
1110
- this.#initValue = val;
1111
- }
1112
1375
  return this.#runUpdate(val, sta);
1113
1376
  }
1114
1377
  /**
1115
1378
  *
1116
1379
  * @param {*} val
1117
1380
  * @param {*} sta
1118
- * @returns
1381
+ * @returns {[any, any]}
1119
1382
  */
1120
1383
  #runUpdate(val, sta) {
1121
- if (this.#destroyed) { return [val, sta]; }
1122
1384
  this.#needUpdate = false;
1385
+ let initValue = val;
1123
1386
  if (val && typeof val === 'object') {
1124
1387
  /** @type {T} */
1125
1388
  // @ts-ignore
@@ -1144,34 +1407,58 @@
1144
1407
  if (updated) {
1145
1408
  val = newValues;
1146
1409
  sta = newStates;
1410
+ initValue = val;
1147
1411
  this.#value.set(val);
1148
1412
  this.#state.set(newStates);
1149
1413
  }
1150
1414
  }
1151
- if (this.#lastValue === val && this.#lastState === sta) {
1152
- return [val, sta];
1415
+ if (!this.#set) {
1416
+ this.#set = true;
1417
+ this.#initValue.set(initValue);
1153
1418
  }
1154
- this.#lastValue = val;
1155
- this.#lastState = sta;
1156
1419
  return [val, sta];
1157
-
1158
1420
  }
1159
-
1160
-
1161
-
1162
- get destroyed() { return this.#destroyed; }
1163
- destroy() {
1164
- if (this.#destroyed) { return; }
1165
- this.#destroyed = true;
1166
- for (const [, field] of this) {
1167
- field.destroy();
1421
+ /**
1422
+ *
1423
+ * @overload
1424
+ * @param {null} [path]
1425
+ * @returns {Promise<string[] | null>}
1426
+ */
1427
+ /**
1428
+ * @overload
1429
+ * @param {(string | number)[]} path
1430
+ * @returns {Promise<{ path: (string | number)[]; store: Store; errors: string[]}[]>}
1431
+ */
1432
+ /**
1433
+ *
1434
+ * @param {(string | number)[]?} [path]
1435
+ * @returns {Promise<string[] | { path: (string | number)[]; store: Store; errors: string[] | null;}[] | null>}
1436
+ */
1437
+ validate(path) {
1438
+ if (!Array.isArray(path)) {
1439
+ return Promise.all([this.#validatorResult.get(), this.#changed(), this.#blurred()])
1440
+ .then(v => {
1441
+ const errors = v.flat();
1442
+ return errors.length ? errors : null
1443
+ });
1168
1444
  }
1445
+ const list = [this.validate().then(errors => {
1446
+ if (!errors?.length) {return [];}
1447
+ return [{path: [...path], store: /** @type {Store} */(this), errors}]
1448
+ })];
1449
+ for (const [key, field] of this) {
1450
+ list.push(field.validate([...path, key]));
1451
+ }
1452
+ return Promise.all(list).then(v => v.flat())
1169
1453
  }
1170
-
1171
1454
  }
1172
1455
 
1456
+ /** @import { Schema } from '../types.mjs' */
1173
1457
 
1174
-
1458
+ /**
1459
+ * @template {Record<string, any>} [T=Record<string, any>]
1460
+ * @extends {Store<T>}
1461
+ */
1175
1462
  class ObjectStore extends Store {
1176
1463
  get kind() { return 'object'; }
1177
1464
  /** @type {Record<string, Store>} */
@@ -1187,24 +1474,18 @@
1187
1474
  * @param {Schema.Object & Schema.Attr} schema
1188
1475
  * @param {object} [options]
1189
1476
  * @param {Store?} [options.parent]
1190
- * @param {string | number} [options.index]
1477
+ * @param {number | string | null} [options.index]
1191
1478
  * @param {boolean} [options.new]
1192
- * @param {(value: any, index: any) => void} [options.onUpdate]
1193
- * @param {(value: any, index: any) => void} [options.onUpdateState]
1479
+ * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdate]
1480
+ * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdateState]
1194
1481
  */
1195
1482
  constructor(schema,{ parent, index, new: isNew, onUpdate, onUpdateState } = {}) {
1196
1483
  const childrenTypes = Object.entries(schema.type);
1197
1484
  super(schema, {
1198
1485
  parent, index, new: isNew, onUpdate, onUpdateState,
1199
1486
  length: childrenTypes.length,
1200
- setValue(v) {
1201
- if (typeof v !== 'object') { return {}; }
1202
- return v;
1203
- },
1204
- setState(v) {
1205
- if (typeof v !== 'object') { return {}; }
1206
- return v;
1207
- },
1487
+ setValue(v) { return typeof v === 'object' ? v : null; },
1488
+ setState(v) { return typeof v === 'object' ? v : null; },
1208
1489
  convert(v, state) {
1209
1490
  return [
1210
1491
  typeof v === 'object' ? v : {},
@@ -1215,34 +1496,31 @@
1215
1496
  const children = Object.create(null);
1216
1497
  const childCommonOptions = {
1217
1498
  parent: this,
1218
- /** @param {*} value @param {*} index */
1219
- onUpdate: (value, index) => {
1499
+ /** @param {*} value @param {*} index @param {Store} store */
1500
+ onUpdate: (value, index, store) => {
1501
+ if (store !== this.#children[index]) { return; }
1502
+ // @ts-ignore
1220
1503
  this.value = {...this.value, [index]: value};
1221
1504
  },
1222
- /** @param {*} state @param {*} index */
1223
- onUpdateState: (state, index) => {
1505
+ /** @param {*} state @param {*} index @param {Store} store */
1506
+ onUpdateState: (state, index, store) => {
1507
+ if (store !== this.#children[index]) { return; }
1224
1508
  this.state = {...this.state, [index]: state};
1225
1509
  }
1226
1510
  };
1227
1511
 
1228
1512
  for (const [index, field] of childrenTypes) {
1229
- let child;
1230
- if (typeof field.type === 'string') {
1231
- if (field.array) {
1232
- child = new ArrayStore(field, {...childCommonOptions, index});
1233
- } else {
1234
- child = new Store(field, {...childCommonOptions, index});
1235
- }
1236
- } else if (field.array) {
1237
- child = new ArrayStore(field, {...childCommonOptions, index});
1238
- } else {
1239
- child = new ObjectStore(/**@type {*}*/(field), { ...childCommonOptions, index});
1240
- }
1241
- children[index] = child;
1513
+ children[index] = create(field, {...childCommonOptions, index});
1242
1514
  }
1243
1515
  this.#children = children;
1244
1516
  }
1245
1517
  }
1518
+ // @ts-ignore
1519
+ setObjectStore(ObjectStore);
1520
+
1521
+ /** @import { Schema } from '../types.mjs' */
1522
+
1523
+
1246
1524
 
1247
1525
  /**
1248
1526
  * @template [T=any]
@@ -1274,23 +1552,19 @@
1274
1552
  * @param {Store?} [options.parent]
1275
1553
  * @param {string | number | null} [options.index]
1276
1554
  * @param {boolean} [options.new]
1277
- * @param {(value: any, index: any) => void} [options.onUpdate]
1278
- * @param {(value: any, index: any) => void} [options.onUpdateState]
1555
+ * @param {(value: any, index: any, store: Store) => void} [options.onUpdate]
1556
+ * @param {(value: any, index: any, store: Store) => void} [options.onUpdateState]
1279
1557
  */
1280
- constructor(schema, { parent, onUpdate, onUpdateState, index, new: isNew} = {}) {
1558
+ constructor(schema, { parent, onUpdate, onUpdateState, index, new: isNew} = {}) {
1281
1559
  const childrenState = new exports.Signal.State(/** @type {Store[]} */([]));
1282
1560
  // @ts-ignore
1283
1561
  const updateChildren = (list) => {
1284
- if (this.destroyed) { return; }
1285
1562
  const length = Array.isArray(list) && list.length || 0;
1286
1563
  const children = [...childrenState.get()];
1287
1564
  const oldLength = children.length;
1288
1565
  for (let i = children.length; i < length; i++) {
1289
1566
  children.push(this.#create(i));
1290
1567
  }
1291
- for (const schema of children.splice(length)) {
1292
- schema.destroy();
1293
- }
1294
1568
  if (oldLength !== length) {
1295
1569
  childrenState.set(children);
1296
1570
  }
@@ -1300,27 +1574,28 @@
1300
1574
  index, new: isNew, parent,
1301
1575
  length: new exports.Signal.Computed(() => childrenState.get().length),
1302
1576
  state: [],
1303
- setValue(v) { return Array.isArray(v) ? v : v == null ? [] : [v] },
1304
- setState(v) { return Array.isArray(v) ? v : v == null ? [] : [v] },
1577
+ setValue(v) { return Array.isArray(v) ? v : v == null ? null : [v] },
1578
+ setState(v) { return Array.isArray(v) ? v : v == null ? null : [v] },
1305
1579
  convert(v, state) {
1306
- const val = Array.isArray(v) ? v : v == null ? [] : [v];
1580
+ const val = Array.isArray(v) ? v : v == null ? null : [v];
1307
1581
  updateChildren(val);
1308
1582
  return [
1309
1583
  val,
1310
1584
  (Array.isArray(state) ? state : v == null ? [] : [state]),
1311
1585
  ];
1312
1586
  },
1313
- onUpdate:(value, index) => {
1587
+ onUpdate:(value, index, state) => {
1314
1588
  updateChildren(value);
1315
- onUpdate?.(value, index);
1589
+ onUpdate?.(value, index, state);
1316
1590
  },
1317
1591
  onUpdateState,
1318
1592
  });
1319
1593
  this.#children = childrenState;
1320
1594
  const childCommonOptions = {
1321
1595
  parent: this,
1322
- /** @param {*} value @param {*} index */
1323
- onUpdate: (value, index) => {
1596
+ /** @param {*} value @param {*} index @param {Store} store */
1597
+ onUpdate: (value, index, store) => {
1598
+ if (childrenState.get()[index] !== store) { return;}
1324
1599
  const val = [...this.value || []];
1325
1600
  if (val.length < index) {
1326
1601
  val.length = index;
@@ -1328,8 +1603,9 @@
1328
1603
  val[index] = value;
1329
1604
  this.value = val;
1330
1605
  },
1331
- /** @param {*} state @param {*} index */
1332
- onUpdateState: (state, index) => {
1606
+ /** @param {*} state @param {*} index @param {Store} store */
1607
+ onUpdateState: (state, index, store) => {
1608
+ if (childrenState.get()[index] !== store) { return;}
1333
1609
  const sta = [...this.state || []];
1334
1610
  if (sta.length < index) {
1335
1611
  sta.length = index;
@@ -1338,33 +1614,21 @@
1338
1614
  this.state = sta;
1339
1615
  },
1340
1616
  };
1341
- if (typeof schema.type === 'string') {
1342
- this.#create = (index, isNew) => {
1343
- const child = new Store(schema, {...childCommonOptions, index, new: isNew });
1344
- child.index = index;
1345
- return child
1346
- };
1347
- } else if (schema.type && typeof schema.type === 'object' && !Array.isArray(schema.type)) {
1348
- this.#create = (index, isNew) => {
1349
- const child = new ObjectStore(/** @type {*} */(schema), { ...childCommonOptions, index, new: isNew});
1350
- child.index = index;
1351
- return child
1352
- };
1353
- } else {
1354
- throw new Error();
1355
- }
1356
-
1617
+ this.#create = (index, isNew) => {
1618
+ const child = create(schema, {...childCommonOptions, index, new: isNew });
1619
+ child.index = index;
1620
+ return child
1621
+ };
1357
1622
  }
1358
1623
  /**
1359
1624
  *
1360
1625
  * @param {number} index
1361
- * @param {T} value
1626
+ * @param {T?} [value]
1362
1627
  * @param {boolean} [isNew]
1363
1628
  * @returns
1364
1629
  */
1365
- insert(index, value, isNew) {
1366
- if (this.destroyed) { return false; }
1367
- const data = this.value;
1630
+ insert(index, value = null, isNew) {
1631
+ const data = this.value || [];
1368
1632
  if (!Array.isArray(data)) { return false; }
1369
1633
  const children = [...this.#children.get()];
1370
1634
  const insertIndex = Math.max(0, Math.min(Math.floor(index), children.length));
@@ -1382,16 +1646,16 @@
1382
1646
  sta.splice(insertIndex, 0, {});
1383
1647
  this.state = sta;
1384
1648
  }
1385
- this.value = val;
1386
1649
  this.#children.set(children);
1650
+ this.value = val;
1387
1651
  return true;
1388
1652
  }
1389
1653
  /**
1390
1654
  *
1391
- * @param {T} value
1655
+ * @param {T?} [value]
1392
1656
  * @returns
1393
1657
  */
1394
- add(value) {
1658
+ add(value = null) {
1395
1659
  return this.insert(this.#children.get().length, value);
1396
1660
  }
1397
1661
  /**
@@ -1400,7 +1664,6 @@
1400
1664
  * @returns
1401
1665
  */
1402
1666
  remove(index) {
1403
- if (this.destroyed) { return; }
1404
1667
  const data = this.value;
1405
1668
  if (!Array.isArray(data)) { return; }
1406
1669
  const children = [...this.#children.get()];
@@ -1410,7 +1673,6 @@
1410
1673
  for (let i = index; i < children.length; i++) {
1411
1674
  children[i].index = i;
1412
1675
  }
1413
- item.destroy();
1414
1676
  const val = [...data];
1415
1677
  const [value] = val.splice(removeIndex, 1);
1416
1678
  const state = this.state;
@@ -1419,8 +1681,8 @@
1419
1681
  sta.splice(removeIndex, 1);
1420
1682
  this.state = sta;
1421
1683
  }
1422
- this.value = val;
1423
1684
  this.#children.set(children);
1685
+ this.value = val;
1424
1686
  return value;
1425
1687
 
1426
1688
  }
@@ -1431,7 +1693,6 @@
1431
1693
  * @returns
1432
1694
  */
1433
1695
  move(from, to) {
1434
- if (this.destroyed) { return false; }
1435
1696
  const data = this.value;
1436
1697
  if (!Array.isArray(data)) { return false; }
1437
1698
  const children = [...this.#children.get()];
@@ -1457,8 +1718,8 @@
1457
1718
  }
1458
1719
  this.state = sta;
1459
1720
  }
1460
- this.value = val;
1461
1721
  this.#children.set(children);
1722
+ this.value = val;
1462
1723
  return true;
1463
1724
 
1464
1725
  }
@@ -1469,7 +1730,6 @@
1469
1730
  * @returns
1470
1731
  */
1471
1732
  exchange(a, b) {
1472
- if (this.destroyed) { return false; }
1473
1733
  const data = this.value;
1474
1734
  if (!Array.isArray(data)) { return false; }
1475
1735
  const children = [...this.#children.get()];
@@ -1494,11 +1754,13 @@
1494
1754
  sta[a] = bValue;
1495
1755
  this.state = sta;
1496
1756
  }
1497
- this.value = val;
1498
1757
  this.#children.set(children);
1758
+ this.value = val;
1499
1759
  return true;
1500
1760
  }
1501
1761
  }
1762
+ // @ts-ignore
1763
+ setArrayStore(ArrayStore);
1502
1764
 
1503
1765
  const errors = {
1504
1766
  CALC: 'no `createCalc` option, no expression parsing support',
@@ -2410,6 +2672,7 @@
2410
2672
  store: true,
2411
2673
  parent: true,
2412
2674
  root: true,
2675
+ ref: true,
2413
2676
 
2414
2677
  schema: true,
2415
2678
 
@@ -2417,6 +2680,7 @@
2417
2680
  readonly: true,
2418
2681
  creatable: true,
2419
2682
  immutable: true,
2683
+ changed: true,
2420
2684
 
2421
2685
  required: true,
2422
2686
  clearable: true,
@@ -2429,12 +2693,18 @@
2429
2693
  min: true,
2430
2694
  max: true,
2431
2695
  step: true,
2696
+ minLength: true,
2697
+ maxLength: true,
2698
+ pattern: true,
2432
2699
  values: true,
2433
2700
 
2434
2701
  null: true,
2435
2702
  index: true,
2436
2703
  no: true,
2437
2704
  length: true,
2705
+
2706
+ error: true,
2707
+ errors: true,
2438
2708
  };
2439
2709
  /** @type {Set<keyof typeof bindable>} */
2440
2710
  // @ts-ignore
@@ -2455,6 +2725,9 @@
2455
2725
  }
2456
2726
  yield [`${key}${sign}value`, {get: () => val.value, set: v => val.value = v}];
2457
2727
  yield [`${key}${sign}state`, {get: () => val.state, set: v => val.state = v}];
2728
+ yield [`${key}${sign}reset`, {exec: () => val.reset()}];
2729
+ // @ts-ignore
2730
+ yield [`${key}${sign}validate`, {exec: v => val.validate(v ? [] : null)}];
2458
2731
  if (!(val instanceof ArrayStore)) { return; }
2459
2732
  yield [`${key}${sign}insert`, {exec: (index, value) => val.insert(index, value)}];
2460
2733
  yield [`${key}${sign}add`, {exec: (v) => val.add(v)}];
@@ -2527,6 +2800,17 @@
2527
2800
  /** @import { ValueDefine, ExecDefine, CalcDefine } from './index.mjs' */
2528
2801
 
2529
2802
 
2803
+ /** @type {Record<string, ValueDefine | ExecDefine | CalcDefine>} */
2804
+ const items = {
2805
+ value$: { calc(r) { return r?.[ref].value; } },
2806
+ state$: { calc(r) { return r?.[ref].state; } },
2807
+ };
2808
+
2809
+ var globals = Object.getOwnPropertyDescriptors(items);
2810
+
2811
+ /** @import { ValueDefine, ExecDefine, CalcDefine } from './index.mjs' */
2812
+
2813
+
2530
2814
  /**
2531
2815
  *
2532
2816
  * @param {string} key
@@ -2544,7 +2828,7 @@
2544
2828
  */
2545
2829
  function toGlobal(global) {
2546
2830
  /** @type {Record<string, ValueDefine | ExecDefine | CalcDefine>} */
2547
- const items = Object.create(null);
2831
+ const items = Object.create(null, globals);
2548
2832
  if (!global || typeof global !== 'object') { return items; }
2549
2833
  for (const [key, value] of Object.entries(global)) {
2550
2834
  if (!testKey(key)) { continue; }
@@ -2714,6 +2998,9 @@
2714
2998
  '$click': v => {store.emit('click', v); },
2715
2999
  '$focus': v => {store.emit('focus', v); },
2716
3000
  '$blur': v => {store.emit('blur', v); },
3001
+ '$reset': v => {store.reset(); },
3002
+ // @ts-ignore
3003
+ '$validate': v => {store.validate(v ? [] : null); },
2717
3004
  }
2718
3005
  }
2719
3006
 
@@ -3324,6 +3611,7 @@
3324
3611
  }
3325
3612
 
3326
3613
  /** @import { Component } from '../types.mjs' */
3614
+ /** @import Store from '../Store/index.mjs' */
3327
3615
  /** @import Environment from './Environment/index.mjs' */
3328
3616
 
3329
3617
 
@@ -3448,9 +3736,10 @@
3448
3736
  *
3449
3737
  * @param {Component | string} component
3450
3738
  * @param {Environment} env
3739
+ * @param {((store: Store, el: Element) => () => void)?} [relate]
3451
3740
  * @returns
3452
3741
  */
3453
- function createContext(component, env) {
3742
+ function createContext(component, env, relate) {
3454
3743
  const tag = typeof component === 'string' ? component : component.tag;
3455
3744
  const { attrs, events } = typeof component !== 'string' && component || { attrs: null, events: null };
3456
3745
 
@@ -3462,7 +3751,8 @@
3462
3751
  const attrStates = Object.create(null);
3463
3752
  const tagAttrs = Object.create(null);
3464
3753
 
3465
- const attrWatchers = new Set();
3754
+ /** @type {Set<() => void>} */
3755
+ const cancelFns = new Set();
3466
3756
 
3467
3757
  /** @type {[string, ($event: any) => void, AddEventListenerOptions][]} */
3468
3758
  const allEvents = [];
@@ -3482,13 +3772,28 @@
3482
3772
  old = v;
3483
3773
  fn(v, o, name);
3484
3774
  }, true);
3485
- attrWatchers.add(w);
3775
+ cancelFns.add(w);
3486
3776
 
3487
3777
  return () => {
3488
- attrWatchers.delete(w);
3778
+ cancelFns.delete(w);
3489
3779
  w();
3490
3780
  };
3491
3781
  },
3782
+ relate(el) {
3783
+ if (!relate || destroyed) { return () => { }; }
3784
+ try {
3785
+ const w = relate(env.store, el);
3786
+ if (typeof w !== 'function') { return () => { }; }
3787
+ cancelFns.add(w);
3788
+ return () => {
3789
+ cancelFns.delete(w);
3790
+ w();
3791
+ };
3792
+ } catch {
3793
+ return () => { };
3794
+ }
3795
+
3796
+ },
3492
3797
  get destroyed() { return destroyedState.get(); },
3493
3798
  get init() { return mountedState.get(); },
3494
3799
  listen(name, listener) { return stateEmitter.listen(name, listener); },
@@ -3557,7 +3862,7 @@
3557
3862
  if (destroyed) { return; }
3558
3863
  destroyed = true;
3559
3864
  destroyedState.set(true);
3560
- for (const w of attrWatchers) {
3865
+ for (const w of cancelFns) {
3561
3866
  w();
3562
3867
  }
3563
3868
  stateEmitter.emit('destroy');
@@ -4207,9 +4512,10 @@
4207
4512
  * @param {Environment} env
4208
4513
  * @param {Record<string, [Layout.Node, Environment]>} templates
4209
4514
  * @param {string[]} componentPath
4515
+ * @param {((store: Store, el: Element) => () => void)?} [relate]
4210
4516
  * @param {Component.Getter?} [getComponent]
4211
4517
  */
4212
- function renderItem(layout, parent, next, env, templates, componentPath, getComponent) {
4518
+ function renderItem(layout, parent, next, env, templates, componentPath, relate, getComponent) {
4213
4519
  env = env.set(layout.aliases, layout.vars);
4214
4520
  const bind = layout.directives.bind;
4215
4521
  const fragment = layout.directives.fragment;
@@ -4218,18 +4524,18 @@
4218
4524
  if (!template) { return () => {}; }
4219
4525
  const [templateLayout, templateEnv] = template;
4220
4526
  const newEnv = templateEnv.params(templateLayout, layout, env, bind);
4221
- return render(templateLayout, parent, next, newEnv, templates, componentPath, getComponent);
4527
+ return render(templateLayout, parent, next, newEnv, templates, componentPath, relate, getComponent);
4222
4528
  }
4223
4529
  if (!layout.name || layout.directives.fragment) {
4224
4530
  return renderFillDirectives(parent, next, env, layout.directives) ||
4225
4531
  renderList(layout.children || [], parent, next, env, templates, (layout, templates) => {
4226
- return render(layout, parent, next, env, templates, componentPath, getComponent);
4532
+ return render(layout, parent, next, env, templates, componentPath, relate, getComponent);
4227
4533
  });
4228
4534
  }
4229
4535
  const path = [...componentPath, layout.name];
4230
4536
  const component = getComponent?.(path);
4231
4537
  if (getComponent && !component) { return () => { }; }
4232
- const { context, handler } = createContext(component ? component : layout.name, env);
4538
+ const { context, handler } = createContext(component ? component : layout.name, env, relate);
4233
4539
 
4234
4540
 
4235
4541
  const componentAttrs = component?.attrs;
@@ -4256,7 +4562,7 @@
4256
4562
  const children = slot ?
4257
4563
  renderFillDirectives(slot, null, env, layout.directives)
4258
4564
  || renderList(layout.children || [], slot, null, env, templates, (layout, templates) => {
4259
- return render(layout, slot, null, env, templates, componentPath, getComponent);
4565
+ return render(layout, slot, null, env, templates, componentPath, relate, getComponent);
4260
4566
  }) : () => {};
4261
4567
 
4262
4568
 
@@ -4281,16 +4587,17 @@
4281
4587
  * @param {Environment} env
4282
4588
  * @param {Record<string, [Layout.Node, Environment]>} templates
4283
4589
  * @param {string[]} componentPath
4590
+ * @param {((store: Store, el: Element) => () => void)?} [relate]
4284
4591
  * @param {Component.Getter?} [getComponent]
4285
4592
  * @returns {() => void}
4286
4593
  */
4287
- function render(layout, parent, next, env, templates, componentPath, getComponent) {
4594
+ function render(layout, parent, next, env, templates, componentPath, relate, getComponent) {
4288
4595
  const { directives } = layout;
4289
4596
  const newEnv = env.child(directives.value);
4290
4597
  if (!newEnv) { return () => {}; }
4291
4598
  const list = newEnv.enum(directives.enum);
4292
4599
  /** @type {(next: Node | null, env: any) => () => void} */
4293
- const r = (next, env) => renderItem(layout, parent, next, env, templates, componentPath, getComponent);
4600
+ const r = (next, env) => renderItem(layout, parent, next, env, templates, componentPath, relate, getComponent);
4294
4601
  if (list === true) {
4295
4602
  return r(next, newEnv);
4296
4603
  }
@@ -4307,37 +4614,21 @@
4307
4614
  }
4308
4615
 
4309
4616
  /**
4310
- * @overload
4311
4617
  * @param {Store} store
4312
4618
  * @param {(Layout.Node | string)[]} layouts
4313
4619
  * @param {Element} parent
4314
- * @param {Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>} [global]
4315
- * @param {(path: string[]) => Component?} [components]
4620
+ * @param {object} [options]
4621
+ * @param {Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>} [options.global]
4622
+ * @param {(path: string[]) => Component?} [options.component]
4623
+ * @param {(store: Store, el: Element) => () => void} [options.relate]
4316
4624
  * @returns {() => void}
4317
4625
  */
4318
- /**
4319
- * @overload
4320
- * @param {Store} store
4321
- * @param {(Layout.Node | string)[]} layouts
4322
- * @param {Element} parent
4323
- * @param {Component.Getter?} [components]
4324
- * @returns {() => void}
4325
- */
4326
- /**
4327
- * @param {Store} store
4328
- * @param {(Layout.Node | string)[]} layouts
4329
- * @param {Element} parent
4330
- * @param {Component.Getter | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt1]
4331
- * @param {Component.Getter | Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }> | null} [opt2]
4332
- */
4333
- function index (store, layouts, parent, opt1, opt2) {
4334
- const options = [opt1, opt2];
4335
- const components = options.find(v => typeof v === 'function');
4336
- const global = options.find(v => typeof v === 'object');
4626
+ function index (store, layouts, parent, {component, global, relate} = {}) {
4337
4627
  const env = new Environment(store, global);
4338
4628
  const templates = Object.create(null);
4629
+ const relateFn = typeof relate === 'function' ? relate : null;
4339
4630
  return renderList(layouts, parent, null, env, templates, (layout, templates) => {
4340
- return render(layout, parent, null, env, templates, [], components);
4631
+ return render(layout, parent, null, env, templates, [], relateFn, component);
4341
4632
  });
4342
4633
  }
4343
4634