@neeloong/form 0.24.0 → 0.26.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.24.0
2
+ * @neeloong/form v0.26.0
3
3
  * (c) 2024-2026 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -145,7 +145,8 @@ let ArrayStoreClass = null;
145
145
  let TypeStores = Object.create(null);
146
146
  /**
147
147
  * @template [M=any]
148
- * @param {Schema.Field<M>} schema
148
+ * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
149
+ * @param {Schema.Field<M, S>} schema
149
150
  * @param {object} [options]
150
151
  * @param {Store?} [options.parent]
151
152
  * @param {string | number | null} [options.index]
@@ -299,6 +300,7 @@ function makeDefault(store, def) {
299
300
  * 管理单个表单字段的状态和行为
300
301
  * @template [T=any]
301
302
  * @template [M=any]
303
+ * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
302
304
  */
303
305
  class Store {
304
306
  /** @type {Map<string, Set<(value: any, store: any) => void | boolean | null>>} */
@@ -341,7 +343,8 @@ class Store {
341
343
  /**
342
344
  * 从数据结构模式创建存储
343
345
  * @template [M=any]
344
- * @param {Schema<M>} schema 数据结构模式
346
+ * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
347
+ * @param {Schema<M, S>} schema 数据结构模式
345
348
  * @param {object} [options] 选项
346
349
  * @param {boolean} [options.new] 是否为新建环境
347
350
  */
@@ -365,9 +368,10 @@ class Store {
365
368
  #ref = null;
366
369
  get ref() { return this.#ref || createRef(this); }
367
370
  /**
368
- * @param {Schema.Field<M>} schema 字段的 Schema 定义
371
+ * @param {Schema.Field<M, S>} schema 字段的 Schema 定义
369
372
  * @param {object} [options] 可选配置
370
373
  * @param {Store?} [options.parent]
374
+ * @param {Partial<S>?} [options.states]
371
375
  * @param {((store: Store, value?: any) => any) | object | number | string | boolean | null | undefined} [options.default]
372
376
  * @param {number | string | null} [options.index]
373
377
  * @param {number | Signal.State<number> | Signal.Computed<number>} [options.size]
@@ -402,7 +406,7 @@ class Store {
402
406
  */
403
407
  constructor(schema, {
404
408
  null: isNull, ref, default: defaultValue,
405
- setValue, convert, onUpdate,
409
+ setValue, convert, onUpdate, states,
406
410
  validator, validators,
407
411
  index, size, new: isNew, parent: parentNode,
408
412
  hidden, clearable, required, disabled, readonly, removable,
@@ -480,6 +484,22 @@ class Store {
480
484
 
481
485
  const validatorResult = createValidator(this, schema.validator, validator);
482
486
 
487
+
488
+ const schemaStates = schema.states;
489
+ this.#states = schemaStates ? Object.defineProperties(Object.create(null),
490
+ Object.fromEntries(
491
+ Object.entries(schemaStates).map(([k, { get, set, toState }]) => {
492
+ const state = new Signal.State(toState?.(states?.[k]));
493
+ const computed = new Signal.Computed(() => get(this, state));
494
+ return [k, {
495
+ configurable: true,
496
+ enumerable: true,
497
+ get() { return computed.get(); },
498
+ set(v) { return set?.(this, state, v); },
499
+ }];
500
+ })
501
+ )) : null;
502
+
483
503
  const [changed, changedResult, cancelChange] = createAsyncValidator(this, schema.validators?.change, validators?.change);
484
504
  const [blurred, blurredResult, cancelBlur] = createAsyncValidator(this, schema.validators?.blur, validators?.blur);
485
505
  this.listen('change', () => { changed(); });
@@ -514,8 +534,11 @@ class Store {
514
534
  this.listen(k, f);
515
535
  }
516
536
  }
537
+ /** @type {S?} */
538
+ #states;
539
+ get states() { return this.#states; }
517
540
  /** @type {StoreLayout.Field<any>} */
518
- #layout
541
+ #layout;
519
542
  get layout() { return this.#layout; }
520
543
  #createDefault;
521
544
  /** @param {any} [value] @returns {any} */
@@ -691,9 +714,10 @@ class Store {
691
714
  #selfMin;
692
715
  /** @readonly @type {Signal.Computed<number?>} */
693
716
  #min;
717
+ /** @deprecated */
694
718
  get selfMin() { return this.#selfMin.get(); }
695
719
  set selfMin(v) { this.#selfMin.set(number(v)); }
696
- /** 数值字段的最小值限制 */
720
+ /** @deprecated 数值字段的最小值限制 */
697
721
  get min() { return this.#min.get(); }
698
722
  set min(v) { this.#selfMin.set(number(v)); }
699
723
 
@@ -702,9 +726,10 @@ class Store {
702
726
  #selfMax;
703
727
  /** @readonly @type {Signal.Computed<number?>} */
704
728
  #max;
729
+ /** @deprecated */
705
730
  get selfMax() { return this.#selfMax.get(); }
706
731
  set selfMax(v) { this.#selfMax.set(number(v)); }
707
- /** 数值字段的最大值限制 */
732
+ /** @deprecated 数值字段的最大值限制 */
708
733
  get max() { return this.#max.get(); }
709
734
  set max(v) { this.#selfMax.set(number(v)); }
710
735
 
@@ -713,9 +738,10 @@ class Store {
713
738
  #selfStep;
714
739
  /** @readonly @type {Signal.Computed<number?>} */
715
740
  #step;
741
+ /** @deprecated */
716
742
  get selfStep() { return this.#selfStep.get(); }
717
743
  set selfStep(v) { this.#selfStep.set(number(v)); }
718
- /** 数值字段的步长 */
744
+ /** @deprecated 数值字段的步长 */
719
745
  get step() { return this.#step.get(); }
720
746
  set step(v) { this.#selfStep.set(number(v)); }
721
747
 
@@ -723,9 +749,10 @@ class Store {
723
749
  #selfMinLength;
724
750
  /** @readonly @type {Signal.Computed<number?>} */
725
751
  #minLength;
752
+ /** @deprecated */
726
753
  get selfMinLength() { return this.#selfMinLength.get(); }
727
754
  set selfMinLength(v) { this.#selfMinLength.set(number(v)); }
728
- /** 最小长度 */
755
+ /** @deprecated 最小长度 */
729
756
  get minLength() { return this.#minLength.get(); }
730
757
  set minLength(v) { this.#selfMinLength.set(number(v)); }
731
758
 
@@ -733,9 +760,10 @@ class Store {
733
760
  #selfMaxLength;
734
761
  /** @readonly @type {Signal.Computed<number?>} */
735
762
  #maxLength;
763
+ /** @deprecated */
736
764
  get selfMaxLength() { return this.#selfMaxLength.get(); }
737
765
  set selfMaxLength(v) { this.#selfMaxLength.set(number(v)); }
738
- /** 最大长度 */
766
+ /** @deprecated 最大长度 */
739
767
  get maxLength() { return this.#maxLength.get(); }
740
768
  set maxLength(v) { this.#selfMaxLength.set(number(v)); }
741
769
 
@@ -743,9 +771,10 @@ class Store {
743
771
  #selfPattern;
744
772
  /** @readonly @type {Signal.Computed<RegExp?>} */
745
773
  #pattern;
774
+ /** @deprecated */
746
775
  get selfPattern() { return this.#selfPattern.get(); }
747
776
  set selfPattern(v) { this.#selfPattern.set(regex(v)); }
748
- /** 模式 */
777
+ /** @deprecated 模式 */
749
778
  get pattern() { return this.#pattern.get(); }
750
779
  set pattern(v) { this.#selfPattern.set(regex(v)); }
751
780
 
@@ -754,9 +783,10 @@ class Store {
754
783
  #selfValues;
755
784
  /** @readonly @type {Signal.Computed<(Schema.Value.Group | Schema.Value)[] | null>} */
756
785
  #values;
786
+ /** @deprecated */
757
787
  get selfValues() { return this.#selfValues.get(); }
758
788
  set selfValues(v) { this.#selfValues.set(values(v)); }
759
- /** 可选值列表 */
789
+ /** @deprecated 可选值列表 */
760
790
  get values() { return this.#values.get(); }
761
791
  set values(v) { this.#selfValues.set(values(v)); }
762
792
 
@@ -791,6 +821,40 @@ class Store {
791
821
  #initValue = new Signal.State(/** @type {T?} */(null));
792
822
  #value = new Signal.State(this.#initValue.get());
793
823
 
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();
794
858
 
795
859
  /** 内容是否已改变 */
796
860
  get changed() { return !Object.is(this.#value.get(), this.#initValue.get()); }
@@ -837,6 +901,9 @@ class Store {
837
901
  for (const [, field] of this) {
838
902
  field.#reset(null, false);
839
903
  }
904
+ for (const [field] of this.#bindStores) {
905
+ field.#reset(null, false);
906
+ }
840
907
  this.#value.set(value);
841
908
  this.#initValue.set(value);
842
909
  this.#onUpdate?.(value, this.#index.get(), this);
@@ -847,6 +914,9 @@ class Store {
847
914
  for (const [key, field] of this) {
848
915
  newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
849
916
  }
917
+ for (const [field, key] of this.#bindStores) {
918
+ newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
919
+ }
850
920
  this.#value.set(newValues);
851
921
  this.#initValue.set(newValues);
852
922
  this.#onUpdate?.(newValues, this.#index.get(), this);
@@ -888,6 +958,15 @@ class Store {
888
958
  newValues[key] = newData;
889
959
  updated = true;
890
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;
969
+ }
891
970
  if (updated) {
892
971
  val = newValues;
893
972
  initValue = val;
@@ -904,7 +983,7 @@ class Store {
904
983
  /**
905
984
  * 异步校验
906
985
  * @overload
907
- * @param {true} [path]
986
+ * @param {true} [self]
908
987
  * @returns {Promise<string[] | null>}
909
988
  */
910
989
  /**
@@ -927,6 +1006,7 @@ class Store {
927
1006
  });
928
1007
  }
929
1008
  const selfPath = Array.isArray(path) ? path : [];
1009
+ if (this.#hidden.get()) { return Promise.resolve([]); }
930
1010
  const list = [this.validate(true).then(errors => {
931
1011
  if (!errors?.length) { return []; }
932
1012
  return [{ path: [...selfPath], store: /** @type {Store} */(this), errors }];
@@ -934,6 +1014,9 @@ class Store {
934
1014
  for (const [key, field] of this) {
935
1015
  list.push(field.validate([...selfPath, key]));
936
1016
  }
1017
+ for (const [field, key] of this.#bindStores) {
1018
+ list.push(field.validate([...selfPath, key]));
1019
+ }
937
1020
  return Promise.all(list).then(v => v.flat());
938
1021
  }
939
1022
  }
@@ -943,7 +1026,8 @@ class Store {
943
1026
  /**
944
1027
  * @template {Record<string, any>} [T=Record<string, any>]
945
1028
  * @template [M=any]
946
- * @extends {Store<T, M>}
1029
+ * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
1030
+ * @extends {Store<T, M, S>}
947
1031
  */
948
1032
  class ObjectStore extends Store {
949
1033
  get kind() { return 'object'; }
@@ -957,7 +1041,7 @@ class ObjectStore extends Store {
957
1041
  */
958
1042
  child(key) { return this.#children[key] || null; }
959
1043
  /**
960
- * @param {Schema.Object<M> & Schema.Attr<M>} schema
1044
+ * @param {Schema.Object<M, S> & Schema.Attr<M, S>} schema
961
1045
  * @param {object} [options]
962
1046
  * @param {Store?} [options.parent]
963
1047
  * @param {number | string | null} [options.index]
@@ -1009,7 +1093,8 @@ setObjectStore(ObjectStore);
1009
1093
  /**
1010
1094
  * @template [T=any]
1011
1095
  * @template [M=any]
1012
- * @extends {Store<(T | null)[], M>}
1096
+ * @template {Object.<string, Schema.State>} [S=Object.<string, Schema.State>]
1097
+ * @extends {Store<(T | null)[], M, S>}
1013
1098
  */
1014
1099
  class ArrayStore extends Store {
1015
1100
  /** @type {(index: number, isNew?: boolean) => Store} */
@@ -1032,7 +1117,7 @@ class ArrayStore extends Store {
1032
1117
  }
1033
1118
  get kind() { return 'array'; }
1034
1119
  /**
1035
- * @param {Schema.Field<M>} schema
1120
+ * @param {Schema.Field<M, S>} schema
1036
1121
  * @param {object} [options]
1037
1122
  * @param {Store?} [options.parent]
1038
1123
  * @param {string | number | null} [options.index]
@@ -1101,6 +1186,8 @@ class ArrayStore extends Store {
1101
1186
  return child;
1102
1187
  };
1103
1188
  }
1189
+ /** @returns {never} */
1190
+ bindObject() { throw new Error(`ArrayStore 不支持 bindObject()方法`); }
1104
1191
 
1105
1192
 
1106
1193
  /** @readonly @type {Signal.State<boolean?>} */
@@ -4218,7 +4305,7 @@ function renderObject(parent, next, store, env, renderItem, sort) {
4218
4305
  const children = [];
4219
4306
  const childStores = [...store];
4220
4307
  const count = childStores.length;
4221
- /** @type {[string, Store<any, any>, number][]} */
4308
+ /** @type {[string, Store<any, any, any>, number][]} */
4222
4309
  const stores = sort
4223
4310
  ? childStores
4224
4311
  .map(([k,v]) => [k,v,env.setStore(v, store).exec(sort)])
@@ -4765,7 +4852,7 @@ function createFieldFilter(field) {
4765
4852
  * @param {StoreLayout.Options?} options
4766
4853
  * @param {StoreLayout<T>} layout
4767
4854
  * @param {Node} [anchor]
4768
- * @param {(child?: Store<any, any> | undefined) => void} [dragenter]
4855
+ * @param {(child?: Store<any, any, any> | undefined) => void} [dragenter]
4769
4856
  * @returns {void}
4770
4857
  */
4771
4858
  function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragenter) {
@@ -4778,27 +4865,49 @@ function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragent
4778
4865
  const mode = node.getAttribute('mode') || '';
4779
4866
  const fieldStore = field ? store.child(field) : store;
4780
4867
  if (!fieldStore) { return; }
4781
- const fieldLayout = field
4782
- ? layout?.fields?.find(createFieldFilter(field)) || fieldStore.layout
4783
- : { ...layout, html: '' };
4868
+ /** @type {HTMLElement?} */
4869
+ let el = null;
4784
4870
  switch (mode) {
4785
4871
  case 'grid': {
4786
- const el = Form(store, fieldRenderer, fieldLayout, options);
4787
- if (el) { node.replaceWith(el); }
4788
- return;
4872
+ const fieldLayout = field
4873
+ ? layout?.fields?.find(createFieldFilter(field)) || fieldStore.layout
4874
+ : { ...layout, html: '' };
4875
+ el = Form(fieldStore, fieldRenderer, fieldLayout, options);
4876
+ break;
4877
+ }
4878
+ default: {
4879
+ el = fieldRenderer(fieldStore, layout.renderer, options);
4880
+ break;
4789
4881
  }
4790
4882
  }
4791
- const res = fieldRenderer(fieldStore, layout.renderer, options);
4792
- if (res) {
4793
- node.replaceWith(res);
4883
+ if (!el) {
4884
+ const value = node.getAttribute('placeholder') || '';
4885
+ node.replaceWith(document.createTextNode(value));
4794
4886
  return;
4795
4887
  }
4796
- const value = node.getAttribute('placeholder') || '';
4797
- node.replaceWith(document.createTextNode(value));
4888
+ const className = node.getAttribute('class') || '';
4889
+ if (className) {
4890
+ el.setAttribute('class', [
4891
+ el.getAttribute('class') || '',
4892
+ className,
4893
+ ].filter(Boolean).join(' '));
4894
+ }
4895
+ const style = node.getAttribute('style') || '';
4896
+ if (style) {
4897
+ el.setAttribute('style', [
4898
+ el.getAttribute('style') || '',
4899
+ style,
4900
+ ].filter(Boolean).join(' '));
4901
+ }
4902
+ node.replaceWith(el);
4798
4903
  return;
4799
4904
  }
4800
4905
  if (tagName === 'nl-form-button') {
4801
4906
  const button = document.createElement('button');
4907
+ const className = node.getAttribute('class') || '';
4908
+ const style = node.getAttribute('style') || '';
4909
+ if (className) { button.setAttribute('class', className); }
4910
+ if (style) { button.setAttribute('style', style); }
4802
4911
  button.classList.add('NeeloongForm-item-button');
4803
4912
  const click = node.getAttribute('click') || '';
4804
4913
  const call = options?.call;
@@ -4986,7 +5095,7 @@ function getHtmlContent(html) {
4986
5095
  /**
4987
5096
  *
4988
5097
  * @template T
4989
- * @param {Store<any, any>} store
5098
+ * @param {Store<any, any, any>} store
4990
5099
  * @param {StoreLayout.Renderer<T>} fieldRenderer
4991
5100
  * @param {StoreLayout<T>} layout
4992
5101
  * @param {StoreLayout.Options?} options
@@ -5009,7 +5118,7 @@ function FormFieldInline(store, fieldRenderer, layout, options) {
5009
5118
  /**
5010
5119
  *
5011
5120
  * @template T
5012
- * @param {Store<any, any>} store
5121
+ * @param {Store<any, any, any>} store
5013
5122
  * @param {StoreLayout.Renderer<T>} fieldRenderer
5014
5123
  * @param {StoreLayout.Field<T>} layout
5015
5124
  * @param {object} option
@@ -5048,6 +5157,7 @@ function Line(store, fieldRenderer, layout, {
5048
5157
  const body = root.appendChild(document.createElement('tr'));
5049
5158
  const main = body.appendChild(document.createElement('td'));
5050
5159
  main.colSpan = columns.length;
5160
+ main.appendChild(form);
5051
5161
  body.hidden = true;
5052
5162
  trigger = () => {
5053
5163
  if (body.hidden) {
@@ -5087,12 +5197,12 @@ function Line(store, fieldRenderer, layout, {
5087
5197
  }
5088
5198
 
5089
5199
  for (const column of columns) {
5090
- const { actions, field, pattern } = column;
5200
+ const { actions, field, pattern, editable } = column;
5091
5201
  if (!actions?.length) {
5092
5202
  const td = head.appendChild(document.createElement('td'));
5093
5203
  const child = field && store.child(field);
5094
5204
  if (child) {
5095
- const el = FormFieldInline(child, fieldRenderer, column, options);
5205
+ const el = FormFieldInline(child, fieldRenderer, column, {...options, editable: options?.editable && (editable !== false)});
5096
5206
  if (el) { td.appendChild(el); }
5097
5207
  }
5098
5208
  continue;
@@ -5140,6 +5250,64 @@ function Line(store, fieldRenderer, layout, {
5140
5250
  return root;
5141
5251
  }
5142
5252
 
5253
+ /** @import { ArrayStore } from '../Store/index.mjs' */
5254
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
5255
+
5256
+ /**
5257
+ *
5258
+ * @template T
5259
+ * @param {ArrayStore} store
5260
+ * @param {StoreLayout.Field<T>} layout
5261
+ * @param {StoreLayout.Action[]} actionOptions
5262
+ * @param {(fields: { field: string; width: any; label: any; editable?: boolean? }[]) => StoreLayout.Column<T>[]} createDefault
5263
+ * @returns {StoreLayout.Column<T>[]}
5264
+ */
5265
+ function getColumns(store, layout, actionOptions, createDefault) {
5266
+ const fieldList = Object.entries(store.type || {})
5267
+ .filter(([k, v]) => typeof v?.type !== 'object')
5268
+ .map(([field, { width, label }]) => ({ field, width, label }));
5269
+ const headerColumns = layout.columns;
5270
+ if (Array.isArray(headerColumns)) {
5271
+ const map = new Map(fieldList.map(v => [v.field, v]));
5272
+ /** @type {(StoreLayout.Column<T> | null)[]} */
5273
+ const allColumns = headerColumns.map(v => {
5274
+ if (!v) { return null; }
5275
+ if (typeof v === 'number') { return { placeholder: v }; }
5276
+ if (typeof v === 'string') { return map.get(v) || null; }
5277
+ if (typeof v !== 'object') { return null; }
5278
+ if (Array.isArray(v)) {
5279
+ /** @type {Set<StoreLayout.Action>} */
5280
+ const options = new Set(actionOptions);
5281
+ const actions = v.filter(v => options.delete(v));
5282
+ if (!actions) { return null; }
5283
+ return { actions };
5284
+ }
5285
+ const { action, actions, field, placeholder, pattern, width, label, editable } = v;
5286
+ if (field) {
5287
+ const define = map.get(field);
5288
+ if (define) {
5289
+ return { field, placeholder, width, label: label || define.label, editable };
5290
+ }
5291
+ }
5292
+ const options = new Set(actionOptions);
5293
+ const allActions = [action, actions].flat().filter(v => v && options.delete(v));
5294
+ if (allActions.length) {
5295
+ return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label, editable };
5296
+ }
5297
+ if (pattern) {
5298
+ return { pattern, placeholder, width, label, editable };
5299
+ }
5300
+ if (placeholder || width) {
5301
+ return { placeholder, width, label, editable };
5302
+ }
5303
+ return null;
5304
+ });
5305
+ const columns = /** @type {StoreLayout.Column<T>[]} */(allColumns.filter(Boolean));
5306
+ if (columns.length) { return columns; }
5307
+ }
5308
+ return createDefault(fieldList);
5309
+ }
5310
+
5143
5311
  /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5144
5312
  /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
5145
5313
 
@@ -5170,6 +5338,7 @@ function renderHead(signal, parent, columns, add, addable, editable) {
5170
5338
  watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true, signal);
5171
5339
  }
5172
5340
  }
5341
+
5173
5342
  /**
5174
5343
  *
5175
5344
  * @template T
@@ -5181,54 +5350,15 @@ function renderHead(signal, parent, columns, add, addable, editable) {
5181
5350
  */
5182
5351
  function Table(store, fieldRenderer, layout, options) {
5183
5352
  if (options?.signal?.aborted) { return null; }
5184
- const headerColumns = layout.columns;
5185
- const fieldList = Object.entries(store.type || {})
5186
- .filter(([k, v]) => typeof v?.type !== 'object')
5187
- .map(([field, { width, label }]) => ({ field, width, label }));
5188
- /** @type {StoreLayout.Column<T>[]} */
5189
- let columns = [];
5190
- if (Array.isArray(headerColumns)) {
5191
- const map = new Map(fieldList.map(v => [v.field, v]));
5192
-
5193
- /** @type {(StoreLayout.Column<T> | null)[]} */
5194
- const allColumns = headerColumns.map(v => {
5195
- if (!v) { return null; }
5196
- if (typeof v === 'number') { return { placeholder: v }; }
5197
- if (typeof v === 'string') { return map.get(v) || null; }
5198
- if (typeof v !== 'object') { return null; }
5199
- if (Array.isArray(v)) {
5200
- /** @type {Set<StoreLayout.Action>} */
5201
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
5202
- const actions = v.filter(v => options.delete(v));
5203
- if (!actions) { return null; }
5204
- return { actions };
5205
- }
5206
- const { action, actions, field, placeholder, pattern, width, label } = v;
5207
- if (field) {
5208
- const define = map.get(field);
5209
- if (define) {
5210
- return { field, placeholder, width, label: label || define.label };
5211
- }
5212
- }
5213
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial']);
5214
- const allActions = [action, actions].flat().filter(v => v && options.delete(v));
5215
- if (allActions.length) {
5216
- return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label };
5217
- }
5218
- // if (pattern) {
5219
- // return { pattern, placeholder, width, label };
5220
- // }
5221
- return null;
5222
- });
5223
- columns = /** @type {StoreLayout.Column<T>[]} */(allColumns.filter(Boolean));
5224
-
5225
- }
5226
- if (!columns.length) {
5227
- columns = [
5353
+ const columns = getColumns(
5354
+ store,
5355
+ layout,
5356
+ ['add', 'move', 'trigger', 'remove', 'serial'],
5357
+ fields => [
5228
5358
  { actions: ['add', 'trigger', 'move', 'remove', 'serial'] },
5229
- ...fieldList.slice(0, 3),
5230
- ];
5231
- }
5359
+ ...fields.slice(0, 3),
5360
+ ],
5361
+ );
5232
5362
 
5233
5363
  const table = document.createElement('table');
5234
5364
  table.classList.add('NeeloongForm-table');
@@ -5473,8 +5603,8 @@ function createCell(signal, layout, values, defCell, blockOnly) {
5473
5603
  /**
5474
5604
  *
5475
5605
  * @template T
5476
- * @param {Store<any, any>} store
5477
- * @param {Signal.State<Store<any, any>?>} currentStore
5606
+ * @param {Store<any, any, any>} store
5607
+ * @param {Signal.State<Store<any, any, any>?>} currentStore
5478
5608
  * @param {StoreLayout.Renderer<T>} fieldRenderer
5479
5609
  * @param {StoreLayout.Field<T>} layout
5480
5610
  * @param {Signal.State<State>} state
@@ -5488,7 +5618,7 @@ function createCell(signal, layout, values, defCell, blockOnly) {
5488
5618
  * @param {() => void} option.dragend
5489
5619
  * @param {{get(): boolean}} option.deletable
5490
5620
  * @param {() => void} option.addNode
5491
- * @param {(store: Store<any, any>) => () => void} option.createDetails
5621
+ * @param {(store: Store<any, any, any>) => () => void} option.createDetails
5492
5622
  * @param {StoreLayout.Options?} options
5493
5623
  * @returns {HTMLElement}
5494
5624
  */
@@ -5805,56 +5935,16 @@ function createState(store, states, drag, levelKey, index) {
5805
5935
  */
5806
5936
  function Tree(store, fieldRenderer, layout, options) {
5807
5937
  if (options?.signal?.aborted) { return null; }
5808
- const headerColumns = layout.columns;
5809
- const fieldList = Object.entries(store.type || {})
5810
- .filter(([k, v]) => typeof v?.type !== 'object')
5811
- .map(([field, { width, label }]) => ({ field, width, label }));
5812
- /** @type {StoreLayout.Column<T>[]} */
5813
- let columns = [];
5814
- if (Array.isArray(headerColumns)) {
5815
- const map = new Map(fieldList.map(v => [v.field, v]));
5816
- /** @type {(StoreLayout.Column<T> | null)[]} */
5817
- const allColumns = headerColumns.map(v => {
5818
- if (!v) { return null; }
5819
- if (typeof v === 'number') { return { placeholder: v }; }
5820
- if (typeof v === 'string') { return map.get(v) || null; }
5821
- if (typeof v !== 'object') { return null; }
5822
- if (Array.isArray(v)) {
5823
- /** @type {Set<StoreLayout.Action>} */
5824
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
5825
- const actions = v.filter(v => options.delete(v));
5826
- if (!actions) { return null; }
5827
- return { actions };
5828
- }
5829
- const { action, actions, field, placeholder, pattern, width, label } = v;
5830
- if (field) {
5831
- const define = map.get(field);
5832
- if (define) {
5833
- return { field, placeholder, width, label: label || define.label };
5834
- }
5835
- }
5836
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
5837
- const allActions = [action, actions].flat().filter(v => v && options.delete(v));
5838
- if (allActions.length) {
5839
- return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label };
5840
- }
5841
- if (pattern) {
5842
- return { pattern, placeholder, width, label };
5843
- }
5844
- if (placeholder || width) {
5845
- return { placeholder, width, label };
5846
- }
5847
- return null;
5848
- });
5849
- columns = /** @type {StoreLayout.Column<T>[]} */(allColumns.filter(Boolean));
5850
- }
5851
- if (!columns.length) {
5852
- columns = [
5938
+ const columns = getColumns(
5939
+ store,
5940
+ layout,
5941
+ ['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse'],
5942
+ fields => [
5853
5943
  { actions: ['collapse', 'move'] },
5854
- fieldList[0],
5944
+ fields[0],
5855
5945
  { actions: ['add', 'remove'] },
5856
- ];
5857
- }
5946
+ ]
5947
+ );
5858
5948
 
5859
5949
 
5860
5950
  const root = document.createElement('div');
@@ -5930,10 +6020,10 @@ function Tree(store, fieldRenderer, layout, options) {
5930
6020
 
5931
6021
  /** @type {AbortController?} */
5932
6022
  let detailAbortController = null;
5933
- const detailsStore = new Signal.State(/** @type{Store<any, any>?}*/(null));
6023
+ const detailsStore = new Signal.State(/** @type{Store<any, any, any>?}*/(null));
5934
6024
  /**
5935
6025
  *
5936
- * @param {Store<any, any>} store
6026
+ * @param {Store<any, any, any>} store
5937
6027
  * @returns
5938
6028
  */
5939
6029
  function createDetails(store) {
@@ -6185,7 +6275,7 @@ function Tree(store, fieldRenderer, layout, options) {
6185
6275
  *
6186
6276
  * @template T
6187
6277
  * @param {string | ParentNode} html
6188
- * @param {Store<any, any>} store
6278
+ * @param {Store<any, any, any>} store
6189
6279
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6190
6280
  * @param {StoreLayout.Options?} options
6191
6281
  * @param {StoreLayout.Field<T>} layout
@@ -6217,7 +6307,7 @@ function renderArrayCell(arrayStyle, store, fieldRenderer, layout, options) {
6217
6307
  /**
6218
6308
  *
6219
6309
  * @template T
6220
- * @param {Store<any, any>} store
6310
+ * @param {Store<any, any, any>} store
6221
6311
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6222
6312
  * @param {StoreLayout.Field<T>} layout
6223
6313
  * @param {StoreLayout.Options?} options
@@ -6251,7 +6341,7 @@ function FormField(store, fieldRenderer, layout, options) {
6251
6341
 
6252
6342
  /**
6253
6343
  *
6254
- * @param {Store<any, any>} store
6344
+ * @param {Store<any, any, any>} store
6255
6345
  * @param {StoreLayout.Button} layout
6256
6346
  * @param {StoreLayout.Options?} options
6257
6347
  * @returns {ParentNode}
@@ -6289,7 +6379,7 @@ function FormButton(store, layout, options) {
6289
6379
  /**
6290
6380
  *
6291
6381
  * @template T
6292
- * @param {Store<any, any>} store
6382
+ * @param {Store<any, any, any>} store
6293
6383
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6294
6384
  * @param {StoreLayout.Html} layout
6295
6385
  * @param {StoreLayout.Options?} options
@@ -6311,7 +6401,7 @@ function FormHtml(store, fieldRenderer, layout, options) {
6311
6401
  /**
6312
6402
  *
6313
6403
  * @template T
6314
- * @param {Store<any, any>} store
6404
+ * @param {Store<any, any, any>} store
6315
6405
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6316
6406
  * @param {StoreLayout.Item<T>} item
6317
6407
  * @param {StoreLayout.Options?} options
@@ -6336,7 +6426,7 @@ function FormItem(store, fieldRenderer, item, options) {
6336
6426
  /**
6337
6427
  *
6338
6428
  * @template T
6339
- * @param {Store<any, any>} store
6429
+ * @param {Store<any, any, any>} store
6340
6430
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6341
6431
  * @param {StoreLayout<T>} layout
6342
6432
  * @param {StoreLayout.Options?} options