@neeloong/form 0.27.0 → 0.29.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.27.0
2
+ * @neeloong/form v0.29.0
3
3
  * (c) 2024-2026 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -303,6 +303,12 @@ function makeDefault(store, def) {
303
303
  * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
304
304
  */
305
305
  class Store {
306
+ /** @type {Store<T, M, S>?} */
307
+ #originStore = null;
308
+
309
+ /** @type {Schema.Field<M, S>} 字段的 Schema 定义 */
310
+ #schema;
311
+ get schema() { return this.#schema; }
306
312
  /** @type {Map<string, Set<(value: any, store: any) => void | boolean | null>>} */
307
313
  #events = new Map();
308
314
  /**
@@ -310,8 +316,11 @@ class Store {
310
316
  * @template {keyof Schema.Events} K
311
317
  * @param {K} event
312
318
  * @param {Schema.Events[K]} value
319
+ * @returns {boolean}
313
320
  */
314
321
  emit(event, value) {
322
+ const originStore = this.#originStore;
323
+ if (originStore) { return originStore.emit(event, value); }
315
324
  const key = typeof event === 'number' ? String(event) : event;
316
325
  const events = this.#events;
317
326
  let canceled = false;
@@ -328,6 +337,8 @@ class Store {
328
337
  * @returns {() => void}
329
338
  */
330
339
  listen(event, listener) {
340
+ const originStore = this.#originStore;
341
+ if (originStore) { return originStore.listen(event, p => listener.call(this, p, this)); }
331
342
  const fn = listener.bind(this);
332
343
  const events = this.#events;
333
344
  const key = typeof event === 'number' ? String(event) : event;
@@ -368,51 +379,93 @@ class Store {
368
379
  #ref = null;
369
380
  get ref() { return this.#ref || createRef(this); }
370
381
  /**
371
- * @param {Schema.Field<M, S>} schema 字段的 Schema 定义
372
- * @param {object} [options] 可选配置
373
- * @param {Store?} [options.parent]
374
- * @param {Partial<S>?} [options.states]
375
- * @param {((store: Store, value?: any) => any) | object | number | string | boolean | null | undefined} [options.default]
376
- * @param {number | string | null} [options.index]
377
- * @param {number | Signal.State<number> | Signal.Computed<number>} [options.size]
378
- * @param {boolean} [options.null]
379
- * @param {boolean} [options.new]
380
- * @param {boolean} [options.hidden]
381
- * @param {boolean} [options.clearable]
382
- * @param {boolean} [options.required]
383
- * @param {boolean} [options.disabled]
384
- * @param {boolean} [options.readonly]
385
- * @param {boolean} [options.removable]
386
- *
387
- * @param {string} [options.label] 字段标签
388
- * @param {string} [options.description] 字段描述
389
- * @param {string} [options.placeholder] 占位符
390
- * @param {number} [options.min] 日期、时间、数字的最小值
391
- * @param {number} [options.max] 日期、时间、数字的最大值
392
- * @param {number} [options.step] 日期、时间、数字的步长
393
- * @param {number} [options.minLength]
394
- * @param {number} [options.maxLength]
395
- * @param {RegExp} [options.pattern]
396
- * @param {(Schema.Value.Group | Schema.Value | string | number)[]} [options.values] 可选值
397
- * @param {Schema.Validator | Schema.Validator[] | null} [options.validator]
398
- * @param {{[k in keyof Schema.Events]?: Schema.AsyncValidator | Schema.AsyncValidator[] | null}} [options.validators]
399
- *
400
- * @param {Ref?} [options.ref]
401
- *
402
- * @param {((value: any) => any)?} [options.setValue]
403
- * @param {((value: any) => any)?} [options.convert]
404
- *
405
- * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdate]
382
+ * @param {Schema.Field<M, S> | Store<T,M,S>} schema 字段的 Schema 定义
383
+ * @param {StoreOptions | AbortSignal | null} [options] 可选配置
406
384
  */
407
- constructor(schema, {
408
- null: isNull, ref, default: defaultValue,
409
- setValue, convert, onUpdate, states,
410
- validator, validators,
411
- index, size, new: isNew, parent: parentNode,
412
- hidden, clearable, required, disabled, readonly, removable,
413
- label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
414
- } = {}) {
415
- this.schema = schema;
385
+ constructor(schema, options) {
386
+ if (schema instanceof Store) {
387
+ const store = schema.#originStore || schema;
388
+ this.#originStore= store;
389
+ this.#schema= store.#schema;
390
+ this.#null= store.#null;
391
+ this.#ref= store.#ref;
392
+ this.#states= store.#states;
393
+ this.#layout= store.#layout;
394
+ this.#createDefault= store.#createDefault;
395
+ this.#setValue= store.#setValue;
396
+ this.#convert= store.#convert;
397
+ this.#onUpdate= store.#onUpdate;
398
+ this.#parent= store.#parent;
399
+ this.#root= store.#root;
400
+ this.#type= store.#type;
401
+ this.#meta= store.#meta;
402
+ this.#component= store.#component;
403
+ this.#selfLoading= store.#selfLoading;
404
+ this.#loading= store.#loading;
405
+ this.#size= store.#size;
406
+ this.#index= store.#index;
407
+ this.#creatable= store.#creatable;
408
+ this.#immutable= store.#immutable;
409
+ this.#new= store.#new;
410
+ this.#selfNew= store.#selfNew;
411
+ this.#selfHidden= store.#selfHidden;
412
+ this.#hidden= store.#hidden;
413
+ this.#selfClearable= store.#selfClearable;
414
+ this.#clearable= store.#clearable;
415
+ this.#selfRequired= store.#selfRequired;
416
+ this.#required= store.#required;
417
+ this.#selfDisabled= store.#selfDisabled;
418
+ this.#disabled= store.#disabled;
419
+ this.#selfReadonly= store.#selfReadonly;
420
+ this.#readonly= store.#readonly;
421
+ this.#selfRemovable= store.#selfRemovable;
422
+ this.#removable= store.#removable;
423
+ this.#selfLabel= store.#selfLabel;
424
+ this.#label= store.#label;
425
+ this.#selfDescription= store.#selfDescription;
426
+ this.#description= store.#description;
427
+ this.#selfPlaceholder= store.#selfPlaceholder;
428
+ this.#placeholder= store.#placeholder;
429
+ this.#selfMin= store.#selfMin;
430
+ this.#min= store.#min;
431
+ this.#selfMax= store.#selfMax;
432
+ this.#max= store.#max;
433
+ this.#selfStep= store.#selfStep;
434
+ this.#step= store.#step;
435
+ this.#selfMinLength= store.#selfMinLength;
436
+ this.#minLength= store.#minLength;
437
+ this.#selfMaxLength= store.#selfMaxLength;
438
+ this.#maxLength= store.#maxLength;
439
+ this.#selfPattern= store.#selfPattern;
440
+ this.#pattern= store.#pattern;
441
+ this.#selfValues= store.#selfValues;
442
+ this.#values= store.#values;
443
+ this.#errors= store.#errors;
444
+ this.#validatorResult= store.#validatorResult;
445
+ this.#changed= store.#changed;
446
+ this.#blurred= store.#blurred;
447
+ this.#cancelChange= store.#cancelChange;
448
+ this.#cancelBlur= store.#cancelBlur;
449
+ this.#set= store.#set;
450
+ this.#initValue= store.#initValue;
451
+ this.#value= store.#value;
452
+ const signal = options instanceof AbortSignal ? options : null;
453
+ if (signal?.aborted) { return; }
454
+ const subBindStores= store.#subBindStores;
455
+ subBindStores.add(this);
456
+ signal?.addEventListener('abort', () => subBindStores.delete(this));
457
+ store.#requestUpdate();
458
+ return;
459
+ }
460
+ const {
461
+ null: isNull, ref, default: defaultValue,
462
+ setValue, convert, onUpdate, states,
463
+ validator, validators,
464
+ index, size, new: isNew, parent: parentNode,
465
+ hidden, clearable, required, disabled, readonly, removable,
466
+ label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
467
+ } = !(options instanceof AbortSignal) && options || {};
468
+ this.#schema = schema;
416
469
  const parent = parentNode instanceof Store ? parentNode : null;
417
470
  if (parent) {
418
471
  this.#parent = parent;
@@ -540,6 +593,7 @@ class Store {
540
593
  /** @type {StoreLayout.Field<any>} */
541
594
  #layout;
542
595
  get layout() { return this.#layout; }
596
+ /** @type {(value?: any) => unknown} */
543
597
  #createDefault;
544
598
  /** @param {any} [value] @returns {any} */
545
599
  createDefault(value) { return this.#createDefault(value); }
@@ -821,40 +875,8 @@ class Store {
821
875
  #initValue = new Signal.State(/** @type {T?} */(null));
822
876
  #value = new Signal.State(this.#initValue.get());
823
877
 
824
- /**
825
- * @template [M=any]
826
- * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
827
- * @param {Schema<M, S>} schema 数据结构模式
828
- */
829
- bindObject(schema) {
830
- const bindStores = this.#bindStores;
831
- /** @type {Store[]} */
832
- const list = [];
833
- for (const [index, field] of Object.entries(schema)) {
834
- const bindStore = create(field, {
835
- index, parent: this,
836
- /** @param {*} value @param {*} currentIndex @param {Store} store */
837
- onUpdate: (value, currentIndex, store) => {
838
- if (index !== currentIndex) { return; }
839
- if (bindStores.has(store)) { return; }
840
- const val = this.#value ?? null;
841
- if (typeof val !== 'object' || Array.isArray(val)) { return }
842
- // @ts-ignore
843
- this.value = { ...val, [currentIndex]: value };
844
- },
845
- });
846
- list.push(bindStore);
847
- bindStores.set(bindStore, index);
848
- }
849
- this.#requestUpdate();
850
- return () => {
851
- for (const bindStore of list) {
852
- bindStores.delete(bindStore);
853
- }
854
- };
855
- }
856
- /** @type {Map<Store, string>} */
857
- #bindStores = new Map();
878
+ /** @type {Set<Store>} */
879
+ #subBindStores = new Set();
858
880
 
859
881
  /** 内容是否已改变 */
860
882
  get changed() { return !Object.is(this.#value.get(), this.#initValue.get()); }
@@ -862,6 +884,8 @@ class Store {
862
884
  /** 字段当前值 */
863
885
  get value() { return this.#value.get(); }
864
886
  set value(v) {
887
+ const originStore = this.#originStore;
888
+ if (originStore) { originStore.value = v; return; }
865
889
  const newValue = this.#setValue?.(v);
866
890
  const val = newValue === undefined ? v : newValue;
867
891
  this.#value.set(val);
@@ -882,6 +906,8 @@ class Store {
882
906
  }
883
907
  /** 重置数据 */
884
908
  reset(value = this.#set ? this.#initValue.get() : this.#createDefault(), isNew = this.#selfNew.get()) {
909
+ const originStore = this.#originStore;
910
+ if (originStore) { originStore.reset(value, isNew); return; }
885
911
  this.#reset(value, Boolean(isNew));
886
912
  }
887
913
  /**
@@ -898,11 +924,10 @@ class Store {
898
924
  this.#cancelBlur();
899
925
  this.#set = true;
900
926
  if (!value || typeof value !== 'object') {
901
- for (const [, field] of this) {
902
- field.#reset(null, false);
903
- }
904
- for (const [field] of this.#bindStores) {
905
- field.#reset(null, false);
927
+ for (const bind of [this, ...this.#subBindStores]) {
928
+ for (const [, field] of bind) {
929
+ field.#reset(null, false);
930
+ }
906
931
  }
907
932
  this.#value.set(value);
908
933
  this.#initValue.set(value);
@@ -911,11 +936,10 @@ class Store {
911
936
  }
912
937
  /** @type {*} */
913
938
  const newValues = Array.isArray(value) ? [...value] : { ...value };
914
- for (const [key, field] of this) {
915
- newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
916
- }
917
- for (const [field, key] of this.#bindStores) {
918
- newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
939
+ for (const bind of [this, ...this.#subBindStores]) {
940
+ for (const [key, field] of bind) {
941
+ newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
942
+ }
919
943
  }
920
944
  this.#value.set(newValues);
921
945
  this.#initValue.set(newValues);
@@ -949,23 +973,16 @@ class Store {
949
973
  // @ts-ignore
950
974
  let newValues = Array.isArray(val) ? [...val] : { ...val };
951
975
  let updated = false;
952
- for (const [key, field] of this) {
953
- // @ts-ignore
954
- const data = Object.hasOwn(val, key) ? val[key] : undefined;
955
- const newData = field.#toUpdate(data);
956
- if (Object.is(data, newData)) { continue; }
957
- // @ts-ignore
958
- newValues[key] = newData;
959
- updated = true;
960
- }
961
- for (const [field, key] of this.#bindStores) {
962
- // @ts-ignore
963
- const data = Object.hasOwn(val, key) ? val[key] : undefined;
964
- const newData = field.#toUpdate(data);
965
- if (Object.is(data, newData)) { continue; }
966
- // @ts-ignore
967
- newValues[key] = newData;
968
- updated = true;
976
+ for (const bind of [this, ...this.#subBindStores]) {
977
+ for (const [key, field] of bind) {
978
+ // @ts-ignore
979
+ const data = Object.hasOwn(val, key) ? val[key] : undefined;
980
+ const newData = field.#toUpdate(data);
981
+ if (Object.is(data, newData)) { continue; }
982
+ // @ts-ignore
983
+ newValues[key] = newData;
984
+ updated = true;
985
+ }
969
986
  }
970
987
  if (updated) {
971
988
  val = newValues;
@@ -999,6 +1016,7 @@ class Store {
999
1016
  */
1000
1017
  validate(path) {
1001
1018
  if (path === true) {
1019
+ if (this.#originStore) { return Promise.resolve(null); }
1002
1020
  return Promise.all([this.#validatorResult.get(), this.#changed(), this.#blurred()])
1003
1021
  .then(v => {
1004
1022
  const errors = v.flat();
@@ -1006,7 +1024,7 @@ class Store {
1006
1024
  });
1007
1025
  }
1008
1026
  const selfPath = Array.isArray(path) ? path : [];
1009
- if (this.#hidden.get()) { return Promise.resolve([]); }
1027
+ if (!this.#originStore && this.#hidden.get()) { return Promise.resolve([]); }
1010
1028
  const list = [this.validate(true).then(errors => {
1011
1029
  if (!errors?.length) { return []; }
1012
1030
  return [{ path: [...selfPath], store: /** @type {Store} */(this), errors }];
@@ -1014,13 +1032,53 @@ class Store {
1014
1032
  for (const [key, field] of this) {
1015
1033
  list.push(field.validate([...selfPath, key]));
1016
1034
  }
1017
- for (const [field, key] of this.#bindStores) {
1018
- list.push(field.validate([...selfPath, key]));
1035
+ for (const sub of this.#subBindStores) {
1036
+ list.push(sub.validate(selfPath));
1019
1037
  }
1020
1038
  return Promise.all(list).then(v => v.flat());
1021
1039
  }
1022
1040
  }
1023
1041
 
1042
+ /**
1043
+ * @template [T=any]
1044
+ * @template [M=any]
1045
+ * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
1046
+ * @typedef {object} StoreOptions
1047
+ * @property {Store?} [parent]
1048
+ * @property {Partial<S>?} [states]
1049
+ * @property {((store: Store, value?: any) => any) | object | number | string | boolean | null | undefined} [default]
1050
+ * @property {number | string | null} [index]
1051
+ * @property {number | Signal.State<number> | Signal.Computed<number>} [size]
1052
+ * @property {boolean} [null]
1053
+ * @property {boolean} [new]
1054
+ * @property {boolean} [hidden]
1055
+ * @property {boolean} [clearable]
1056
+ * @property {boolean} [required]
1057
+ * @property {boolean} [disabled]
1058
+ * @property {boolean} [readonly]
1059
+ * @property {boolean} [removable]
1060
+ *
1061
+ * @property {string} [label] 字段标签
1062
+ * @property {string} [description] 字段描述
1063
+ * @property {string} [placeholder] 占位符
1064
+ * @property {number} [min] 日期、时间、数字的最小值
1065
+ * @property {number} [max] 日期、时间、数字的最大值
1066
+ * @property {number} [step] 日期、时间、数字的步长
1067
+ * @property {number} [minLength]
1068
+ * @property {number} [maxLength]
1069
+ * @property {RegExp} [pattern]
1070
+ * @property {(Schema.Value.Group | Schema.Value | string | number)[]} [values] 可选值
1071
+ * @property {Schema.Validator | Schema.Validator[] | null} [validator]
1072
+ * @property {{[k in keyof Schema.Events]?: Schema.AsyncValidator | Schema.AsyncValidator[] | null}} [validators]
1073
+ *
1074
+ * @property {Ref?} [ref]
1075
+ *
1076
+ * @property {((value: any) => any)?} [setValue]
1077
+ * @property {((value: any) => any)?} [convert]
1078
+ *
1079
+ * @property {((value: T?, index: any, store: Store) => void)?} [onUpdate]
1080
+ */
1081
+
1024
1082
  /** @import { Schema } from '../Schema.types.mjs' */
1025
1083
 
1026
1084
  /**
@@ -1326,6 +1384,51 @@ class ArrayStore extends Store {
1326
1384
  // @ts-ignore
1327
1385
  setArrayStore(ArrayStore);
1328
1386
 
1387
+ /** @import { Schema } from '../Schema.types.mjs' */
1388
+
1389
+ /**
1390
+ * @template [T=any]
1391
+ * @template [M=any]
1392
+ * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
1393
+ * @extends {Store<T, M, S>}
1394
+ */
1395
+ class BindObjectStore extends Store {
1396
+
1397
+ get kind() { return 'object'; }
1398
+ /** @type {Record<string, Store>} */
1399
+ #children = Object.create(null);
1400
+ *[Symbol.iterator]() { yield* Object.entries(this.#children); }
1401
+ /**
1402
+ *
1403
+ * @param {string | number} key
1404
+ * @returns {Store?}
1405
+ */
1406
+ child(key) { return this.#children[key] || null; }
1407
+ /**
1408
+ * @param {Schema<any, Object.<string, Schema.State>>} schema 数据结构模式
1409
+ * @param {Store<T, M, S>} store
1410
+ * @param {AbortSignal} [signal]
1411
+ */
1412
+ constructor(schema, store, signal) {
1413
+ super(store, signal);
1414
+ const children = this.#children;
1415
+ for (const [index, field] of Object.entries(schema)) {
1416
+ const bindStore = create(field, {
1417
+ index, parent: this,
1418
+ /** @param {*} value @param {*} index @param {Store} store */
1419
+ onUpdate: (value, index, store) => {
1420
+ if (store !== children[index]) { return; }
1421
+ const val = this.value ?? null;
1422
+ if (typeof val !== 'object' || Array.isArray(val)) { return; }
1423
+ // @ts-ignore
1424
+ this.value = { ...val, [index]: value };
1425
+ },
1426
+ });
1427
+ children[index] = bindStore;
1428
+ }
1429
+ }
1430
+ }
1431
+
1329
1432
  /** @import * as Layout from './index.mjs' */
1330
1433
 
1331
1434
  /** @import { OldNode } from './createElement.mjs' */
@@ -6486,4 +6589,4 @@ function renderStore(store, fieldRenderer, root, layout, options) {
6486
6589
  }, { once: true });
6487
6590
  }
6488
6591
 
6489
- export { ArrayStore, index as Layout, ObjectStore, Store, effect, render, renderStore, watch };
6592
+ export { ArrayStore, BindObjectStore, index as Layout, ObjectStore, Store, effect, render, renderStore, watch };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neeloong/form",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "一个基于响应式数据绑定的表单渲染库",
5
5
  "keywords": [
6
6
  "from",