@neeloong/form 0.20.0 → 0.21.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,6 +1,6 @@
1
1
  /*!
2
- * @neeloong/form v0.20.0
3
- * (c) 2024-2025 Fierflame
2
+ * @neeloong/form v0.21.0
3
+ * (c) 2024-2026 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
6
6
 
@@ -151,7 +151,6 @@ let TypeStores = Object.create(null);
151
151
  * @param {string | number | null} [options.index]
152
152
  * @param {boolean} [options.new]
153
153
  * @param {(value: any, index: any, store: Store) => void} [options.onUpdate]
154
- * @param {(value: any, index: any, store: Store) => void} [options.onUpdateState]
155
154
  */
156
155
  function create(schema, options) {
157
156
  const type = schema.type;
@@ -282,13 +281,14 @@ function merge(...v) {
282
281
  *
283
282
  * @template {Store} T
284
283
  * @param {T} store
285
- * @param {((store: T) => any) | any} def
284
+ * @param {((store: T, value?: any) => any) | any} def
285
+ * @returns {(value?: any) => unknown}
286
286
  */
287
287
  function makeDefault(store, def) {
288
288
  if (typeof def !== 'function') {
289
289
  return () => structuredClone(def);
290
290
  }
291
- return () => structuredClone(def(store));
291
+ return (value) => structuredClone(def(store, value));
292
292
  }
293
293
 
294
294
  /** @import { Ref } from './ref.mjs' */
@@ -301,7 +301,7 @@ function makeDefault(store, def) {
301
301
  */
302
302
  class Store {
303
303
  /** @type {Map<string, Set<(value: any, store: any) => void | boolean | null>>} */
304
- #events = new Map()
304
+ #events = new Map();
305
305
  /**
306
306
  * 触发事件并通知监听器
307
307
  * @template {keyof Schema.Events} K
@@ -334,7 +334,7 @@ class Store {
334
334
  events.set(key, set);
335
335
  }
336
336
  set.add(fn);
337
- return () => { set?.delete(fn); }
337
+ return () => { set?.delete(fn); };
338
338
 
339
339
  }
340
340
  /**
@@ -345,7 +345,7 @@ class Store {
345
345
  * @param {boolean} [options.new] 是否为新建环境
346
346
  */
347
347
  static create(schema, options = {}) {
348
- return create({type: schema}, { ...options, parent: null });
348
+ return create({ type: schema }, { ...options, parent: null });
349
349
  }
350
350
  /**
351
351
  * 设置自定义类型的存储类
@@ -367,8 +367,7 @@ class Store {
367
367
  * @param {Schema.Field<M>} schema 字段的 Schema 定义
368
368
  * @param {object} [options] 可选配置
369
369
  * @param {Store?} [options.parent]
370
- * @param {((store: Store) => any) | any} [options.default]
371
- * @param {*} [options.state]
370
+ * @param {((store: Store, value?: any) => any) | object | number | string | boolean | null | undefined} [options.default]
372
371
  * @param {number | string | null} [options.index]
373
372
  * @param {number | Signal.State<number> | Signal.Computed<number>} [options.size]
374
373
  * @param {boolean} [options.null]
@@ -396,22 +395,19 @@ class Store {
396
395
  * @param {Ref?} [options.ref]
397
396
  *
398
397
  * @param {((value: any) => any)?} [options.setValue]
399
- * @param {((value: any) => any)?} [options.setState]
400
- * @param {((value: any, state: any) => [value: any, state: any])?} [options.convert]
398
+ * @param {((value: any) => any)?} [options.convert]
401
399
  *
402
400
  * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdate]
403
- * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdateState]
404
401
  */
405
402
  constructor(schema, {
406
- null: isNull, state, ref, default: defaultValue,
407
- setValue, setState, convert, onUpdate, onUpdateState,
403
+ null: isNull, ref, default: defaultValue,
404
+ setValue, convert, onUpdate,
408
405
  validator, validators,
409
406
  index, size, new: isNew, parent: parentNode,
410
407
  hidden, clearable, required, disabled, readonly, removable,
411
408
  label, description, placeholder, min, max, step, minLength, maxLength, pattern, values: values$1
412
409
  } = {}) {
413
410
  this.schema = schema;
414
- this.#state.set(typeof state === 'object' && state || {});
415
411
  const parent = parentNode instanceof Store ? parentNode : null;
416
412
  if (parent) {
417
413
  this.#parent = parent;
@@ -455,7 +451,7 @@ class Store {
455
451
  const s = selfReadonly.get();
456
452
  return s === null ? readonlyScript.get() : s;
457
453
  };
458
- const readonlyParent = parent ? parent.#readonly : null;
454
+ const readonlyParent = parent ? parent.#readonly : null;
459
455
  this.#selfReadonly = selfReadonly;
460
456
  this.#readonly = readonlyParent
461
457
  ? new Signal.Computed(() => readonlyParent.get() || getReadonly())
@@ -484,15 +480,15 @@ class Store {
484
480
 
485
481
  const [changed, changedResult, cancelChange] = createAsyncValidator(this, schema.validators?.change, validators?.change);
486
482
  const [blurred, blurredResult, cancelBlur] = createAsyncValidator(this, schema.validators?.blur, validators?.blur);
487
- this.listen('change', () => {changed();});
488
- this.listen('blur', () => {blurred();});
483
+ this.listen('change', () => { changed(); });
484
+ this.listen('blur', () => { blurred(); });
489
485
  this.#errors = merge(validatorResult, changedResult, blurredResult);
490
486
  this.#validatorResult = validatorResult;
491
487
  this.#changed = changed;
492
488
  this.#blurred = blurred;
493
489
  this.#cancelChange = cancelChange;
494
490
  this.#cancelBlur = cancelBlur;
495
-
491
+
496
492
  if (size instanceof Signal.State || size instanceof Signal.Computed) {
497
493
  this.#size = size;
498
494
  } else {
@@ -506,30 +502,25 @@ class Store {
506
502
  }
507
503
  this.#ref = ref || null;
508
504
  this.#onUpdate = onUpdate || null;
509
- this.#onUpdateState = onUpdateState || null;
510
505
  this.#setValue = typeof setValue === 'function' ? setValue : null;
511
- this.#setState = typeof setState === 'function' ? setState : null;
512
506
  this.#convert = typeof convert === 'function' ? convert : null;
513
507
  this.#index.set(index ?? '');
514
-
508
+
515
509
  for (const [k, f] of Object.entries(schema.events || {})) {
516
510
  if (typeof f !== 'function') { continue; }
517
511
  // @ts-ignore
518
512
  this.listen(k, f);
519
513
  }
520
514
  }
521
- #createDefault
522
- createDefault() { return this.#createDefault(); }
515
+ #createDefault;
516
+ /** @param {any} [value] @returns {any} */
517
+ createDefault(value) { return this.#createDefault(value); }
523
518
  /** @type {((value: any) => any)?} */
524
- #setValue = null
519
+ #setValue = null;
525
520
  /** @type {((value: any) => any)?} */
526
- #setState = null
527
- /** @type {((value: any, state: any) => [value: any, state: any])?} */
528
- #convert = null
521
+ #convert = null;
529
522
  /** @type {((value: any, index: any, store: Store) => void)?} */
530
- #onUpdate = null
531
- /** @type {((value: any, index: any, store: Store) => void)?} */
532
- #onUpdateState = null
523
+ #onUpdate = null;
533
524
  /** @readonly @type {Store?} */
534
525
  #parent = null;
535
526
  /** @readonly @type {Store} */
@@ -541,7 +532,7 @@ class Store {
541
532
  /** @readonly @type {any} */
542
533
  #component;
543
534
  /** @type {Signal.State<boolean>?} */
544
- #selfLoading = null
535
+ #selfLoading = null;
545
536
  /** @type {Signal.State<boolean>} */
546
537
  #loading;
547
538
  get loading() {
@@ -549,7 +540,7 @@ class Store {
549
540
  }
550
541
  set loading(loading) {
551
542
  const s = this.#selfLoading;
552
- if (!s) { return }
543
+ if (!s) { return; }
553
544
  s.set(Boolean(loading));
554
545
  }
555
546
  /** 存储对象自身 */
@@ -588,9 +579,9 @@ class Store {
588
579
  get immutable() { return this.#immutable; }
589
580
 
590
581
  /** @readonly @type {Signal.Computed<boolean>} */
591
- #new
582
+ #new;
592
583
  /** @readonly @type {Signal.State<boolean>} */
593
- #selfNew
584
+ #selfNew;
594
585
  get selfNew() { return this.#selfNew.get(); }
595
586
  set selfNew(v) { this.#selfNew.set(Boolean(v)); }
596
587
  /** 是否新建项 */
@@ -598,9 +589,9 @@ class Store {
598
589
  set new(v) { this.#selfNew.set(Boolean(v)); }
599
590
 
600
591
  /** @readonly @type {Signal.State<boolean?>} */
601
- #selfHidden
592
+ #selfHidden;
602
593
  /** @readonly @type {Signal.Computed<boolean>} */
603
- #hidden
594
+ #hidden;
604
595
  get selfHidden() { return this.#selfHidden.get(); }
605
596
  set selfHidden(v) { this.#selfHidden.set(typeof v === 'boolean' ? v : null); }
606
597
  /** 是否可隐藏 */
@@ -608,9 +599,9 @@ class Store {
608
599
  set hidden(v) { this.#selfHidden.set(typeof v === 'boolean' ? v : null); }
609
600
 
610
601
  /** @readonly @type {Signal.State<boolean?>} */
611
- #selfClearable
602
+ #selfClearable;
612
603
  /** @readonly @type {Signal.Computed<boolean>} */
613
- #clearable
604
+ #clearable;
614
605
  get selfClearable() { return this.#selfClearable.get(); }
615
606
  set selfClearable(v) { this.#selfClearable.set(typeof v === 'boolean' ? v : null); }
616
607
  /** 是否可清除 */
@@ -618,9 +609,9 @@ class Store {
618
609
  set clearable(v) { this.#selfClearable.set(typeof v === 'boolean' ? v : null); }
619
610
 
620
611
  /** @readonly @type {Signal.State<boolean?>} */
621
- #selfRequired
612
+ #selfRequired;
622
613
  /** @readonly @type {Signal.Computed<boolean>} */
623
- #required
614
+ #required;
624
615
  get selfRequired() { return this.#selfRequired.get(); }
625
616
  set selfRequired(v) { this.#selfRequired.set(typeof v === 'boolean' ? v : null); }
626
617
  /** 是否必填 */
@@ -628,9 +619,9 @@ class Store {
628
619
  set required(v) { this.#selfRequired.set(typeof v === 'boolean' ? v : null); }
629
620
 
630
621
  /** @readonly @type {Signal.State<boolean?>} */
631
- #selfDisabled
622
+ #selfDisabled;
632
623
  /** @readonly @type {Signal.Computed<boolean>} */
633
- #disabled
624
+ #disabled;
634
625
  get selfDisabled() { return this.#selfDisabled.get(); }
635
626
  set selfDisabled(v) { this.#selfDisabled.set(typeof v === 'boolean' ? v : null); }
636
627
  /** 是否禁用字段 */
@@ -638,9 +629,9 @@ class Store {
638
629
  set disabled(v) { this.#selfDisabled.set(typeof v === 'boolean' ? v : null); }
639
630
 
640
631
  /** @readonly @type {Signal.State<boolean?>} */
641
- #selfReadonly
632
+ #selfReadonly;
642
633
  /** @readonly @type {Signal.Computed<boolean>} */
643
- #readonly
634
+ #readonly;
644
635
  get selfReadonly() { return this.#selfReadonly.get(); }
645
636
  set selfReadonly(v) { this.#selfReadonly.set(typeof v === 'boolean' ? v : null); }
646
637
  /** 是否只读 */
@@ -649,9 +640,9 @@ class Store {
649
640
 
650
641
 
651
642
  /** @readonly @type {Signal.State<boolean?>} */
652
- #selfRemovable
643
+ #selfRemovable;
653
644
  /** @readonly @type {Signal.Computed<boolean>} */
654
- #removable
645
+ #removable;
655
646
  get selfRemovable() { return this.#selfRemovable.get(); }
656
647
  set selfRemovable(v) { this.#selfRemovable.set(typeof v === 'boolean' ? v : null); }
657
648
  /** 是否只读 */
@@ -660,9 +651,9 @@ class Store {
660
651
 
661
652
 
662
653
  /** @readonly @type {Signal.State<string?>} */
663
- #selfLabel
654
+ #selfLabel;
664
655
  /** @readonly @type {Signal.Computed<string?>} */
665
- #label
656
+ #label;
666
657
  get selfLabel() { return this.#selfLabel.get(); }
667
658
  set selfLabel(v) { this.#selfLabel.set(string(v)); }
668
659
  /** 字段的标签信息 */
@@ -671,9 +662,9 @@ class Store {
671
662
 
672
663
 
673
664
  /** @readonly @type {Signal.State<string?>} */
674
- #selfDescription
665
+ #selfDescription;
675
666
  /** @readonly @type {Signal.Computed<string?>} */
676
- #description
667
+ #description;
677
668
  get selfDescription() { return this.#selfDescription.get(); }
678
669
  set selfDescription(v) { this.#selfDescription.set(string(v)); }
679
670
  /** 字段的描述信息 */
@@ -681,9 +672,9 @@ class Store {
681
672
  set description(v) { this.#selfDescription.set(string(v)); }
682
673
 
683
674
  /** @readonly @type {Signal.State<string?>} */
684
- #selfPlaceholder
675
+ #selfPlaceholder;
685
676
  /** @readonly @type {Signal.Computed<string?>} */
686
- #placeholder
677
+ #placeholder;
687
678
  get selfPlaceholder() { return this.#selfPlaceholder.get(); }
688
679
  set selfPlaceholder(v) { this.#selfPlaceholder.set(string(v)); }
689
680
  /** 字段的占位符信息 */
@@ -692,9 +683,9 @@ class Store {
692
683
 
693
684
 
694
685
  /** @readonly @type {Signal.State<number?>} */
695
- #selfMin
686
+ #selfMin;
696
687
  /** @readonly @type {Signal.Computed<number?>} */
697
- #min
688
+ #min;
698
689
  get selfMin() { return this.#selfMin.get(); }
699
690
  set selfMin(v) { this.#selfMin.set(number(v)); }
700
691
  /** 数值字段的最小值限制 */
@@ -703,9 +694,9 @@ class Store {
703
694
 
704
695
 
705
696
  /** @readonly @type {Signal.State<number?>} */
706
- #selfMax
697
+ #selfMax;
707
698
  /** @readonly @type {Signal.Computed<number?>} */
708
- #max
699
+ #max;
709
700
  get selfMax() { return this.#selfMax.get(); }
710
701
  set selfMax(v) { this.#selfMax.set(number(v)); }
711
702
  /** 数值字段的最大值限制 */
@@ -714,9 +705,9 @@ class Store {
714
705
 
715
706
 
716
707
  /** @readonly @type {Signal.State<number?>} */
717
- #selfStep
708
+ #selfStep;
718
709
  /** @readonly @type {Signal.Computed<number?>} */
719
- #step
710
+ #step;
720
711
  get selfStep() { return this.#selfStep.get(); }
721
712
  set selfStep(v) { this.#selfStep.set(number(v)); }
722
713
  /** 数值字段的步长 */
@@ -724,9 +715,9 @@ class Store {
724
715
  set step(v) { this.#selfStep.set(number(v)); }
725
716
 
726
717
  /** @readonly @type {Signal.State<number?>} */
727
- #selfMinLength
718
+ #selfMinLength;
728
719
  /** @readonly @type {Signal.Computed<number?>} */
729
- #minLength
720
+ #minLength;
730
721
  get selfMinLength() { return this.#selfMinLength.get(); }
731
722
  set selfMinLength(v) { this.#selfMinLength.set(number(v)); }
732
723
  /** 最小长度 */
@@ -734,9 +725,9 @@ class Store {
734
725
  set minLength(v) { this.#selfMinLength.set(number(v)); }
735
726
 
736
727
  /** @readonly @type {Signal.State<number?>} */
737
- #selfMaxLength
728
+ #selfMaxLength;
738
729
  /** @readonly @type {Signal.Computed<number?>} */
739
- #maxLength
730
+ #maxLength;
740
731
  get selfMaxLength() { return this.#selfMaxLength.get(); }
741
732
  set selfMaxLength(v) { this.#selfMaxLength.set(number(v)); }
742
733
  /** 最大长度 */
@@ -744,9 +735,9 @@ class Store {
744
735
  set maxLength(v) { this.#selfMaxLength.set(number(v)); }
745
736
 
746
737
  /** @readonly @type {Signal.State<RegExp?>} */
747
- #selfPattern
738
+ #selfPattern;
748
739
  /** @readonly @type {Signal.Computed<RegExp?>} */
749
- #pattern
740
+ #pattern;
750
741
  get selfPattern() { return this.#selfPattern.get(); }
751
742
  set selfPattern(v) { this.#selfPattern.set(regex(v)); }
752
743
  /** 模式 */
@@ -755,9 +746,9 @@ class Store {
755
746
 
756
747
 
757
748
  /** @readonly @type {Signal.State<(Schema.Value.Group | Schema.Value)[] | null>} */
758
- #selfValues
749
+ #selfValues;
759
750
  /** @readonly @type {Signal.Computed<(Schema.Value.Group | Schema.Value)[] | null>} */
760
- #values
751
+ #values;
761
752
  get selfValues() { return this.#selfValues.get(); }
762
753
  set selfValues(v) { this.#selfValues.set(values(v)); }
763
754
  /** 可选值列表 */
@@ -766,24 +757,24 @@ class Store {
766
757
 
767
758
 
768
759
  /** @type {Signal.Computed<string[]>} */
769
- #errors
760
+ #errors;
770
761
  /** @type {Signal.Computed<string[]>} */
771
- #validatorResult
762
+ #validatorResult;
772
763
  /** @type {() => Promise<string[]>} */
773
- #changed
764
+ #changed;
774
765
  /** @type {() => Promise<string[]>} */
775
- #blurred
766
+ #blurred;
776
767
  /** @type {() => void} */
777
- #cancelChange
768
+ #cancelChange;
778
769
  /** @type {() => void} */
779
- #cancelBlur
770
+ #cancelBlur;
780
771
  /** 所有校验错误列表 */
781
772
  get errors() { return this.#errors.get(); }
782
773
  /** 字段校验错误信息 */
783
774
  get error() { return this.#errors.get()[0]; }
784
775
 
785
776
  /** @returns {IterableIterator<[key: string | number, value: Store]>} */
786
- *[Symbol.iterator]() {}
777
+ *[Symbol.iterator]() { }
787
778
  /**
788
779
  * 获取子存储
789
780
  * @param {string | number} key
@@ -795,11 +786,9 @@ class Store {
795
786
  #initValue = new Signal.State(/** @type {T?} */(null));
796
787
  #value = new Signal.State(this.#initValue.get());
797
788
 
798
-
799
- #state = new Signal.State(/** @type {any} */(null));
800
789
 
801
790
  /** 内容是否已改变 */
802
- get changed() { return this.#value.get() === this.#initValue.get(); }
791
+ get changed() { return Object.is(this.#value.get(), this.#initValue.get()); }
803
792
 
804
793
  /** 字段当前值 */
805
794
  get value() { return this.#value.get(); }
@@ -814,34 +803,26 @@ class Store {
814
803
  this.#requestUpdate();
815
804
  }
816
805
 
817
- /** 字段状态 */
818
- get state() { return this.#state.get(); }
819
- set state(v) {
820
- const newState = this.#setState?.(v);
821
- const sta = newState === undefined ? v : newState;
822
- this.#state.set(sta);
823
- this.#onUpdateState?.(sta, this.#index.get(), this);
824
- this.#requestUpdate();
825
- }
826
806
  #requestUpdate() {
827
807
  if (this.#needUpdate) { return; }
828
808
  this.#needUpdate = true;
829
809
  queueMicrotask(() => {
830
810
  const oldValue = this.#value.get();
831
- const oldState = this.#state.get();
832
- this.#runUpdate(oldValue, oldState);
811
+ this.#runUpdate(oldValue);
833
812
  });
834
813
  }
835
814
  /** 重置数据 */
836
- reset(value = this.#initValue.get()) {
837
- this.#reset(value);
815
+ reset(value = this.#set ? this.#initValue.get() : this.#createDefault(), isNew = this.#selfNew.get()) {
816
+ this.#reset(value, Boolean(isNew));
838
817
  }
839
818
  /**
840
819
  *
841
820
  * @param {*} v
821
+ * @param {boolean} isNew
842
822
  * @returns
843
823
  */
844
- #reset(v) {
824
+ #reset(v, isNew) {
825
+ this.#selfNew.set(isNew);
845
826
  const newValue = this.#setValue?.(v);
846
827
  const value = newValue === undefined ? v : newValue;
847
828
  this.#cancelChange();
@@ -849,16 +830,17 @@ class Store {
849
830
  this.#set = true;
850
831
  if (!value || typeof value !== 'object') {
851
832
  for (const [, field] of this) {
852
- field.#reset(null);
833
+ field.#reset(null, false);
853
834
  }
854
835
  this.#value.set(value);
855
836
  this.#initValue.set(value);
837
+ this.#onUpdate?.(value, this.#index.get(), this);
856
838
  return value;
857
839
  }
858
840
  /** @type {*} */
859
- const newValues = Array.isArray(value) ? [...value] : {...value};
841
+ const newValues = Array.isArray(value) ? [...value] : { ...value };
860
842
  for (const [key, field] of this) {
861
- newValues[key] = field.#reset(newValues[key]);
843
+ newValues[key] = field.#reset(Object.hasOwn(newValues, key) ? newValues[key] : undefined, false);
862
844
  }
863
845
  this.#value.set(newValues);
864
846
  this.#initValue.set(newValues);
@@ -871,59 +853,48 @@ class Store {
871
853
  /**
872
854
  *
873
855
  * @param {T} value
874
- * @param {*} state
875
856
  * @returns
876
857
  */
877
- #toUpdate(value, state) {
878
- const [val,sta] = this.#convert?.(value, state) || [value, state];
879
- if(this.#value.get() === val && this.#state.get() === sta) { return [val,sta] }
880
- this.#value.set(val);
881
- this.#state.set(sta);
882
- return this.#runUpdate(val, sta);
858
+ #toUpdate(value) {
859
+ let val = this.#convert?.(value) ?? value;
860
+ if (val === undefined) { val = value; }
861
+ if (Object.is(this.#value.get(), val)) { return val; }
862
+ return this.#runUpdate(val);
883
863
  }
884
864
  /**
885
865
  *
886
866
  * @param {*} val
887
- * @param {*} sta
888
867
  * @returns {[any, any]}
889
868
  */
890
- #runUpdate(val, sta) {
869
+ #runUpdate(val) {
891
870
  this.#needUpdate = false;
892
871
  let initValue = val;
893
872
  if (val && typeof val === 'object') {
894
873
  /** @type {T} */
895
874
  // @ts-ignore
896
- let newValues = Array.isArray(val) ? [...val] : {...val};
897
- let newStates = Array.isArray(val) ? Array.isArray(sta) ? [...sta] : [] : {...sta};
875
+ let newValues = Array.isArray(val) ? [...val] : { ...val };
898
876
  let updated = false;
899
877
  for (const [key, field] of this) {
900
878
  // @ts-ignore
901
- const data = val[key];
902
- const state = sta?.[key];
903
- const [newData, newState] = field.#toUpdate(data, state);
904
- if (data !== newData) {
905
- // @ts-ignore
906
- newValues[key] = newData;
907
- updated = true;
908
- }
909
- if (state !== newState) {
910
- newStates[key] = newState;
911
- updated = true;
912
- }
879
+ const data = Object.hasOwn(val, key) ? val[key] : undefined;
880
+ const newData = field.#toUpdate(data);
881
+ if (Object.is(data, newData)) { continue; }
882
+ // @ts-ignore
883
+ newValues[key] = newData;
884
+ updated = true;
913
885
  }
914
886
  if (updated) {
915
887
  val = newValues;
916
- sta = newStates;
917
888
  initValue = val;
918
889
  this.#value.set(val);
919
- this.#state.set(newStates);
920
890
  }
921
891
  }
922
892
  if (!this.#set) {
923
893
  this.#set = true;
924
894
  this.#initValue.set(initValue);
925
895
  }
926
- return [val, sta];
896
+ this.#value.set(val);
897
+ return val;
927
898
  }
928
899
  /**
929
900
  * 异步校验
@@ -947,18 +918,18 @@ class Store {
947
918
  return Promise.all([this.#validatorResult.get(), this.#changed(), this.#blurred()])
948
919
  .then(v => {
949
920
  const errors = v.flat();
950
- return errors.length ? errors : null
921
+ return errors.length ? errors : null;
951
922
  });
952
923
  }
953
924
  const selfPath = Array.isArray(path) ? path : [];
954
- const list = [this.validate().then(errors => {
955
- if (!errors?.length) {return [];}
956
- return [{path: [...selfPath], store: /** @type {Store} */(this), errors}]
925
+ const list = [this.validate(true).then(errors => {
926
+ if (!errors?.length) { return []; }
927
+ return [{ path: [...selfPath], store: /** @type {Store} */(this), errors }];
957
928
  })];
958
929
  for (const [key, field] of this) {
959
930
  list.push(field.validate([...selfPath, key]));
960
931
  }
961
- return Promise.all(list).then(v => v.flat())
932
+ return Promise.all(list).then(v => v.flat());
962
933
  }
963
934
  }
964
935
 
@@ -972,8 +943,8 @@ class Store {
972
943
  class ObjectStore extends Store {
973
944
  get kind() { return 'object'; }
974
945
  /** @type {Record<string, Store>} */
975
- #children
976
- *[Symbol.iterator]() {yield* Object.entries(this.#children);}
946
+ #children;
947
+ *[Symbol.iterator]() { yield* Object.entries(this.#children); }
977
948
  /**
978
949
  *
979
950
  * @param {string | number} key
@@ -987,26 +958,25 @@ class ObjectStore extends Store {
987
958
  * @param {number | string | null} [options.index]
988
959
  * @param {boolean} [options.new]
989
960
  * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdate]
990
- * @param {((value: T?, index: any, store: Store) => void)?} [options.onUpdateState]
991
961
  */
992
- constructor(schema,{ parent, index, new: isNew, onUpdate, onUpdateState } = {}) {
962
+ constructor(schema, { parent, index, new: isNew, onUpdate } = {}) {
993
963
  const childrenTypes = Object.entries(schema.type);
994
964
  /** @type {Record<string, Store>} */
995
965
  const children = Object.create(null);
996
966
  super(schema, {
997
- parent, index, new: isNew, onUpdate, onUpdateState,
967
+ parent, index, new: isNew, onUpdate,
998
968
  size: childrenTypes.length,
999
969
  setValue(v) { return typeof v === 'object' ? v : null; },
1000
- setState(v) { return typeof v === 'object' ? v : null; },
1001
- convert(v, state) {
1002
- return [
1003
- typeof v === 'object' ? v : {},
1004
- typeof state === 'object' ? state : {},
1005
- ]
970
+ convert(v) {
971
+ return typeof v === 'object' ? v : {};
1006
972
  },
1007
- default: schema.default ?? (() => Object.fromEntries(
1008
- Object.entries(children).map(([k,v]) => [k, v.createDefault()])
1009
- )),
973
+ default: schema.default ?? ((store, value) => {
974
+ const list = Object.entries(children);
975
+ let obj = value;
976
+ if (!obj || typeof obj !== 'object') { obj = schema.default; }
977
+ if (!obj || typeof obj !== 'object') { obj = {}; }
978
+ return Object.fromEntries(list.map(([k, v]) => [k, v.createDefault(Object.hasOwn(obj, k) ? obj[k] : null)]));
979
+ }),
1010
980
  });
1011
981
  const childCommonOptions = {
1012
982
  parent: this,
@@ -1014,17 +984,12 @@ class ObjectStore extends Store {
1014
984
  onUpdate: (value, index, store) => {
1015
985
  if (store !== this.#children[index]) { return; }
1016
986
  // @ts-ignore
1017
- this.value = {...this.value, [index]: value};
987
+ this.value = { ...this.value, [index]: value };
1018
988
  },
1019
- /** @param {*} state @param {*} index @param {Store} store */
1020
- onUpdateState: (state, index, store) => {
1021
- if (store !== this.#children[index]) { return; }
1022
- this.state = {...this.state, [index]: state};
1023
- }
1024
989
  };
1025
990
 
1026
991
  for (const [index, field] of childrenTypes) {
1027
- children[index] = create(field, {...childCommonOptions, index});
992
+ children[index] = create(field, { ...childCommonOptions, index });
1028
993
  }
1029
994
  this.#children = children;
1030
995
  }
@@ -1069,9 +1034,8 @@ class ArrayStore extends Store {
1069
1034
  * @param {boolean} [options.new]
1070
1035
  * @param {boolean} [options.addable]
1071
1036
  * @param {(value: any, index: any, store: Store) => void} [options.onUpdate]
1072
- * @param {(value: any, index: any, store: Store) => void} [options.onUpdateState]
1073
1037
  */
1074
- constructor(schema, { parent, onUpdate, onUpdateState, index, new: isNew, addable } = {}) {
1038
+ constructor(schema, { parent, onUpdate, index, new: isNew, addable } = {}) {
1075
1039
  const childrenState = new Signal.State(/** @type {Store[]} */([]));
1076
1040
  // @ts-ignore
1077
1041
  const updateChildren = (list) => {
@@ -1090,22 +1054,16 @@ class ArrayStore extends Store {
1090
1054
  super(schema, {
1091
1055
  index, new: isNew, parent,
1092
1056
  size: new Signal.Computed(() => childrenState.get().length),
1093
- state: [],
1094
1057
  setValue(v) { return Array.isArray(v) ? v : v == null ? null : [v]; },
1095
- setState(v) { return Array.isArray(v) ? v : v == null ? null : [v]; },
1096
- convert(v, state) {
1058
+ convert(v) {
1097
1059
  const val = Array.isArray(v) ? v : v == null ? null : [v];
1098
1060
  updateChildren(val);
1099
- return [
1100
- val,
1101
- (Array.isArray(state) ? state : v == null ? [] : [state]),
1102
- ];
1061
+ return val;
1103
1062
  },
1104
- onUpdate: (value, index, state) => {
1063
+ onUpdate: (value, index, store) => {
1105
1064
  updateChildren(value);
1106
- onUpdate?.(value, index, state);
1065
+ onUpdate?.(value, index, store);
1107
1066
  },
1108
- onUpdateState,
1109
1067
  default: schema.default ?? [],
1110
1068
  });
1111
1069
 
@@ -1124,16 +1082,6 @@ class ArrayStore extends Store {
1124
1082
  val[index] = value;
1125
1083
  this.value = val;
1126
1084
  },
1127
- /** @param {*} state @param {*} index @param {Store} store */
1128
- onUpdateState: (state, index, store) => {
1129
- if (childrenState.get()[index] !== store) { return; }
1130
- const sta = [...this.state || []];
1131
- if (sta.length < index) {
1132
- sta.length = index;
1133
- }
1134
- sta[index] = state;
1135
- this.state = sta;
1136
- },
1137
1085
  };
1138
1086
  this.#create = (index, isNew) => {
1139
1087
  const child = create(schema, { ...childCommonOptions, index, new: isNew });
@@ -1175,13 +1123,7 @@ class ArrayStore extends Store {
1175
1123
  children[i].index = i;
1176
1124
  }
1177
1125
  const val = [...data];
1178
- val.splice(insertIndex, 0, value ?? item.createDefault());
1179
- const state = this.state;
1180
- if (Array.isArray(state)) {
1181
- const sta = [...state];
1182
- sta.splice(insertIndex, 0, {});
1183
- this.state = sta;
1184
- }
1126
+ val.splice(insertIndex, 0, item.createDefault(value));
1185
1127
  this.#children.set(children);
1186
1128
  this.value = val;
1187
1129
  return true;
@@ -1211,12 +1153,6 @@ class ArrayStore extends Store {
1211
1153
  }
1212
1154
  const val = [...data];
1213
1155
  const [value] = val.splice(removeIndex, 1);
1214
- const state = this.state;
1215
- if (Array.isArray(state)) {
1216
- const sta = [...this.state];
1217
- sta.splice(removeIndex, 1);
1218
- this.state = sta;
1219
- }
1220
1156
  this.#children.set(children);
1221
1157
  this.value = val;
1222
1158
  return value;
@@ -1256,16 +1192,6 @@ class ArrayStore extends Store {
1256
1192
  while (toIndex > val.length) { val.push(null); }
1257
1193
  val.splice(toIndex, 0, ...values);
1258
1194
 
1259
- const state = this.state;
1260
- if (Array.isArray(state)) {
1261
- const sta = [...state];
1262
- const states = sta.splice(from, len);
1263
- for (let i = states.length; i < len; i++) { states.push({}); }
1264
- while (toIndex > sta.length) { sta.push({}); }
1265
- sta.splice(toIndex, 0, ...states);
1266
-
1267
- this.state = sta;
1268
- }
1269
1195
  this.#children.set(children);
1270
1196
  this.value = val;
1271
1197
  return len;
@@ -1293,15 +1219,6 @@ class ArrayStore extends Store {
1293
1219
  const bValue = val[b];
1294
1220
  val[b] = aValue;
1295
1221
  val[a] = bValue;
1296
- const state = this.state;
1297
- if (Array.isArray(state)) {
1298
- const sta = [...state];
1299
- const aValue = sta[a];
1300
- const bValue = sta[b];
1301
- sta[b] = aValue;
1302
- sta[a] = bValue;
1303
- this.state = sta;
1304
- }
1305
1222
  this.#children.set(children);
1306
1223
  this.value = val;
1307
1224
  return true;
@@ -2450,7 +2367,6 @@ const bindable = {
2450
2367
  kind: true,
2451
2368
 
2452
2369
  value: true,
2453
- state: true,
2454
2370
 
2455
2371
  store: true,
2456
2372
  parent: true,
@@ -2508,7 +2424,8 @@ function *toItem(val, key = '', sign = '$') {
2508
2424
  yield [`${key}${sign}${k}`, {get: () => val[k]}];
2509
2425
  }
2510
2426
  yield [`${key}${sign}value`, {get: () => val.value, set: v => val.value = v}];
2511
- yield [`${key}${sign}state`, {get: () => val.state, set: v => val.state = v}];
2427
+ /** @deprecated */
2428
+ yield [`${key}${sign}state`, {get: () => null, set: v => {}}];
2512
2429
  yield [`${key}${sign}reset`, {exec: () => val.reset()}];
2513
2430
  // @ts-ignore
2514
2431
  yield [`${key}${sign}validate`, {exec: v => val.validate(v ? [] : null)}];
@@ -2796,6 +2713,8 @@ class Environment {
2796
2713
  const res = Object.fromEntries([...bindableSet].map(v => [
2797
2714
  `$${v}`, cb => watch(() => store[v], cb, true)
2798
2715
  ]));
2716
+ /** @deprecated */
2717
+ res.$$state = cb => watch(() => null, cb, true);
2799
2718
  return res;
2800
2719
  }
2801
2720
  /**
@@ -2812,13 +2731,15 @@ class Environment {
2812
2731
  }
2813
2732
  /** @type {Record<string, {get(): any; set?(v: any): void}> | void} */
2814
2733
  const res = Object.fromEntries([...bindableSet].map(v => [
2815
- `$${v}`, v === 'value' || v === 'state' ? {
2734
+ `$${v}`, v === 'value' ? {
2816
2735
  get: () => store[v],
2817
2736
  set: (s)=>{store[v] = s;}
2818
2737
  } : {
2819
2738
  get: () => store[v],
2820
2739
  }
2821
2740
  ]));
2741
+ /** @deprecated */
2742
+ res.$$state = { get: () => null, set: () => {} };
2822
2743
  return res;
2823
2744
  }
2824
2745
  /**
@@ -2833,7 +2754,8 @@ class Environment {
2833
2754
  if (!store) { return; }
2834
2755
  switch(type) {
2835
2756
  case 'value': return v => {store.value = v; };
2836
- case 'state': return v => {store.state = v; };
2757
+ /** @deprecated */
2758
+ case 'state': return v => { };
2837
2759
  }
2838
2760
  }
2839
2761
  /**
@@ -2851,7 +2773,8 @@ class Environment {
2851
2773
  }
2852
2774
  return {
2853
2775
  '$value': v => {store.value = v; },
2854
- '$state': v => {store.state = v; },
2776
+ /** @deprecated */
2777
+ '$state': v => { },
2855
2778
  '$input': v => {store.emit('input', v); },
2856
2779
  '$change': v => {store.emit('change', v); },
2857
2780
  '$click': v => {store.emit('click', v); },
@@ -4746,431 +4669,85 @@ function render(store, layouts, parent, { component, global, relate, enhancement
4746
4669
  /** @import { Store } from '../Store/index.mjs' */
4747
4670
  /** @import { StoreLayout } from '../types.mjs' */
4748
4671
 
4672
+
4749
4673
  /**
4750
4674
  *
4751
- * @param {Store<any, any>} store
4752
- * @param {StoreLayout.Renderer} fieldRenderer
4753
- * @param {StoreLayout.Field?} layout
4754
- * @param {object} option
4755
- * @param {(string | StoreLayout.Action[])[]} option.columns
4756
- * @param {() => void} option.remove
4757
- * @param {() => void} option.dragenter
4758
- * @param {() => void} option.dragstart
4759
- * @param {() => void} option.dragend
4760
- * @param {{get(): boolean}} option.deletable
4761
- * @param {StoreLayout.Options?} options
4762
-
4763
- * @returns {[HTMLTableSectionElement, () => void]}
4675
+ * @param {string} field
4764
4676
  */
4765
- function Line(store, fieldRenderer, layout, {
4766
- columns,
4767
- remove, dragenter, dragstart, dragend, deletable
4768
- }, options) {
4769
- const root = document.createElement('tbody');
4770
- root.addEventListener('dragenter', () => {
4771
- dragenter();
4772
- });
4773
- root.addEventListener('dragstart', (event) => {
4774
- if (event.target !== event.currentTarget) { return; }
4775
- dragstart();
4776
- });
4777
- root.addEventListener('dragend', dragend);
4778
- const head = root.appendChild(document.createElement('tr'));
4779
-
4780
- /** @type {(() => void)[]} */
4781
- const destroyList = [];
4782
-
4783
-
4784
- let trigger = () => { };
4785
- /** @type {HTMLButtonElement[]} */
4786
- const triggerList = [];
4787
- if (columns.find(v => Array.isArray(v) && v.includes('trigger'))) {
4788
- const body = root.appendChild(document.createElement('tr'));
4789
- const main = body.appendChild(document.createElement('td'));
4790
- main.colSpan = columns.length;
4791
-
4792
- const [form, destroy] = Form(store, fieldRenderer, layout, options);
4793
- main.appendChild(form);
4794
- destroyList.push(destroy);
4795
- body.hidden = true;
4796
- trigger = function click() {
4797
- if (body.hidden) {
4798
- body.hidden = false;
4799
- for (const ext of triggerList) {
4800
- ext.classList.remove('NeeloongForm-table-line-open');
4801
- ext.classList.add('NeeloongForm-table-line-close');
4802
- }
4803
- } else {
4804
- body.hidden = true;
4805
- for (const ext of triggerList) {
4806
- ext.classList.remove('NeeloongForm-table-line-close');
4807
- ext.classList.add('NeeloongForm-table-line-open');
4808
- }
4809
- }
4810
- };
4811
-
4812
- }
4677
+ function createFieldFilter(field) {
4813
4678
 
4814
4679
  /**
4815
4680
  *
4816
- * @param {PointerEvent} event
4681
+ * @param {StoreLayout.Item} v
4682
+ * @returns {v is StoreLayout.Field}
4817
4683
  */
4818
- function pointerdown({ pointerId }) {
4819
- root.draggable = true;
4820
- /** @param {PointerEvent} event */
4821
- function pointerup(event) {
4822
- if (event.pointerId !== pointerId) { return; }
4823
- if (!root) { return; }
4824
- root.draggable = false;
4825
- window.removeEventListener('pointerup', pointerup, { capture: true });
4826
- window.removeEventListener('pointercancel', pointerup, { capture: true });
4827
- }
4828
- window.addEventListener('pointerup', pointerup, { capture: true });
4829
- window.addEventListener('pointercancel', pointerup, { capture: true });
4830
-
4831
- }
4832
4684
 
4833
- for (const name of columns) {
4834
- if (!Array.isArray(name)) {
4835
- const td = head.appendChild(document.createElement('td'));
4836
- const child = store.child(name);
4837
- if (!child) { continue; }
4838
- const [el, destroy] = FormField(child, fieldRenderer, null, options, true);
4839
- destroyList.push(destroy);
4840
- td.appendChild(el);
4841
- continue;
4842
- }
4843
- const handle = head.appendChild(document.createElement('th'));
4844
- handle.classList.add('NeeloongForm-table-line-handle');
4845
- for (const k of name) {
4846
- switch (k) {
4847
- case 'trigger': {
4848
- const ext = handle.appendChild(document.createElement('button'));
4849
- ext.classList.add('NeeloongForm-table-line-open');
4850
- triggerList.push(ext);
4851
- ext.addEventListener('click', trigger);
4852
- continue;
4853
- }
4854
- case 'move': {
4855
- if (!options?.editable) { continue; }
4856
- const move = handle.appendChild(document.createElement('button'));
4857
- move.classList.add('NeeloongForm-table-move');
4858
- move.addEventListener('pointerdown', pointerdown);
4859
- destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
4860
- move.disabled = disabled;
4861
- }, true));
4862
- continue;
4863
- }
4864
- case 'remove': {
4865
- if (!options?.editable) { continue; }
4866
- const del = handle.appendChild(document.createElement('button'));
4867
- del.classList.add('NeeloongForm-table-remove');
4868
- del.addEventListener('click', remove);
4869
- destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
4870
- del.disabled = disabled;
4871
- }, true));
4872
- continue;
4873
- }
4874
- case 'serial': {
4875
- const serial = handle.appendChild(document.createElement('span'));
4876
- serial.classList.add('NeeloongForm-table-serial');
4877
- continue;
4878
- }
4879
- }
4880
- }
4881
- }
4685
+ return function (v) {
4686
+ if (v.type && v.type !== 'field') { return false; }
4687
+ return v.field === field;
4882
4688
 
4883
- return [root, () => {
4884
- for (const destroy of destroyList) {
4885
- destroy();
4886
- }
4887
- }];
4689
+ };
4888
4690
  }
4889
-
4890
- /** @import { Store, ArrayStore } from '../Store/index.mjs' */
4891
- /** @import { StoreLayout } from '../types.mjs' */
4892
-
4893
4691
  /**
4894
4692
  *
4895
- * @param {HTMLElement} parent
4896
- * @param {({ field: string; width: any; label: any; } | StoreLayout.Action[])[]} columns
4897
- * @param {() => any} add
4898
- * @param {{get(): boolean}} addable
4899
- * @param {boolean?} [editable]
4693
+ * @param {StoreLayout.Renderer} fieldRenderer
4694
+ * @param {Store} store
4695
+ * @param {Node} node
4696
+ * @param {StoreLayout.Options?} options
4697
+ * @param {StoreLayout?} [layout]
4698
+ * @param {Node} [anchor]
4699
+ * @param {(child?: Store<any, any> | undefined) => void} [dragenter]
4900
4700
  */
4901
- function renderHead(parent, columns, add, addable, editable) {
4902
- const tr = parent.appendChild(document.createElement('tr'));
4701
+ function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragenter) {
4903
4702
  /** @type {(() => void)[]} */
4904
4703
  const destroyList = [];
4905
- for (const col of columns) {
4906
- if (!Array.isArray(col)) {
4907
- const { width, label } = col;
4908
- const td = tr.appendChild(document.createElement('th'));
4909
- if (width) {
4910
- td.setAttribute('width', width);
4704
+ if (node instanceof Element) {
4705
+ const tagName = node.tagName.toLowerCase();
4706
+ if (!node.parentNode) { return () => { }; }
4707
+ if (tagName === 'nl-form-field') {
4708
+ const field = node.getAttribute('name') || '';
4709
+ const mode = node.getAttribute('mode') || '';
4710
+ const fieldStore = field ? store.child(field) : store;
4711
+ const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) || null : null;
4712
+ if (!fieldStore) { return () => { }; }
4713
+ switch (mode) {
4714
+ case 'grid': {
4715
+ const [el, destroy] = Form(store, fieldRenderer, fieldLayout, options);
4716
+ node.replaceWith(el);
4717
+ return destroy;
4718
+ }
4719
+ }
4720
+ const component = fieldStore.component;
4721
+ if (component) {
4722
+ const res = fieldRenderer(fieldStore, component, options);
4723
+ if (res) {
4724
+ const [el, destroy] = res;
4725
+ node.replaceWith(el);
4726
+ return destroy;
4911
4727
  }
4912
- td.innerText = label;
4913
- continue;
4914
- }
4915
- const th = tr.appendChild(document.createElement('th'));
4916
- if (!editable) { continue; }
4917
- for (const it of col) {
4918
- switch (it) {
4919
- case 'add':
4920
- const button = th.appendChild(document.createElement('button'));
4921
- button.addEventListener('click', add);
4922
- button.classList.add('NeeloongForm-table-add');
4923
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
4924
- continue;
4925
4728
  }
4729
+ const value = node.getAttribute('placeholder') || '';
4730
+ node.replaceWith(document.createTextNode(value));
4731
+ return () => { };
4926
4732
  }
4927
- }
4928
- return () => {
4929
- for (const destroy of destroyList) {
4930
- destroy();
4733
+ if (tagName === 'nl-form-button') {
4734
+ const button = document.createElement('button');
4735
+ button.className = 'NeeloongForm-item-button';
4736
+ const click = node.getAttribute('click') || '';
4737
+ const call = options?.call;
4738
+ if (click && typeof call === 'function') {
4739
+ button.addEventListener('click', e => call(click, e, store, options));
4740
+ }
4741
+ for (const n of [...node.childNodes]) {
4742
+ button.appendChild(n);
4743
+ }
4744
+ node.replaceWith(button);
4745
+ return () => { };
4931
4746
  }
4932
- };
4933
- }
4934
- /**
4935
- *
4936
- * @param {ArrayStore} store
4937
- * @param {StoreLayout.Renderer} fieldRenderer
4938
- * @param {StoreLayout.Field?} layout
4939
- * @param {StoreLayout.Options?} options
4940
- * @returns {[HTMLTableElement, () => void]}
4941
- */
4942
- function Table(store, fieldRenderer, layout, options) {
4943
- const headerColumns = layout?.columns;
4944
- const fieldList = Object.entries(store.type || {})
4945
- .filter(([k, v]) => typeof v?.type !== 'object')
4946
- .map(([field, {width, label}]) => ({field, width, label}));
4947
- /** @type {({ field: string; width: any; label: any; } | StoreLayout.Action[])[]} */
4948
- let columns = [];
4949
- if (Array.isArray(headerColumns)) {
4950
- const map = new Map(fieldList.map(v => [v.field, v]));
4951
- columns = headerColumns.map(v => {
4952
- if (typeof v === 'number') { return [] }
4953
- if (typeof v === 'string') { return map.get(v) || [] }
4954
- if (!Array.isArray(v)) { return []; }
4955
- /** @type {Set<StoreLayout.Action>} */
4956
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial']);
4957
- return v.filter(v => options.delete(v));
4958
- }).filter(v => !Array.isArray(v) || v.length);
4959
- }
4960
- if (!columns.length) {
4961
- columns = [['add', 'trigger', 'move', 'remove', 'serial']];
4962
- }
4963
- if (!columns.find(v => !Array.isArray(v))) {
4964
- columns.push(...fieldList.slice(0, 3));
4965
- }
4966
-
4967
-
4968
-
4969
- const table = document.createElement('table');
4970
- table.className = 'NeeloongForm-table';
4971
- const thead = table.appendChild(document.createElement('thead'));
4972
-
4973
-
4974
-
4975
- const addable = new Signal.Computed(() => store.addable);
4976
- const deletable = { get: () => Boolean(options?.editable) };
4977
- function add() {
4978
- const data = {};
4979
- store.add(data);
4980
-
4981
- }
4982
- /**
4983
- *
4984
- * @param {Store} child
4985
- */
4986
- function remove(child) {
4987
- store.remove(Number(child.index));
4988
- }
4989
- let dragRow = -1;
4990
- /**
4991
- *
4992
- * @param {Store} [child]
4993
- */
4994
- function dragenter(child) {
4995
- if (dragRow < 0) { return; }
4996
- const index = child ? Number(child.index) : store.children.length;
4997
- if (index < 0 || dragRow < 0 || dragRow === index) { return; }
4998
- if (store.move(dragRow, index)) {
4999
- dragRow = index;
5000
- }
5001
- }
5002
- /**
5003
- *
5004
- * @param {Store} child
5005
- */
5006
- function dragstart(child) {
5007
- dragRow = Number(child.index);
5008
-
5009
- }
5010
- function dragend() {
5011
- dragRow = -1;
5012
-
5013
- }
5014
- /** @type {(() => void)[]} */
5015
- const destroyList = [];
5016
- destroyList.push(renderHead(thead, columns, add, addable, Boolean(options?.editable)));
5017
- switch (layout?.tableFoot) {
5018
- default:
5019
- case 'header': {
5020
- const tfoot = table.appendChild(document.createElement('tfoot'));
5021
- tfoot.addEventListener('dragenter', () => { dragenter(); });
5022
- destroyList.push(renderHead(tfoot, columns, add, addable, Boolean(options?.editable)));
5023
- break;
5024
- }
5025
- case 'add': {
5026
- const tfoot = table.appendChild(document.createElement('tfoot'));
5027
- tfoot.addEventListener('dragenter', () => { dragenter(); });
5028
- const tr = tfoot.appendChild(document.createElement('tr'));
5029
- const th = tr.appendChild(document.createElement('th'));
5030
- th.colSpan = columns.length;
5031
- const button = th.appendChild(document.createElement('button'));
5032
- button.addEventListener('click', add);
5033
- button.classList.add('NeeloongForm-table-foot-add');
5034
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5035
- break;
5036
- }
5037
- case 'none':
5038
- }
5039
- const start = thead;
5040
- /** @type {Map<Store, [HTMLTableSectionElement, () => void]>} */
5041
- let seMap = new Map();
5042
- /** @param {Map<Store, [tbody: HTMLTableSectionElement, destroy: () => void]>} map */
5043
- function destroyMap(map) {
5044
- for (const [el, destroy] of map.values()) {
5045
- destroy();
5046
- el.remove();
5047
- }
5048
-
5049
- }
5050
- const columnNames = columns.map((v) => Array.isArray(v) ? v : v.field);
5051
- const childrenResult = watch(() => store.children, function render(children) {
5052
- let nextNode = thead.nextSibling;
5053
- const oldSeMap = seMap;
5054
- seMap = new Map();
5055
- for (let child of children) {
5056
- const old = oldSeMap.get(child);
5057
- if (!old) {
5058
- const [el, destroy] = Line(child, fieldRenderer, layout, {
5059
- columns: columnNames,
5060
- remove: remove.bind(null, child),
5061
- dragenter: dragenter.bind(null, child),
5062
- dragstart: dragstart.bind(null, child),
5063
- dragend,
5064
- deletable,
5065
- }, options);
5066
- table.insertBefore(el, nextNode);
5067
- seMap.set(child, [el, destroy]);
5068
- continue;
5069
- }
5070
- oldSeMap.delete(child);
5071
- seMap.set(child, old);
5072
- if (nextNode === old[0]) {
5073
- nextNode = nextNode.nextSibling;
5074
- continue;
5075
- }
5076
- table.insertBefore(old[0], nextNode);
5077
- }
5078
- destroyMap(oldSeMap);
5079
- }, true);
5080
-
5081
- return [table, () => {
5082
- start.remove();
5083
- thead.remove();
5084
- destroyMap(seMap);
5085
- childrenResult();
5086
- for (const destroy of destroyList) {
5087
- destroy();
5088
- }
5089
- }];
5090
- }
5091
-
5092
- /** @import { Store } from '../Store/index.mjs' */
5093
- /** @import { StoreLayout } from '../types.mjs' */
5094
-
5095
-
5096
- /**
5097
- *
5098
- * @param {string} field
5099
- */
5100
- function createFieldFilter(field) {
5101
-
5102
- /**
5103
- *
5104
- * @param {StoreLayout.Item} v
5105
- * @returns {v is StoreLayout.Field}
5106
- */
5107
-
5108
- return function (v) {
5109
- if (v.type && v.type !== 'field') { return false; }
5110
- return v.field === field;
5111
-
5112
- };
5113
- }
5114
- /**
5115
- *
5116
- * @param {StoreLayout.Renderer} fieldRenderer
5117
- * @param {Store} store
5118
- * @param {Node} node
5119
- * @param {StoreLayout.Options?} options
5120
- * @param {StoreLayout?} [layout]
5121
- * @param {Node} [anchor]
5122
- * @param {(child?: Store<any, any> | undefined) => void} [dragenter]
5123
- */
5124
- function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragenter) {
5125
- /** @type {(() => void)[]} */
5126
- const destroyList = [];
5127
- if (node instanceof Element) {
5128
- const tagName = node.tagName.toLowerCase();
5129
- if (!node.parentNode) { return () => { }; }
5130
- if (tagName === 'nl-form-field') {
5131
- const field = node.getAttribute('name') || '';
5132
- const mode = node.getAttribute('mode') || '';
5133
- const fieldStore = field ? store.child(field) : store;
5134
- const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) || null : null;
5135
- if (!fieldStore) { return () => { }; }
5136
- switch (mode) {
5137
- case 'grid': {
5138
- const [el, destroy] = Form(store, fieldRenderer, fieldLayout, options);
5139
- node.replaceWith(el);
5140
- return destroy;
5141
- }
5142
- }
5143
- const component = fieldStore.component;
5144
- if (component) {
5145
- const res = fieldRenderer(fieldStore, component, options);
5146
- if (res) {
5147
- const [el, destroy] = res;
5148
- node.replaceWith(el);
5149
- return destroy;
5150
- }
5151
- }
5152
- const value = node.getAttribute('placeholder') || '';
5153
- node.replaceWith(document.createTextNode(value));
5154
- return () => { };
5155
- }
5156
- if (tagName === 'nl-form-button') {
5157
- const button = document.createElement('button');
5158
- button.className = 'NeeloongForm-item-button';
5159
- const click = node.getAttribute('click') || '';
5160
- const call = options?.call;
5161
- if (click && typeof call === 'function') {
5162
- button.addEventListener('click', e => call(click, e, store, options));
5163
- }
5164
- for (const n of [...node.childNodes]) {
5165
- button.appendChild(n);
5166
- }
5167
- node.replaceWith(button);
5168
- return () => { };
5169
- }
5170
- const name = node.getAttribute('nl-form-field');
5171
- if (name) {
5172
- const array = name.endsWith('[]');
5173
- const field = array ? name.slice(0, name.length - 2) : name;
4747
+ const name = node.getAttribute('nl-form-field');
4748
+ if (name) {
4749
+ const array = name.endsWith('[]');
4750
+ const field = array ? name.slice(0, name.length - 2) : name;
5174
4751
 
5175
4752
  const fieldStore = field ? store.child(field) : store;
5176
4753
  if (!fieldStore) {
@@ -5306,34 +4883,406 @@ function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragent
5306
4883
  }
5307
4884
  }
5308
4885
  }
5309
- if (node instanceof Element || node instanceof DocumentFragment) {
5310
- for (const n of [...node.children]) {
5311
- destroyList.push(renderHtml(store, fieldRenderer, n, options, layout, anchor));
4886
+ if (node instanceof Element || node instanceof DocumentFragment) {
4887
+ for (const n of [...node.children]) {
4888
+ destroyList.push(renderHtml(store, fieldRenderer, n, options, layout, anchor));
4889
+ }
4890
+ }
4891
+ return () => {
4892
+ for (const destroy of destroyList) {
4893
+ destroy();
4894
+ }
4895
+ };
4896
+
4897
+ }
4898
+
4899
+ /**
4900
+ *
4901
+ * @param {string | ParentNode | null} [html]
4902
+ * @returns {ParentNode}
4903
+ */
4904
+ function getHtmlContent(html) {
4905
+ if (!html) {
4906
+ return document.createElement('template').content;
4907
+ }
4908
+ if (typeof html !== 'string') {
4909
+ return /** @type {ParentNode} */(html.cloneNode(true));
4910
+ }
4911
+ const template = document.createElement('template');
4912
+ template.innerHTML = html;
4913
+ return template.content;
4914
+ }
4915
+
4916
+ /**
4917
+ *
4918
+ * @param {Store<any, any>} store
4919
+ * @param {StoreLayout.Renderer} fieldRenderer
4920
+ * @param {StoreLayout.Field?} layout
4921
+ * @param {StoreLayout.Options?} options
4922
+ * @returns {[ParentNode, () => void]}
4923
+ */
4924
+ function FormFieldInline(store, fieldRenderer, layout, options) {
4925
+ const { component } = store;
4926
+ return component
4927
+ && fieldRenderer(store, component, options)
4928
+ || [document.createElement('div'), () => { }];
4929
+ }
4930
+
4931
+ /** @import { Store } from '../Store/index.mjs' */
4932
+ /** @import { StoreLayout } from '../types.mjs' */
4933
+
4934
+ /**
4935
+ *
4936
+ * @param {Store<any, any>} store
4937
+ * @param {StoreLayout.Renderer} fieldRenderer
4938
+ * @param {StoreLayout.Field?} layout
4939
+ * @param {object} option
4940
+ * @param {StoreLayout.Column[]} option.columns
4941
+ * @param {() => void} option.remove
4942
+ * @param {() => void} option.dragenter
4943
+ * @param {() => void} option.dragstart
4944
+ * @param {() => void} option.dragend
4945
+ * @param {{get(): boolean}} option.deletable
4946
+ * @param {StoreLayout.Options?} options
4947
+
4948
+ * @returns {[HTMLTableSectionElement, () => void]}
4949
+ */
4950
+ function Line(store, fieldRenderer, layout, {
4951
+ columns,
4952
+ remove, dragenter, dragstart, dragend, deletable
4953
+ }, options) {
4954
+ const root = document.createElement('tbody');
4955
+ root.addEventListener('dragenter', () => {
4956
+ dragenter();
4957
+ });
4958
+ root.addEventListener('dragstart', (event) => {
4959
+ if (event.target !== event.currentTarget) { return; }
4960
+ dragstart();
4961
+ });
4962
+ root.addEventListener('dragend', dragend);
4963
+ const head = root.appendChild(document.createElement('tr'));
4964
+
4965
+ /** @type {(() => void)[]} */
4966
+ const destroyList = [];
4967
+
4968
+
4969
+ let trigger = () => { };
4970
+ /** @type {HTMLButtonElement[]} */
4971
+ const triggerList = [];
4972
+ if (columns.find(v => v.actions?.includes('trigger'))) {
4973
+ const body = root.appendChild(document.createElement('tr'));
4974
+ const main = body.appendChild(document.createElement('td'));
4975
+ main.colSpan = columns.length;
4976
+
4977
+ const [form, destroy] = Form(store, fieldRenderer, layout, options);
4978
+ main.appendChild(form);
4979
+ destroyList.push(destroy);
4980
+ body.hidden = true;
4981
+ trigger = function click() {
4982
+ if (body.hidden) {
4983
+ body.hidden = false;
4984
+ for (const ext of triggerList) {
4985
+ ext.classList.remove('NeeloongForm-table-line-open');
4986
+ ext.classList.add('NeeloongForm-table-line-close');
4987
+ }
4988
+ } else {
4989
+ body.hidden = true;
4990
+ for (const ext of triggerList) {
4991
+ ext.classList.remove('NeeloongForm-table-line-close');
4992
+ ext.classList.add('NeeloongForm-table-line-open');
4993
+ }
4994
+ }
4995
+ };
4996
+
4997
+ }
4998
+
4999
+ /**
5000
+ *
5001
+ * @param {PointerEvent} event
5002
+ */
5003
+ function pointerdown({ pointerId }) {
5004
+ root.draggable = true;
5005
+ /** @param {PointerEvent} event */
5006
+ function pointerup(event) {
5007
+ if (event.pointerId !== pointerId) { return; }
5008
+ if (!root) { return; }
5009
+ root.draggable = false;
5010
+ window.removeEventListener('pointerup', pointerup, { capture: true });
5011
+ window.removeEventListener('pointercancel', pointerup, { capture: true });
5012
+ }
5013
+ window.addEventListener('pointerup', pointerup, { capture: true });
5014
+ window.addEventListener('pointercancel', pointerup, { capture: true });
5015
+
5016
+ }
5017
+
5018
+ for (const name of columns) {
5019
+ const { actions, field, pattern } = name;
5020
+ if (!actions?.length) {
5021
+ const td = head.appendChild(document.createElement('td'));
5022
+ const child = field && store.child(field);
5023
+ if (child) {
5024
+ const [el, destroy] = FormFieldInline(child, fieldRenderer, null, options);
5025
+ destroyList.push(destroy);
5026
+ td.appendChild(el);
5027
+ }
5028
+ continue;
5029
+ }
5030
+ const handle = head.appendChild(document.createElement('th'));
5031
+ handle.classList.add('NeeloongForm-table-line-handle');
5032
+ for (const k of actions) {
5033
+ switch (k) {
5034
+ case 'trigger': {
5035
+ const ext = handle.appendChild(document.createElement('button'));
5036
+ ext.classList.add('NeeloongForm-table-line-open');
5037
+ triggerList.push(ext);
5038
+ ext.addEventListener('click', trigger);
5039
+ continue;
5040
+ }
5041
+ case 'move': {
5042
+ if (!options?.editable) { continue; }
5043
+ const move = handle.appendChild(document.createElement('button'));
5044
+ move.classList.add('NeeloongForm-table-move');
5045
+ move.addEventListener('pointerdown', pointerdown);
5046
+ destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5047
+ move.disabled = disabled;
5048
+ }, true));
5049
+ continue;
5050
+ }
5051
+ case 'remove': {
5052
+ if (!options?.editable) { continue; }
5053
+ const del = handle.appendChild(document.createElement('button'));
5054
+ del.classList.add('NeeloongForm-table-remove');
5055
+ del.addEventListener('click', remove);
5056
+ destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5057
+ del.disabled = disabled;
5058
+ }, true));
5059
+ continue;
5060
+ }
5061
+ case 'serial': {
5062
+ const serial = handle.appendChild(document.createElement('span'));
5063
+ serial.classList.add('NeeloongForm-table-serial');
5064
+ continue;
5065
+ }
5066
+ }
5067
+ }
5068
+ }
5069
+
5070
+ return [root, () => {
5071
+ for (const destroy of destroyList) {
5072
+ destroy();
5073
+ }
5074
+ }];
5075
+ }
5076
+
5077
+ /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5078
+ /** @import { StoreLayout } from '../types.mjs' */
5079
+
5080
+ /**
5081
+ *
5082
+ * @param {HTMLElement} parent
5083
+ * @param {StoreLayout.Column[]} columns
5084
+ * @param {() => any} add
5085
+ * @param {{get(): boolean}} addable
5086
+ * @param {boolean?} [editable]
5087
+ */
5088
+ function renderHead(parent, columns, add, addable, editable) {
5089
+ const tr = parent.appendChild(document.createElement('tr'));
5090
+ /** @type {(() => void)[]} */
5091
+ const destroyList = [];
5092
+ for (const { action, actions, width, label } of columns) {
5093
+ const th = tr.appendChild(document.createElement('th'));
5094
+ if (width) { th.setAttribute('width', `${width}`); }
5095
+ if (![action, actions].flat().includes('add')) {
5096
+ th.innerText = label || '';
5097
+ continue;
5312
5098
  }
5099
+ if (!editable) { continue; }
5100
+ const button = th.appendChild(document.createElement('button'));
5101
+ button.addEventListener('click', add);
5102
+ button.classList.add('NeeloongForm-table-add');
5103
+ destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5313
5104
  }
5314
5105
  return () => {
5315
5106
  for (const destroy of destroyList) {
5316
5107
  destroy();
5317
5108
  }
5318
5109
  };
5319
-
5320
5110
  }
5321
-
5322
5111
  /**
5323
- *
5324
- * @param {string | ParentNode | null} [html]
5325
- * @returns {ParentNode}
5112
+ *
5113
+ * @param {ArrayStore} store
5114
+ * @param {StoreLayout.Renderer} fieldRenderer
5115
+ * @param {StoreLayout.Field?} layout
5116
+ * @param {StoreLayout.Options?} options
5117
+ * @returns {[HTMLTableElement, () => void]}
5326
5118
  */
5327
- function getHtmlContent(html) {
5328
- if (!html) {
5329
- return document.createElement('template').content;
5119
+ function Table(store, fieldRenderer, layout, options) {
5120
+ const headerColumns = layout?.columns;
5121
+ const fieldList = Object.entries(store.type || {})
5122
+ .filter(([k, v]) => typeof v?.type !== 'object')
5123
+ .map(([field, { width, label }]) => ({ field, width, label }));
5124
+ /** @type {StoreLayout.Column[]} */
5125
+ let columns = [];
5126
+ if (Array.isArray(headerColumns)) {
5127
+ const map = new Map(fieldList.map(v => [v.field, v]));
5128
+
5129
+ /** @type {(StoreLayout.Column | null)[]} */
5130
+ const allColumns = headerColumns.map(v => {
5131
+ if (!v) { return null; }
5132
+ if (typeof v === 'number') { return { placeholder: v }; }
5133
+ if (typeof v === 'string') { return map.get(v) || null; }
5134
+ if (typeof v !== 'object') { return null; }
5135
+ if (Array.isArray(v)) {
5136
+ /** @type {Set<StoreLayout.Action>} */
5137
+ const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
5138
+ const actions = v.filter(v => options.delete(v));
5139
+ if (!actions) { return null; }
5140
+ return { actions };
5141
+ }
5142
+ const { action, actions, field, placeholder, pattern, width, label } = v;
5143
+ if (field) {
5144
+ const define = map.get(field);
5145
+ if (define) {
5146
+ return { field, placeholder, width, label: label || define.label };
5147
+ }
5148
+ }
5149
+ const options = new Set(['add', 'move', 'trigger', 'remove', 'serial']);
5150
+ const allActions = [action, actions].flat().filter(v => v && options.delete(v));
5151
+ if (allActions.length) {
5152
+ return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label };
5153
+ }
5154
+ // if (pattern) {
5155
+ // return { pattern, placeholder, width, label };
5156
+ // }
5157
+ return null;
5158
+ });
5159
+ columns = /** @type {StoreLayout.Column[]} */(allColumns.filter(Boolean));
5160
+
5330
5161
  }
5331
- if (typeof html !== 'string') {
5332
- return /** @type {ParentNode} */(html.cloneNode(true));
5162
+ if (!columns.length) {
5163
+ columns = [
5164
+ { actions: ['add', 'trigger', 'move', 'remove', 'serial'] },
5165
+ ...fieldList.slice(0, 3),
5166
+ ];
5333
5167
  }
5334
- const template = document.createElement('template');
5335
- template.innerHTML = html;
5336
- return template.content;
5168
+
5169
+ const table = document.createElement('table');
5170
+ table.className = 'NeeloongForm-table';
5171
+ const thead = table.appendChild(document.createElement('thead'));
5172
+
5173
+ const addable = new Signal.Computed(() => store.addable);
5174
+ const deletable = { get: () => Boolean(options?.editable) };
5175
+ function add() {
5176
+ const data = {};
5177
+ store.add(data);
5178
+ }
5179
+ /**
5180
+ *
5181
+ * @param {Store} child
5182
+ */
5183
+ function remove(child) {
5184
+ store.remove(Number(child.index));
5185
+ }
5186
+ let dragRow = -1;
5187
+ /**
5188
+ *
5189
+ * @param {Store} [child]
5190
+ */
5191
+ function dragenter(child) {
5192
+ if (dragRow < 0) { return; }
5193
+ const index = child ? Number(child.index) : store.children.length;
5194
+ if (index < 0 || dragRow < 0 || dragRow === index) { return; }
5195
+ if (store.move(dragRow, index)) {
5196
+ dragRow = index;
5197
+ }
5198
+ }
5199
+ /**
5200
+ *
5201
+ * @param {Store} child
5202
+ */
5203
+ function dragstart(child) {
5204
+ dragRow = Number(child.index);
5205
+
5206
+ }
5207
+ function dragend() {
5208
+ dragRow = -1;
5209
+
5210
+ }
5211
+ /** @type {(() => void)[]} */
5212
+ const destroyList = [];
5213
+ destroyList.push(renderHead(thead, columns, add, addable, Boolean(options?.editable)));
5214
+ switch (layout?.tableFoot) {
5215
+ default:
5216
+ case 'header': {
5217
+ const tfoot = table.appendChild(document.createElement('tfoot'));
5218
+ tfoot.addEventListener('dragenter', () => { dragenter(); });
5219
+ destroyList.push(renderHead(tfoot, columns, add, addable, Boolean(options?.editable)));
5220
+ break;
5221
+ }
5222
+ case 'add': {
5223
+ const tfoot = table.appendChild(document.createElement('tfoot'));
5224
+ tfoot.addEventListener('dragenter', () => { dragenter(); });
5225
+ const tr = tfoot.appendChild(document.createElement('tr'));
5226
+ const th = tr.appendChild(document.createElement('th'));
5227
+ th.colSpan = columns.length;
5228
+ const button = th.appendChild(document.createElement('button'));
5229
+ button.addEventListener('click', add);
5230
+ button.classList.add('NeeloongForm-table-foot-add');
5231
+ destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5232
+ break;
5233
+ }
5234
+ case 'none':
5235
+ }
5236
+ const start = thead;
5237
+ /** @type {Map<Store, [HTMLTableSectionElement, () => void]>} */
5238
+ let seMap = new Map();
5239
+ /** @param {Map<Store, [tbody: HTMLTableSectionElement, destroy: () => void]>} map */
5240
+ function destroyMap(map) {
5241
+ for (const [el, destroy] of map.values()) {
5242
+ destroy();
5243
+ el.remove();
5244
+ }
5245
+
5246
+ }
5247
+ const childrenResult = watch(() => store.children, function render(children) {
5248
+ let nextNode = thead.nextSibling;
5249
+ const oldSeMap = seMap;
5250
+ seMap = new Map();
5251
+ for (let child of children) {
5252
+ const old = oldSeMap.get(child);
5253
+ if (!old) {
5254
+ const [el, destroy] = Line(child, fieldRenderer, layout, {
5255
+ columns,
5256
+ remove: remove.bind(null, child),
5257
+ dragenter: dragenter.bind(null, child),
5258
+ dragstart: dragstart.bind(null, child),
5259
+ dragend,
5260
+ deletable,
5261
+ }, options);
5262
+ table.insertBefore(el, nextNode);
5263
+ seMap.set(child, [el, destroy]);
5264
+ continue;
5265
+ }
5266
+ oldSeMap.delete(child);
5267
+ seMap.set(child, old);
5268
+ if (nextNode === old[0]) {
5269
+ nextNode = nextNode.nextSibling;
5270
+ continue;
5271
+ }
5272
+ table.insertBefore(old[0], nextNode);
5273
+ }
5274
+ destroyMap(oldSeMap);
5275
+ }, true);
5276
+
5277
+ return [table, () => {
5278
+ start.remove();
5279
+ thead.remove();
5280
+ destroyMap(seMap);
5281
+ childrenResult();
5282
+ for (const destroy of destroyList) {
5283
+ destroy();
5284
+ }
5285
+ }];
5337
5286
  }
5338
5287
 
5339
5288
  /** @import { StoreLayout } from '../types.mjs' */
@@ -5579,7 +5528,7 @@ function createCell(layout, values, defCell, blockOnly) {
5579
5528
  * @param {StoreLayout.Field?} layout
5580
5529
  * @param {State} initState
5581
5530
  * @param {object} option
5582
- * @param {(string | number | StoreLayout.Action[])[]} option.columns
5531
+ * @param {StoreLayout.Column[]} option.columns
5583
5532
  * @param {() => void} option.remove
5584
5533
  * @param {(el: HTMLElement) => () => void} option.dragenter
5585
5534
  * @param {() => void} option.dragstart
@@ -5674,38 +5623,40 @@ function TreeLine(
5674
5623
  dropChildren.addEventListener('dragover', (e) => e.preventDefault());
5675
5624
  dropFront.addEventListener('drop', () => drop());
5676
5625
  dropChildren.addEventListener('drop', () => drop(true));
5677
- destroyList.push(effect(() => {
5626
+ destroyList.push(effect(() => {
5678
5627
  dropFront.hidden = dropChildren.hidden = !state.get().droppable;
5679
- }));
5628
+ }));
5680
5629
 
5681
- let dragleave = () => {};
5630
+ let dragleave = () => { };
5682
5631
  dropFront.addEventListener('dragenter', () => dragleave = dragenter(dropFront));
5683
5632
  dropChildren.addEventListener('dragenter', () => dragleave = dragenter(dropChildren));
5684
5633
  dropFront.addEventListener('dragleave', () => dragleave());
5685
5634
  dropChildren.addEventListener('dragleave', () => dragleave());
5686
5635
 
5687
- for (const name of columns) {
5688
- if (typeof name === 'number') {
5689
- const td = line.appendChild(document.createElement('div'));
5690
- td.classList.add('NeeloongForm-tree-placeholder');
5691
- td.style.flex = `${name}`;
5692
- if (click) { td.addEventListener('click', click); }
5693
- if (moveStart) { td.addEventListener('pointerdown', moveStart); }
5694
- continue;
5695
- }
5696
- if (!Array.isArray(name)) {
5636
+ for (const { actions, pattern, placeholder, width, field } of columns) {
5637
+ if (!actions?.length) {
5697
5638
  const td = line.appendChild(document.createElement('div'));
5698
5639
  td.classList.add('NeeloongForm-tree-cell');
5699
- const child = store.child(name);
5700
- if (!child) { continue; }
5701
- const [el, destroy] = FormField(child, fieldRenderer, null, { ...options, editable: false }, true);
5702
- destroyList.push(destroy);
5703
- td.appendChild(el);
5704
5640
  if (click) { td.addEventListener('click', click); }
5705
5641
  if (moveStart) { td.addEventListener('pointerdown', moveStart); }
5642
+ if (field) {
5643
+ const child = store.child(field);
5644
+ if (!child) { continue; }
5645
+ const [el, destroy] = FormFieldInline(child, fieldRenderer, null, { ...options, editable: false });
5646
+ destroyList.push(destroy);
5647
+ td.appendChild(el);
5648
+ continue;
5649
+ }
5650
+ if (typeof placeholder === 'number') {
5651
+ td.style.flex = `${placeholder}`;
5652
+ }
5653
+ if (typeof width === 'number') {
5654
+ td.style.width = `${width}px`;
5655
+ }
5706
5656
  continue;
5657
+
5707
5658
  }
5708
- for (const k of name) {
5659
+ for (const k of actions) {
5709
5660
  switch (k) {
5710
5661
  case 'trigger': {
5711
5662
  const btn = line.appendChild(document.createElement('button'));
@@ -5912,28 +5863,54 @@ function Tree(store, fieldRenderer, layout, options) {
5912
5863
  const fieldList = Object.entries(store.type || {})
5913
5864
  .filter(([k, v]) => typeof v?.type !== 'object')
5914
5865
  .map(([field, { width, label }]) => ({ field, width, label }));
5915
- /** @type {({ field: string; width: any; label: any; } | number | StoreLayout.Action[])[]} */
5866
+ /** @type {StoreLayout.Column[]} */
5916
5867
  let columns = [];
5917
5868
  if (Array.isArray(headerColumns)) {
5918
5869
  const map = new Map(fieldList.map(v => [v.field, v]));
5919
- columns = headerColumns.map(v => {
5920
- if (typeof v === 'number') { return v; }
5921
- if (typeof v === 'string') { return map.get(v) || []; }
5922
- if (!Array.isArray(v)) { return []; }
5923
- /** @type {Set<StoreLayout.Action>} */
5870
+ /** @type {(StoreLayout.Column | null)[]} */
5871
+ const allColumns = headerColumns.map(v => {
5872
+ if (!v) { return null; }
5873
+ if (typeof v === 'number') { return { placeholder: v }; }
5874
+ if (typeof v === 'string') { return map.get(v) || null; }
5875
+ if (typeof v !== 'object') { return null; }
5876
+ if (Array.isArray(v)) {
5877
+ /** @type {Set<StoreLayout.Action>} */
5878
+ const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
5879
+ const actions = v.filter(v => options.delete(v));
5880
+ if (!actions) { return null; }
5881
+ return { actions };
5882
+ }
5883
+ const { action, actions, field, placeholder, pattern, width, label } = v;
5884
+ if (field) {
5885
+ const define = map.get(field);
5886
+ if (define) {
5887
+ return { field, placeholder, width, label: label || define.label };
5888
+ }
5889
+ }
5924
5890
  const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
5925
- return v.filter(v => options.delete(v));
5926
- }).filter(v => !Array.isArray(v) || v.length);
5891
+ const allActions = [action, actions].flat().filter(v => v && options.delete(v));
5892
+ if (allActions.length) {
5893
+ return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label };
5894
+ }
5895
+ if (pattern) {
5896
+ return { pattern, placeholder, width, label };
5897
+ }
5898
+ if (placeholder || width) {
5899
+ return { placeholder, width, label };
5900
+ }
5901
+ return null;
5902
+ });
5903
+ columns = /** @type {StoreLayout.Column[]} */(allColumns.filter(Boolean));
5927
5904
  }
5928
5905
  if (!columns.length) {
5929
- columns = [['collapse', 'move'], fieldList[0], ['add', 'remove']];
5930
- }
5931
- if (!columns.find(v => !Array.isArray(v))) {
5932
- columns.push(...fieldList.slice(0, 3));
5906
+ columns = [
5907
+ { actions: ['collapse', 'move'] },
5908
+ fieldList[0],
5909
+ { actions: ['add', 'remove'] },
5910
+ ];
5933
5911
  }
5934
5912
 
5935
5913
 
5936
-
5937
5914
  const root = document.createElement('div');
5938
5915
  root.className = 'NeeloongForm-tree';
5939
5916
  const main = root.appendChild(document.createElement('div'));
@@ -6203,7 +6180,6 @@ function Tree(store, fieldRenderer, layout, options) {
6203
6180
  }
6204
6181
  /** @type {State[]} */
6205
6182
  const states = [];
6206
- const columnNames = columns.map((v) => Array.isArray(v) || typeof v === 'number' ? v : v.field);
6207
6183
  const childrenResult = watch(() => store.children, function render(children) {
6208
6184
  let nextNode = start.nextSibling;
6209
6185
  const oldSeMap = seMap;
@@ -6220,7 +6196,7 @@ function Tree(store, fieldRenderer, layout, options) {
6220
6196
  const old = oldSeMap.get(child);
6221
6197
  if (!old) {
6222
6198
  const [el, destroy, setState] = TreeLine(child, detailsStore, fieldRenderer, layout, state, {
6223
- columns: columnNames,
6199
+ columns,
6224
6200
  remove: remove.bind(null, child),
6225
6201
  dragenter,
6226
6202
  dragstart: dragstart.bind(null, child),
@@ -6291,22 +6267,10 @@ function getArrayCell(arrayStyle) {
6291
6267
  * @param {StoreLayout.Renderer} fieldRenderer
6292
6268
  * @param {StoreLayout.Field?} layout
6293
6269
  * @param {StoreLayout.Options?} options
6294
- * @param {boolean} [inline]
6295
6270
  * @returns {[ParentNode, () => void]}
6296
6271
  */
6297
- function FormField(store, fieldRenderer, layout, options, inline = false) {
6272
+ function FormField(store, fieldRenderer, layout, options) {
6298
6273
  const { type, component } = store;
6299
- if (inline) {
6300
- const html = layout?.inlineHtml;
6301
- if (html) {
6302
- const content = getHtmlContent(html);
6303
- const destroy = renderHtml(store, fieldRenderer, content, options, layout);
6304
- return [content, destroy];
6305
- }
6306
- return component
6307
- && fieldRenderer(store, component, options)
6308
- || [document.createElement('div'), () => { }];
6309
- }
6310
6274
  const isObject = type && typeof type === 'object';
6311
6275
  const html = layout?.html;
6312
6276
  /** @type {StoreLayout.Grid['cell']} */