@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/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.7.0
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, root: Store) => boolean?) | null} [fn]
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, self.root)));
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, root: Store) => T?) | null} [fn]
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, root: Store) => T?} */(fn);
98
- scriptState = new Signal.Computed(() => toValue(f(self, self.root)));
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, this.root)));
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 val = this.#setValue?.(v) || v;
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 sta = this.#setState?.(v) || v;
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 {*} value
757
+ * @param {*} v
598
758
  * @returns
599
759
  */
600
- #reset(value) {
601
- this.#value.set(value);
602
- this.#initValue.set(value);
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
- return;
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(value[key]);
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
- const attrWatchers = new Set();
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
- attrWatchers.add(w);
3197
+ cancelFns.add(w);
2982
3198
 
2983
3199
  return () => {
2984
- attrWatchers.delete(w);
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 attrWatchers) {
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 {Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>} [global]
3811
- * @param {(path: string[]) => Component?} [components]
3812
- * @returns {() => void}
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, [], components);
4053
+ return render(layout, parent, null, env, templates, [], relateFn, component);
3837
4054
  });
3838
4055
  }
3839
4056
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neeloong/form",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "from",