@neeloong/form 0.19.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.19.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
 
@@ -85,7 +85,7 @@ const values = v => {
85
85
  * @param {T | ((store: Store) => T?) | null} [fn]
86
86
  * @returns {[Signal.State<T?>, Signal.Computed<T?>]}
87
87
  */
88
- function createState(self, toValue, defState, fn) {
88
+ function createState$1(self, toValue, defState, fn) {
89
89
 
90
90
  const selfState = new Signal.State(toValue(defState));
91
91
  /** @type {Signal.Computed<T>} */
@@ -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())
@@ -466,17 +462,17 @@ class Store {
466
462
  [this.#selfRequired, this.#required] = createBooleanStates(this, required, schema.required, parent ? parent.#required : null);
467
463
  [this.#selfDisabled, this.#disabled] = createBooleanStates(this, disabled, schema.disabled, parent ? parent.#disabled : null);
468
464
 
469
- [this.#selfLabel, this.#label] = createState(this, string, label, schema.label);
470
- [this.#selfDescription, this.#description] = createState(this, string, description, schema.description);
471
- [this.#selfPlaceholder, this.#placeholder] = createState(this, string, placeholder, schema.placeholder);
472
- [this.#selfMin, this.#min] = createState(this, number, min, schema.min);
473
- [this.#selfMax, this.#max] = createState(this, number, max, schema.max);
474
- [this.#selfStep, this.#step] = createState(this, number, step, schema.step);
475
- [this.#selfMinLength, this.#minLength] = createState(this, number, minLength, schema.minLength);
476
- [this.#selfMaxLength, this.#maxLength] = createState(this, number, maxLength, schema.maxLength);
477
- [this.#selfPattern, this.#pattern] = createState(this, regex, pattern, schema.pattern);
465
+ [this.#selfLabel, this.#label] = createState$1(this, string, label, schema.label);
466
+ [this.#selfDescription, this.#description] = createState$1(this, string, description, schema.description);
467
+ [this.#selfPlaceholder, this.#placeholder] = createState$1(this, string, placeholder, schema.placeholder);
468
+ [this.#selfMin, this.#min] = createState$1(this, number, min, schema.min);
469
+ [this.#selfMax, this.#max] = createState$1(this, number, max, schema.max);
470
+ [this.#selfStep, this.#step] = createState$1(this, number, step, schema.step);
471
+ [this.#selfMinLength, this.#minLength] = createState$1(this, number, minLength, schema.minLength);
472
+ [this.#selfMaxLength, this.#maxLength] = createState$1(this, number, maxLength, schema.maxLength);
473
+ [this.#selfPattern, this.#pattern] = createState$1(this, regex, pattern, schema.pattern);
478
474
  // @ts-ignore
479
- [this.#selfValues, this.#values] = createState(this, values, values$1, schema.values);
475
+ [this.#selfValues, this.#values] = createState$1(this, values, values$1, schema.values);
480
476
 
481
477
  [this.#selfRemovable, this.#removable] = createBooleanStates(this, removable, schema.removable ?? true);
482
478
 
@@ -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
  }
@@ -1043,11 +1008,11 @@ setObjectStore(ObjectStore);
1043
1008
  */
1044
1009
  class ArrayStore extends Store {
1045
1010
  /** @type {(index: number, isNew?: boolean) => Store} */
1046
- #create = () => {throw new Error}
1011
+ #create = () => { throw new Error; };
1047
1012
  /** @type {Signal.State<Store[]>} */
1048
- #children
1013
+ #children;
1049
1014
  get children() { return [...this.#children.get()]; }
1050
- *[Symbol.iterator]() { return yield*[...this.#children.get().entries()]; }
1015
+ *[Symbol.iterator]() { return yield* [...this.#children.get().entries()]; }
1051
1016
  /**
1052
1017
  *
1053
1018
  * @param {string | number} key
@@ -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) => {
@@ -1079,7 +1043,7 @@ class ArrayStore extends Store {
1079
1043
  const children = [...childrenState.get()];
1080
1044
  const oldLength = children.length;
1081
1045
  for (let i = children.length; i < length; i++) {
1082
- children.push(this.#create(i));
1046
+ children.push(this.#create(i));
1083
1047
  }
1084
1048
  children.length = length;
1085
1049
  if (oldLength !== length) {
@@ -1090,33 +1054,27 @@ 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
- 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) {
1057
+ setValue(v) { return Array.isArray(v) ? v : v == null ? null : [v]; },
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
 
1112
1070
  [this.#selfAddable, this.#addable] = createBooleanStates(this, addable, schema.addable ?? true);
1113
-
1071
+
1114
1072
  this.#children = childrenState;
1115
1073
  const childCommonOptions = {
1116
1074
  parent: this,
1117
1075
  /** @param {*} value @param {*} index @param {Store} store */
1118
1076
  onUpdate: (value, index, store) => {
1119
- if (childrenState.get()[index] !== store) { return;}
1077
+ if (childrenState.get()[index] !== store) { return; }
1120
1078
  const val = [...this.value || []];
1121
1079
  if (val.length < index) {
1122
1080
  val.length = index;
@@ -1124,29 +1082,19 @@ 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
- this.#create = (index, isNew) => {
1139
- const child = create(schema, {...childCommonOptions, index, new: isNew });
1086
+ this.#create = (index, isNew) => {
1087
+ const child = create(schema, { ...childCommonOptions, index, new: isNew });
1140
1088
  child.index = index;
1141
- return child
1089
+ return child;
1142
1090
  };
1143
1091
  }
1144
1092
 
1145
1093
 
1146
1094
  /** @readonly @type {Signal.State<boolean?>} */
1147
- #selfAddable
1095
+ #selfAddable;
1148
1096
  /** @readonly @type {Signal.Computed<boolean>} */
1149
- #addable
1097
+ #addable;
1150
1098
  get selfAddable() { return this.#selfAddable.get(); }
1151
1099
  set selfAddable(v) { this.#selfAddable.set(typeof v === 'boolean' ? v : null); }
1152
1100
  /** 是否禁用字段 */
@@ -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;
@@ -1226,37 +1162,39 @@ class ArrayStore extends Store {
1226
1162
  *
1227
1163
  * @param {number} from
1228
1164
  * @param {number} to
1165
+ * @param {number} quantity
1229
1166
  * @returns
1230
1167
  */
1231
- move(from, to) {
1168
+ move(from, to, quantity = 1) {
1169
+ const q = Math.floor(quantity);
1170
+ if (q < 1) { return 0; }
1171
+ if (from <= to && from + q > to) { return 0; }
1172
+
1232
1173
  const data = this.value;
1233
- if (!Array.isArray(data)) { return false; }
1174
+ if (!Array.isArray(data)) { return 0; }
1175
+
1234
1176
  const children = [...this.#children.get()];
1235
- const [item] = children.splice(from, 1);
1236
- if (!item) { return false; }
1237
- children.splice(to, 0, item);
1238
- let lft = Math.min(from, to);
1239
- let rgt = Math.max(from, to);
1177
+ const list = children.splice(from, q);
1178
+ const len = list.length;
1179
+ if (!len) { return 0; }
1180
+ const toIndex = q > 1 && to > from ? to - q + 1 : to;
1181
+ children.splice(toIndex, 0, ...list);
1182
+
1183
+ let lft = Math.min(from, toIndex);
1184
+ let rgt = Math.max(from + q - 1, to);
1240
1185
  for (let i = lft; i <= rgt; i++) {
1241
1186
  children[i].index = i;
1242
1187
  }
1188
+
1243
1189
  const val = [...data];
1244
- const [value] = val.splice(from, 1);
1245
- val.splice(to, 0, value);
1246
- const state = this.state;
1247
- if (Array.isArray(state)) {
1248
- const sta = [...state];
1249
- const [value = {}] = sta.splice(from, 1);
1250
- if (to <= sta.length) {
1251
- sta.splice(to, 0, value);
1252
- } else {
1253
- sta[to] = value;
1254
- }
1255
- this.state = sta;
1256
- }
1190
+ const values = val.splice(from, len);
1191
+ for (let i = values.length; i < len; i++) { values.push(null); }
1192
+ while (toIndex > val.length) { val.push(null); }
1193
+ val.splice(toIndex, 0, ...values);
1194
+
1257
1195
  this.#children.set(children);
1258
1196
  this.value = val;
1259
- return true;
1197
+ return len;
1260
1198
 
1261
1199
  }
1262
1200
  /**
@@ -1281,15 +1219,6 @@ class ArrayStore extends Store {
1281
1219
  const bValue = val[b];
1282
1220
  val[b] = aValue;
1283
1221
  val[a] = bValue;
1284
- const state = this.state;
1285
- if (Array.isArray(state)) {
1286
- const sta = [...state];
1287
- const aValue = sta[a];
1288
- const bValue = sta[b];
1289
- sta[b] = aValue;
1290
- sta[a] = bValue;
1291
- this.state = sta;
1292
- }
1293
1222
  this.#children.set(children);
1294
1223
  this.value = val;
1295
1224
  return true;
@@ -2438,7 +2367,6 @@ const bindable = {
2438
2367
  kind: true,
2439
2368
 
2440
2369
  value: true,
2441
- state: true,
2442
2370
 
2443
2371
  store: true,
2444
2372
  parent: true,
@@ -2496,7 +2424,8 @@ function *toItem(val, key = '', sign = '$') {
2496
2424
  yield [`${key}${sign}${k}`, {get: () => val[k]}];
2497
2425
  }
2498
2426
  yield [`${key}${sign}value`, {get: () => val.value, set: v => val.value = v}];
2499
- yield [`${key}${sign}state`, {get: () => val.state, set: v => val.state = v}];
2427
+ /** @deprecated */
2428
+ yield [`${key}${sign}state`, {get: () => null, set: v => {}}];
2500
2429
  yield [`${key}${sign}reset`, {exec: () => val.reset()}];
2501
2430
  // @ts-ignore
2502
2431
  yield [`${key}${sign}validate`, {exec: v => val.validate(v ? [] : null)}];
@@ -2784,6 +2713,8 @@ class Environment {
2784
2713
  const res = Object.fromEntries([...bindableSet].map(v => [
2785
2714
  `$${v}`, cb => watch(() => store[v], cb, true)
2786
2715
  ]));
2716
+ /** @deprecated */
2717
+ res.$$state = cb => watch(() => null, cb, true);
2787
2718
  return res;
2788
2719
  }
2789
2720
  /**
@@ -2800,13 +2731,15 @@ class Environment {
2800
2731
  }
2801
2732
  /** @type {Record<string, {get(): any; set?(v: any): void}> | void} */
2802
2733
  const res = Object.fromEntries([...bindableSet].map(v => [
2803
- `$${v}`, v === 'value' || v === 'state' ? {
2734
+ `$${v}`, v === 'value' ? {
2804
2735
  get: () => store[v],
2805
2736
  set: (s)=>{store[v] = s;}
2806
2737
  } : {
2807
2738
  get: () => store[v],
2808
2739
  }
2809
2740
  ]));
2741
+ /** @deprecated */
2742
+ res.$$state = { get: () => null, set: () => {} };
2810
2743
  return res;
2811
2744
  }
2812
2745
  /**
@@ -2821,7 +2754,8 @@ class Environment {
2821
2754
  if (!store) { return; }
2822
2755
  switch(type) {
2823
2756
  case 'value': return v => {store.value = v; };
2824
- case 'state': return v => {store.state = v; };
2757
+ /** @deprecated */
2758
+ case 'state': return v => { };
2825
2759
  }
2826
2760
  }
2827
2761
  /**
@@ -2839,7 +2773,8 @@ class Environment {
2839
2773
  }
2840
2774
  return {
2841
2775
  '$value': v => {store.value = v; },
2842
- '$state': v => {store.state = v; },
2776
+ /** @deprecated */
2777
+ '$state': v => { },
2843
2778
  '$input': v => {store.emit('input', v); },
2844
2779
  '$change': v => {store.emit('change', v); },
2845
2780
  '$click': v => {store.emit('click', v); },
@@ -4734,425 +4669,80 @@ function render(store, layouts, parent, { component, global, relate, enhancement
4734
4669
  /** @import { Store } from '../Store/index.mjs' */
4735
4670
  /** @import { StoreLayout } from '../types.mjs' */
4736
4671
 
4672
+
4737
4673
  /**
4738
4674
  *
4739
- * @param {Store<any, any>} store
4740
- * @param {StoreLayout.Renderer} fieldRenderer
4741
- * @param {StoreLayout.Field?} layout
4742
- * @param {object} option
4743
- * @param {(string | StoreLayout.Action[])[]} option.columns
4744
- * @param {() => void} option.remove
4745
- * @param {() => void} option.dragenter
4746
- * @param {() => void} option.dragstart
4747
- * @param {() => void} option.dragend
4748
- * @param {{get(): boolean}} option.deletable
4749
- * @param {StoreLayout.Options?} options
4750
-
4751
- * @returns {[HTMLTableSectionElement, () => void]}
4675
+ * @param {string} field
4752
4676
  */
4753
- function Line(store, fieldRenderer, layout, {
4754
- columns,
4755
- remove, dragenter, dragstart, dragend, deletable
4756
- }, options) {
4757
- const root = document.createElement('tbody');
4758
- root.addEventListener('dragenter', () => {
4759
- dragenter();
4760
- });
4761
- root.addEventListener('dragstart', (event) => {
4762
- if (event.target !== event.currentTarget) { return; }
4763
- dragstart();
4764
- });
4765
- root.addEventListener('dragend', dragend);
4766
- const head = root.appendChild(document.createElement('tr'));
4767
-
4768
- /** @type {(() => void)[]} */
4769
- const destroyList = [];
4770
-
4771
-
4772
- let trigger = () => { };
4773
- /** @type {HTMLButtonElement[]} */
4774
- const triggerList = [];
4775
- if (columns.find(v => Array.isArray(v) && v.includes('trigger'))) {
4776
- const body = root.appendChild(document.createElement('tr'));
4777
- const main = body.appendChild(document.createElement('td'));
4778
- main.colSpan = columns.length;
4779
-
4780
- const [form, destroy] = Form(store, fieldRenderer, layout, options);
4781
- main.appendChild(form);
4782
- destroyList.push(destroy);
4783
- body.hidden = true;
4784
- trigger = function click() {
4785
- if (body.hidden) {
4786
- body.hidden = false;
4787
- for (const ext of triggerList) {
4788
- ext.classList.remove('NeeloongForm-table-line-open');
4789
- ext.classList.add('NeeloongForm-table-line-close');
4790
- }
4791
- } else {
4792
- body.hidden = true;
4793
- for (const ext of triggerList) {
4794
- ext.classList.remove('NeeloongForm-table-line-close');
4795
- ext.classList.add('NeeloongForm-table-line-open');
4796
- }
4797
- }
4798
- };
4799
-
4800
- }
4677
+ function createFieldFilter(field) {
4801
4678
 
4802
4679
  /**
4803
4680
  *
4804
- * @param {PointerEvent} event
4681
+ * @param {StoreLayout.Item} v
4682
+ * @returns {v is StoreLayout.Field}
4805
4683
  */
4806
- function pointerdown({ pointerId }) {
4807
- root.draggable = true;
4808
- /** @param {PointerEvent} event */
4809
- function pointerup(event) {
4810
- if (event.pointerId !== pointerId) { return; }
4811
- if (!root) { return; }
4812
- root.draggable = false;
4813
- window.removeEventListener('pointerup', pointerup, { capture: true });
4814
- window.removeEventListener('pointercancel', pointerup, { capture: true });
4815
- }
4816
- window.addEventListener('pointerup', pointerup, { capture: true });
4817
- window.addEventListener('pointercancel', pointerup, { capture: true });
4818
-
4819
- }
4820
4684
 
4821
- for (const name of columns) {
4822
- if (!Array.isArray(name)) {
4823
- const td = head.appendChild(document.createElement('td'));
4824
- const child = store.child(name);
4825
- if (!child) { continue; }
4826
- const [el, destroy] = FormField(child, fieldRenderer, null, options, true);
4827
- destroyList.push(destroy);
4828
- td.appendChild(el);
4829
- continue;
4830
- }
4831
- const handle = head.appendChild(document.createElement('th'));
4832
- handle.classList.add('NeeloongForm-table-line-handle');
4833
- for (const k of name) {
4834
- switch (k) {
4835
- case 'trigger': {
4836
- const ext = handle.appendChild(document.createElement('button'));
4837
- ext.classList.add('NeeloongForm-table-line-open');
4838
- triggerList.push(ext);
4839
- ext.addEventListener('click', trigger);
4840
- continue;
4841
- }
4842
- case 'move': {
4843
- if (!options?.editable) { continue; }
4844
- const move = handle.appendChild(document.createElement('button'));
4845
- move.classList.add('NeeloongForm-table-move');
4846
- move.addEventListener('pointerdown', pointerdown);
4847
- destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
4848
- move.disabled = disabled;
4849
- }, true));
4850
- continue;
4851
- }
4852
- case 'remove': {
4853
- if (!options?.editable) { continue; }
4854
- const del = handle.appendChild(document.createElement('button'));
4855
- del.classList.add('NeeloongForm-table-remove');
4856
- del.addEventListener('click', remove);
4857
- destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
4858
- del.disabled = disabled;
4859
- }, true));
4860
- continue;
4861
- }
4862
- case 'serial': {
4863
- const serial = handle.appendChild(document.createElement('span'));
4864
- serial.classList.add('NeeloongForm-table-serial');
4865
- continue;
4866
- }
4867
- }
4868
- }
4869
- }
4685
+ return function (v) {
4686
+ if (v.type && v.type !== 'field') { return false; }
4687
+ return v.field === field;
4870
4688
 
4871
- return [root, () => {
4872
- for (const destroy of destroyList) {
4873
- destroy();
4874
- }
4875
- }];
4689
+ };
4876
4690
  }
4877
-
4878
- /** @import { Store, ArrayStore } from '../Store/index.mjs' */
4879
- /** @import { StoreLayout } from '../types.mjs' */
4880
-
4881
4691
  /**
4882
4692
  *
4883
- * @param {HTMLElement} parent
4884
- * @param {({ field: string; width: any; label: any; } | StoreLayout.Action[])[]} columns
4885
- * @param {() => any} add
4886
- * @param {{get(): boolean}} addable
4887
- * @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]
4888
4700
  */
4889
- function renderHead(parent, columns, add, addable, editable) {
4890
- const tr = parent.appendChild(document.createElement('tr'));
4701
+ function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragenter) {
4891
4702
  /** @type {(() => void)[]} */
4892
4703
  const destroyList = [];
4893
- for (const col of columns) {
4894
- if (!Array.isArray(col)) {
4895
- const { width, label } = col;
4896
- const td = tr.appendChild(document.createElement('th'));
4897
- if (width) {
4898
- 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;
4899
4727
  }
4900
- td.innerText = label;
4901
- continue;
4902
- }
4903
- const th = tr.appendChild(document.createElement('th'));
4904
- if (!editable) { continue; }
4905
- for (const it of col) {
4906
- switch (it) {
4907
- case 'add':
4908
- const button = th.appendChild(document.createElement('button'));
4909
- button.addEventListener('click', add);
4910
- button.classList.add('NeeloongForm-table-add');
4911
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
4912
- continue;
4913
4728
  }
4729
+ const value = node.getAttribute('placeholder') || '';
4730
+ node.replaceWith(document.createTextNode(value));
4731
+ return () => { };
4914
4732
  }
4915
- }
4916
- return () => {
4917
- for (const destroy of destroyList) {
4918
- destroy();
4919
- }
4920
- };
4921
- }
4922
- /**
4923
- *
4924
- * @param {ArrayStore} store
4925
- * @param {StoreLayout.Renderer} fieldRenderer
4926
- * @param {StoreLayout.Field?} layout
4927
- * @param {StoreLayout.Options?} options
4928
- * @returns {[HTMLTableElement, () => void]}
4929
- */
4930
- function Table(store, fieldRenderer, layout, options) {
4931
- const headerColumns = layout?.columns;
4932
- const fieldList = Object.entries(store.type || {})
4933
- .filter(([k, v]) => typeof v?.type !== 'object')
4934
- .map(([field, {width, label}]) => ({field, width, label}));
4935
- /** @type {({ field: string; width: any; label: any; } | StoreLayout.Action[])[]} */
4936
- let columns = [];
4937
- if (Array.isArray(headerColumns)) {
4938
- const map = new Map(fieldList.map(v => [v.field, v]));
4939
- columns = headerColumns.map(v => {
4940
- if (typeof v === 'string') { return map.get(v) || [] }
4941
- if (!Array.isArray(v)) { return []; }
4942
- /** @type {Set<StoreLayout.Action>} */
4943
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial']);
4944
- return v.filter(v => options.delete(v));
4945
- }).filter(v => !Array.isArray(v) || v.length);
4946
- }
4947
- if (!columns.length) {
4948
- columns = [['add', 'trigger', 'move', 'remove', 'serial']];
4949
- }
4950
- if (!columns.find(v => !Array.isArray(v))) {
4951
- columns.push(...fieldList.slice(0, 3));
4952
- }
4953
-
4954
-
4955
-
4956
- const table = document.createElement('table');
4957
- table.className = 'NeeloongForm-table';
4958
- const thead = table.appendChild(document.createElement('thead'));
4959
-
4960
-
4961
-
4962
- const addable = new Signal.Computed(() => store.addable);
4963
- const deletable = { get: () => Boolean(options?.editable) };
4964
- function add() {
4965
- const data = {};
4966
- store.add(data);
4967
-
4968
- }
4969
- /**
4970
- *
4971
- * @param {Store} child
4972
- */
4973
- function remove(child) {
4974
- store.remove(Number(child.index));
4975
- }
4976
- let dragRow = -1;
4977
- /**
4978
- *
4979
- * @param {Store} [child]
4980
- */
4981
- function dragenter(child) {
4982
- if (dragRow < 0) { return; }
4983
- const index = child ? Number(child.index) : store.children.length;
4984
- if (index < 0 || dragRow < 0 || dragRow === index) { return; }
4985
- if (store.move(dragRow, index)) {
4986
- dragRow = index;
4987
- }
4988
- }
4989
- /**
4990
- *
4991
- * @param {Store} child
4992
- */
4993
- function dragstart(child) {
4994
- dragRow = Number(child.index);
4995
-
4996
- }
4997
- function dragend() {
4998
- dragRow = -1;
4999
-
5000
- }
5001
- /** @type {(() => void)[]} */
5002
- const destroyList = [];
5003
- destroyList.push(renderHead(thead, columns, add, addable, Boolean(options?.editable)));
5004
- switch (layout?.tableFoot) {
5005
- default:
5006
- case 'header': {
5007
- const tfoot = table.appendChild(document.createElement('tfoot'));
5008
- tfoot.addEventListener('dragenter', () => { dragenter(); });
5009
- destroyList.push(renderHead(tfoot, columns, add, addable, Boolean(options?.editable)));
5010
- break;
5011
- }
5012
- case 'add': {
5013
- const tfoot = table.appendChild(document.createElement('tfoot'));
5014
- tfoot.addEventListener('dragenter', () => { dragenter(); });
5015
- const tr = tfoot.appendChild(document.createElement('tr'));
5016
- const th = tr.appendChild(document.createElement('th'));
5017
- th.colSpan = columns.length;
5018
- const button = th.appendChild(document.createElement('button'));
5019
- button.addEventListener('click', add);
5020
- button.classList.add('NeeloongForm-table-foot-add');
5021
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5022
- break;
5023
- }
5024
- case 'none':
5025
- }
5026
- const start = thead;
5027
- /** @type {Map<Store, [HTMLTableSectionElement, () => void]>} */
5028
- let seMap = new Map();
5029
- /** @param {Map<Store, [tbody: HTMLTableSectionElement, destroy: () => void]>} map */
5030
- function destroyMap(map) {
5031
- for (const [el, destroy] of map.values()) {
5032
- destroy();
5033
- el.remove();
5034
- }
5035
-
5036
- }
5037
- const columnNames = columns.map((v) => Array.isArray(v) ? v : v.field);
5038
- const childrenResult = watch(() => store.children, function render(children) {
5039
- let nextNode = thead.nextSibling;
5040
- const oldSeMap = seMap;
5041
- seMap = new Map();
5042
- for (let child of children) {
5043
- const old = oldSeMap.get(child);
5044
- if (!old) {
5045
- const [el, destroy] = Line(child, fieldRenderer, layout, {
5046
- columns: columnNames,
5047
- remove: remove.bind(null, child),
5048
- dragenter: dragenter.bind(null, child),
5049
- dragstart: dragstart.bind(null, child),
5050
- dragend,
5051
- deletable,
5052
- }, options);
5053
- table.insertBefore(el, nextNode);
5054
- seMap.set(child, [el, destroy]);
5055
- continue;
5056
- }
5057
- oldSeMap.delete(child);
5058
- seMap.set(child, old);
5059
- if (nextNode === old[0]) {
5060
- nextNode = nextNode.nextSibling;
5061
- continue;
5062
- }
5063
- table.insertBefore(old[0], nextNode);
5064
- }
5065
- destroyMap(oldSeMap);
5066
- }, true);
5067
-
5068
- return [table, () => {
5069
- start.remove();
5070
- thead.remove();
5071
- destroyMap(seMap);
5072
- childrenResult();
5073
- for (const destroy of destroyList) {
5074
- destroy();
5075
- }
5076
- }];
5077
- }
5078
-
5079
- /** @import { Store } from '../Store/index.mjs' */
5080
- /** @import { StoreLayout } from '../types.mjs' */
5081
-
5082
-
5083
- /**
5084
- *
5085
- * @param {string} field
5086
- */
5087
- function createFieldFilter(field) {
5088
-
5089
- /**
5090
- *
5091
- * @param {StoreLayout.Item} v
5092
- * @returns {v is StoreLayout.Field}
5093
- */
5094
-
5095
- return function (v) {
5096
- if (v.type && v.type !== 'field') { return false; }
5097
- return v.field === field;
5098
-
5099
- };
5100
- }
5101
- /**
5102
- *
5103
- * @param {StoreLayout.Renderer} fieldRenderer
5104
- * @param {Store} store
5105
- * @param {Node} node
5106
- * @param {StoreLayout.Options?} options
5107
- * @param {StoreLayout?} [layout]
5108
- * @param {Node} [anchor]
5109
- * @param {(child?: Store<any, any> | undefined) => void} [dragenter]
5110
- */
5111
- function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragenter) {
5112
- /** @type {(() => void)[]} */
5113
- const destroyList = [];
5114
- if (node instanceof Element) {
5115
- const tagName = node.tagName.toLowerCase();
5116
- if (!node.parentNode) { return () => { }; }
5117
- if (tagName === 'nl-form-field') {
5118
- const field = node.getAttribute('name') || '';
5119
- const mode = node.getAttribute('mode') || '';
5120
- const fieldStore = field ? store.child(field) : store;
5121
- const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) || null : null;
5122
- if (!fieldStore) { return () => { }; }
5123
- switch (mode) {
5124
- case 'grid': {
5125
- const [el, destroy] = Form(store, fieldRenderer, fieldLayout, options);
5126
- node.replaceWith(el);
5127
- return destroy;
5128
- }
5129
- }
5130
- const component = fieldStore.component;
5131
- if (component) {
5132
- const res = fieldRenderer(fieldStore, component, options);
5133
- if (res) {
5134
- const [el, destroy] = res;
5135
- node.replaceWith(el);
5136
- return destroy;
5137
- }
5138
- }
5139
- const value = node.getAttribute('placeholder') || '';
5140
- node.replaceWith(document.createTextNode(value));
5141
- return () => { };
5142
- }
5143
- if (tagName === 'nl-form-button') {
5144
- const button = document.createElement('button');
5145
- button.className = 'NeeloongForm-item-button';
5146
- const click = node.getAttribute('click') || '';
5147
- const call = options?.call;
5148
- if (click && typeof call === 'function') {
5149
- button.addEventListener('click', e => call(click, e, store, options));
5150
- }
5151
- for (const n of [...node.childNodes]) {
5152
- button.appendChild(n);
5153
- }
5154
- node.replaceWith(button);
5155
- return () => { };
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 () => { };
5156
4746
  }
5157
4747
  const name = node.getAttribute('nl-form-field');
5158
4748
  if (name) {
@@ -5323,46 +4913,486 @@ function getHtmlContent(html) {
5323
4913
  return template.content;
5324
4914
  }
5325
4915
 
5326
- /** @import { CellValues } from './createCell.mjs' */
5327
-
5328
- /**
5329
- *
5330
- * @param {HTMLElement} root
5331
- * @param {CellValues} [values]
5332
- * @returns {() => void}
5333
- */
5334
- function bindErrored(root, values) {
5335
- return effect(() => {
5336
- if (values?.error) {
5337
- root.classList.add('NeeloongForm-item-errored');
5338
- } else {
5339
- root.classList.remove('NeeloongForm-item-errored');
5340
- }
5341
- });
5342
- }
5343
-
5344
4916
  /**
5345
- *
5346
- * @param {HTMLElement} root
5347
- * @param {{ required?: boolean | null }} [values]
5348
- * @returns {() => void}
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]}
5349
4923
  */
5350
- function bindRequired(root, values) {
5351
- return effect(() => {
5352
- if (values?.required) {
5353
- root.classList.add('NeeloongForm-item-required');
5354
- } else {
5355
- root.classList.remove('NeeloongForm-item-required');
5356
- }
5357
- });
4924
+ function FormFieldInline(store, fieldRenderer, layout, options) {
4925
+ const { component } = store;
4926
+ return component
4927
+ && fieldRenderer(store, component, options)
4928
+ || [document.createElement('div'), () => { }];
5358
4929
  }
5359
4930
 
5360
- /** @import { CellValues } from './createCell.mjs' */
4931
+ /** @import { Store } from '../Store/index.mjs' */
4932
+ /** @import { StoreLayout } from '../types.mjs' */
5361
4933
 
5362
4934
  /**
5363
- *
5364
- * @param {CellValues} [values]
5365
- * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]}
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;
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));
5104
+ }
5105
+ return () => {
5106
+ for (const destroy of destroyList) {
5107
+ destroy();
5108
+ }
5109
+ };
5110
+ }
5111
+ /**
5112
+ *
5113
+ * @param {ArrayStore} store
5114
+ * @param {StoreLayout.Renderer} fieldRenderer
5115
+ * @param {StoreLayout.Field?} layout
5116
+ * @param {StoreLayout.Options?} options
5117
+ * @returns {[HTMLTableElement, () => void]}
5118
+ */
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
+
5161
+ }
5162
+ if (!columns.length) {
5163
+ columns = [
5164
+ { actions: ['add', 'trigger', 'move', 'remove', 'serial'] },
5165
+ ...fieldList.slice(0, 3),
5166
+ ];
5167
+ }
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
+ }];
5286
+ }
5287
+
5288
+ /** @import { StoreLayout } from '../types.mjs' */
5289
+
5290
+ /**
5291
+ *
5292
+ * @param {HTMLElement} root
5293
+ * @param {StoreLayout.Grid?} [layout]
5294
+ */
5295
+ function bindGrid(root, layout) {
5296
+ const { colStart, colSpan, colEnd, rowStart, rowSpan, rowEnd } = layout || {};
5297
+ root.classList.add(`NeeloongForm-item-grid`);
5298
+ if (colStart && colEnd) {
5299
+ root.style.gridColumn = `${colStart} / ${colEnd}`;
5300
+ } else if (colStart && colSpan) {
5301
+ root.style.gridColumn = `${colStart} / span ${colSpan}`;
5302
+ } else if (colSpan) {
5303
+ root.style.gridColumn = `span ${colSpan}`;
5304
+ }
5305
+ if (rowStart && rowEnd) {
5306
+ root.style.gridRow = `${rowStart} / ${rowEnd}`;
5307
+ } else if (rowStart && rowSpan) {
5308
+ root.style.gridRow = `${rowStart} / span ${rowSpan}`;
5309
+ } else if (rowSpan) {
5310
+ root.style.gridRow = `span ${rowSpan}`;
5311
+ }
5312
+
5313
+
5314
+
5315
+
5316
+ }
5317
+
5318
+ /** @import { CellValues } from './createCell.mjs' */
5319
+
5320
+ /**
5321
+ *
5322
+ * @param {HTMLElement} root
5323
+ * @param {CellValues} [values]
5324
+ * @returns {() => void}
5325
+ */
5326
+ function bindErrored(root, values) {
5327
+ return effect(() => {
5328
+ if (values?.error) {
5329
+ root.classList.add('NeeloongForm-item-errored');
5330
+ } else {
5331
+ root.classList.remove('NeeloongForm-item-errored');
5332
+ }
5333
+ });
5334
+ }
5335
+
5336
+ /**
5337
+ *
5338
+ * @param {HTMLElement} root
5339
+ * @param {{ required?: boolean | null }} [values]
5340
+ * @returns {() => void}
5341
+ */
5342
+ function bindRequired(root, values) {
5343
+ return effect(() => {
5344
+ if (values?.required) {
5345
+ root.classList.add('NeeloongForm-item-required');
5346
+ } else {
5347
+ root.classList.remove('NeeloongForm-item-required');
5348
+ }
5349
+ });
5350
+ }
5351
+
5352
+ /** @import { CellValues } from './createCell.mjs' */
5353
+
5354
+ /**
5355
+ *
5356
+ * @param {CellValues} [values]
5357
+ * @returns {[HTMLDivElement, () => void, HTMLDivElement, (() => void)[]]}
5358
+ */
5359
+ function createStdCell(values) {
5360
+ /** @type {(() => void)[]} */
5361
+ const destroyList = [];
5362
+ const root = document.createElement('div');
5363
+ root.className = 'NeeloongForm-item';
5364
+ destroyList.push(bindRequired(root, values));
5365
+
5366
+ const label = root.appendChild(document.createElement('div'));
5367
+ label.className = 'NeeloongForm-item-label';
5368
+ destroyList.push(effect(() => label.innerText = values?.label || ''));
5369
+
5370
+ const content = root.appendChild(document.createElement('div'));
5371
+ content.className = 'NeeloongForm-item-content';
5372
+
5373
+ const description = root.appendChild(document.createElement('div'));
5374
+ description.className = 'NeeloongForm-item-description';
5375
+ destroyList.push(effect(() => description.innerText = values?.description || ''));
5376
+ const error = root.appendChild(document.createElement('div'));
5377
+ error.className = 'NeeloongForm-item-error';
5378
+ destroyList.push(effect(() => error.innerText = values?.error || ''));
5379
+ destroyList.push(bindErrored(root, values));
5380
+
5381
+ return [root, () => {
5382
+ for (const destroy of destroyList) {
5383
+ destroy();
5384
+ }
5385
+ }, content, destroyList];
5386
+
5387
+
5388
+ }
5389
+
5390
+ /** @import { CellValues } from './createCell.mjs' */
5391
+
5392
+ /**
5393
+ *
5394
+ * @param {CellValues} [values]
5395
+ * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]}
5366
5396
  */
5367
5397
  function createCollapseCell(values) {
5368
5398
  /** @type {(() => void)[]} */
@@ -5385,130 +5415,848 @@ function createCollapseCell(values) {
5385
5415
 
5386
5416
  }
5387
5417
 
5388
- /** @import { StoreLayout } from '../types.mjs' */
5418
+ /** @import { CellValues } from './createCell.mjs' */
5419
+
5420
+ /**
5421
+ *
5422
+ * @param {CellValues} [values]
5423
+ * @returns {[HTMLDivElement, () => void, HTMLDivElement, (() => void)[]]}
5424
+ */
5425
+ function createNullCell(values) {
5426
+ /** @type {(() => void)[]} */
5427
+ const destroyList = [];
5428
+ const root = document.createElement('div');
5429
+ root.className = 'NeeloongForm-item';
5430
+ destroyList.push(bindRequired(root, values));
5431
+ destroyList.push(bindErrored(root, values));
5432
+
5433
+
5434
+ return [root, () => {
5435
+ for (const destroy of destroyList) {
5436
+ destroy();
5437
+ }
5438
+ }, root, destroyList];
5439
+
5440
+
5441
+ }
5442
+
5443
+ /** @import { CellValues } from './createCell.mjs' */
5444
+
5445
+ /**
5446
+ *
5447
+ * @param {CellValues} [values]
5448
+ * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]}
5449
+ */
5450
+ function createFieldsetCell(values) {
5451
+ /** @type {(() => void)[]} */
5452
+ const destroyList = [];
5453
+ const root = document.createElement('fieldset');
5454
+ root.className = 'NeeloongForm-item';
5455
+ destroyList.push(bindRequired(root, values));
5456
+
5457
+ const legend = root.appendChild(document.createElement('legend'));
5458
+ destroyList.push(effect(() => legend.innerText = values?.label || ''));
5459
+ destroyList.push(bindErrored(root, values));
5460
+
5461
+ return [root, () => {
5462
+ for (const destroy of destroyList) {
5463
+ destroy();
5464
+ }
5465
+ }, root, destroyList];
5466
+
5467
+
5468
+ }
5469
+
5470
+ /** @import { StoreLayout } from '../types.mjs' */
5471
+
5472
+ /**
5473
+ * @typedef {object} CellValues
5474
+ * @property {string?} [label]
5475
+ * @property {string?} [description]
5476
+ * @property {string?} [error]
5477
+ * @property {boolean?} [required]
5478
+ */
5479
+ /**
5480
+ *
5481
+ * @param {StoreLayout.Grid?} [layout]
5482
+ * @param {CellValues} [values]
5483
+ * @param {StoreLayout.Grid['cell']?} [defCell]
5484
+ * @param {boolean?} [blockOnly]
5485
+ * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]}
5486
+ */
5487
+ function createCell(layout, values, defCell, blockOnly) {
5488
+ /**
5489
+ *
5490
+ * @param {string?} [cellType]
5491
+ * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]?}
5492
+ */
5493
+ function create(cellType) {
5494
+ if (!cellType) { return null; }
5495
+ if (!blockOnly) {
5496
+ switch (cellType) {
5497
+ case 'inline': {
5498
+ const result = createStdCell(values);
5499
+ bindGrid(result[0], layout);
5500
+ return result;
5501
+ }
5502
+ case 'base': {
5503
+ const result = createNullCell(values);
5504
+ bindGrid(result[0], layout);
5505
+ return result;
5506
+ }
5507
+ }
5508
+ }
5509
+ switch (cellType) {
5510
+ case 'block': return createStdCell(values);
5511
+ case 'fieldset': return createFieldsetCell(values);
5512
+ case 'collapse': return createCollapseCell(values);
5513
+ }
5514
+ return null;
5515
+ }
5516
+ return create(layout?.cell) || create(defCell) || createStdCell(values);
5517
+ }
5518
+
5519
+ /** @import { Store } from '../Store/index.mjs' */
5520
+ /** @import { State } from './Tree.mjs' */
5521
+ /** @import { StoreLayout } from '../types.mjs' */
5522
+
5523
+ /**
5524
+ *
5525
+ * @param {Store<any, any>} store
5526
+ * @param {Signal.State<Store<any, any>?>} currentStore
5527
+ * @param {StoreLayout.Renderer} fieldRenderer
5528
+ * @param {StoreLayout.Field?} layout
5529
+ * @param {State} initState
5530
+ * @param {object} option
5531
+ * @param {StoreLayout.Column[]} option.columns
5532
+ * @param {() => void} option.remove
5533
+ * @param {(el: HTMLElement) => () => void} option.dragenter
5534
+ * @param {() => void} option.dragstart
5535
+ * @param {(inChildren?: boolean) => void} option.drop
5536
+ * @param {() => void} option.dragend
5537
+ * @param {{get(): boolean}} option.deletable
5538
+ * @param {() => void} option.addNode
5539
+ * @param {(store: Store<any, any>) => () => void} option.createDetails
5540
+ * @param {StoreLayout.Options?} options
5541
+
5542
+ * @returns {[HTMLElement, () => void, (s: State) => void]}
5543
+ */
5544
+ function TreeLine(
5545
+ store, currentStore, fieldRenderer, layout, initState, {
5546
+ columns,
5547
+ remove, dragenter, dragstart, dragend, deletable, addNode, drop, createDetails,
5548
+ }, options) {
5549
+ const state = new Signal.State(initState);
5550
+ const root = document.createElement('div');
5551
+ root.addEventListener('dragstart', (event) => {
5552
+ if (event.target !== event.currentTarget) { return; }
5553
+ dragstart();
5554
+ });
5555
+ root.addEventListener('dragend', dragend);
5556
+
5557
+ /** @type {(() => void)[]} */
5558
+ const destroyList = [];
5559
+ root.classList.add('NeeloongForm-tree-item');
5560
+
5561
+ destroyList.push(effect(() => {
5562
+ if (currentStore.get() === store) {
5563
+ root.classList.add('NeeloongForm-tree-current');
5564
+ } else {
5565
+ root.classList.remove('NeeloongForm-tree-current');
5566
+ }
5567
+ }));
5568
+ destroyList.push(effect(() => {
5569
+ const level = state.get().level;
5570
+ root.style.setProperty(`--NeeloongForm-tree-level`, `${level}`);
5571
+ }));
5572
+
5573
+ destroyList.push(effect(() => { root.hidden = state.get().hidden; }));
5574
+
5575
+
5576
+ /** @type {HTMLButtonElement[]} */
5577
+ const collapseList = [];
5578
+
5579
+ /**
5580
+ *
5581
+ * @param {PointerEvent} event
5582
+ */
5583
+ function pointerdown({ pointerId }) {
5584
+ root.draggable = true;
5585
+ /** @param {PointerEvent} event */
5586
+ function pointerup(event) {
5587
+ if (event.pointerId !== pointerId) { return; }
5588
+ if (!root) { return; }
5589
+ root.draggable = false;
5590
+ window.removeEventListener('pointerup', pointerup, { capture: true });
5591
+ window.removeEventListener('pointercancel', pointerup, { capture: true });
5592
+ }
5593
+ window.addEventListener('pointerup', pointerup, { capture: true });
5594
+ window.addEventListener('pointercancel', pointerup, { capture: true });
5595
+
5596
+ }
5597
+ function switchCollapsed() {
5598
+ const s = state.get();
5599
+ s.collapsed = !s.collapsed;
5600
+ }
5601
+ let close = () => { };
5602
+ function open() {
5603
+ close = createDetails(store);
5604
+ }
5605
+ function trigger() {
5606
+ if (currentStore.get() === store) {
5607
+ close(); return;
5608
+ }
5609
+ close = createDetails(store);
5610
+ }
5611
+ const moveStart = layout?.mainMethod === 'move' ? pointerdown : null;
5612
+ const click = moveStart ? null : layout?.mainMethod === 'collapse' ? switchCollapsed
5613
+ : layout?.mainMethod === 'trigger' ? trigger : open;
5614
+
5615
+ const line = root.appendChild(document.createElement('div'));
5616
+ line.classList.add('NeeloongForm-tree-line');
5617
+
5618
+ const dropFront = root.appendChild(document.createElement('div'));
5619
+ dropFront.classList.add('NeeloongForm-tree-drop');
5620
+ const dropChildren = root.appendChild(document.createElement('div'));
5621
+ dropChildren.classList.add('NeeloongForm-tree-drop-children');
5622
+ dropFront.addEventListener('dragover', (e) => e.preventDefault());
5623
+ dropChildren.addEventListener('dragover', (e) => e.preventDefault());
5624
+ dropFront.addEventListener('drop', () => drop());
5625
+ dropChildren.addEventListener('drop', () => drop(true));
5626
+ destroyList.push(effect(() => {
5627
+ dropFront.hidden = dropChildren.hidden = !state.get().droppable;
5628
+ }));
5629
+
5630
+ let dragleave = () => { };
5631
+ dropFront.addEventListener('dragenter', () => dragleave = dragenter(dropFront));
5632
+ dropChildren.addEventListener('dragenter', () => dragleave = dragenter(dropChildren));
5633
+ dropFront.addEventListener('dragleave', () => dragleave());
5634
+ dropChildren.addEventListener('dragleave', () => dragleave());
5635
+
5636
+ for (const { actions, pattern, placeholder, width, field } of columns) {
5637
+ if (!actions?.length) {
5638
+ const td = line.appendChild(document.createElement('div'));
5639
+ td.classList.add('NeeloongForm-tree-cell');
5640
+ if (click) { td.addEventListener('click', click); }
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
+ }
5656
+ continue;
5657
+
5658
+ }
5659
+ for (const k of actions) {
5660
+ switch (k) {
5661
+ case 'trigger': {
5662
+ const btn = line.appendChild(document.createElement('button'));
5663
+ btn.classList.add('NeeloongForm-tree-trigger');
5664
+ btn.addEventListener('click', trigger);
5665
+ continue;
5666
+ }
5667
+ case 'open': {
5668
+ const btn = line.appendChild(document.createElement('button'));
5669
+ btn.classList.add('NeeloongForm-tree-open');
5670
+ btn.addEventListener('click', open);
5671
+ continue;
5672
+ }
5673
+ case 'collapse': {
5674
+ const btn = line.appendChild(document.createElement('button'));
5675
+ btn.classList.add('NeeloongForm-tree-collapse');
5676
+ btn.addEventListener('click', switchCollapsed);
5677
+ collapseList.push(btn);
5678
+ continue;
5679
+ }
5680
+ case 'move': {
5681
+ if (!options?.editable) { continue; }
5682
+ const move = line.appendChild(document.createElement('button'));
5683
+ move.classList.add('NeeloongForm-tree-move');
5684
+ move.addEventListener('pointerdown', pointerdown);
5685
+ destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5686
+ move.disabled = disabled;
5687
+ }, true));
5688
+ continue;
5689
+ }
5690
+ case 'add': {
5691
+ if (!options?.editable) { continue; }
5692
+ const move = line.appendChild(document.createElement('button'));
5693
+ move.classList.add('NeeloongForm-tree-add');
5694
+ move.addEventListener('click', addNode);
5695
+ destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5696
+ move.disabled = disabled;
5697
+ }, true));
5698
+ continue;
5699
+ }
5700
+ case 'remove': {
5701
+ if (!options?.editable) { continue; }
5702
+ const del = line.appendChild(document.createElement('button'));
5703
+ del.classList.add('NeeloongForm-tree-remove');
5704
+ del.addEventListener('click', remove);
5705
+ destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5706
+ del.disabled = disabled;
5707
+ }, true));
5708
+ continue;
5709
+ }
5710
+ case 'serial': {
5711
+ const serial = line.appendChild(document.createElement('span'));
5712
+ serial.classList.add('NeeloongForm-tree-serial');
5713
+ continue;
5714
+ }
5715
+ }
5716
+ }
5717
+ }
5718
+ destroyList.push(effect(() => {
5719
+ const s = state.get();
5720
+ if (!s.hasChildren) {
5721
+ for (const btn of collapseList) {
5722
+ btn.classList.remove('NeeloongForm-tree-collapse-close');
5723
+ btn.classList.remove('NeeloongForm-tree-collapse-open');
5724
+ btn.disabled = true;
5725
+ }
5726
+ } else if (s.collapsed) {
5727
+ for (const btn of collapseList) {
5728
+ btn.classList.remove('NeeloongForm-tree-collapse-close');
5729
+ btn.classList.add('NeeloongForm-tree-collapse-open');
5730
+ btn.disabled = false;
5731
+ }
5732
+ } else {
5733
+ for (const btn of collapseList) {
5734
+ btn.classList.remove('NeeloongForm-tree-collapse-open');
5735
+ btn.classList.add('NeeloongForm-tree-collapse-close');
5736
+ btn.disabled = false;
5737
+ }
5738
+ }
5739
+ }));
5740
+
5741
+ return [root, () => {
5742
+ for (const destroy of destroyList) {
5743
+ destroy();
5744
+ }
5745
+ }, s => state.set(s)];
5746
+ }
5747
+
5748
+ /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5749
+ /** @import { StoreLayout } from '../types.mjs' */
5750
+
5751
+ const verticalWritingMode = new Set([
5752
+ 'vertical-lr', 'vertical-rl', 'sideways-lr', 'sideways-rl',
5753
+ ]);
5754
+ /**
5755
+ *
5756
+ * @param {Element} root
5757
+ * @returns {[boolean, boolean]}
5758
+ */
5759
+ function getLayout(root) {
5760
+ const style = getComputedStyle(root);
5761
+ const writingMode = style.writingMode?.toLowerCase();
5762
+ const vertical = verticalWritingMode.has(writingMode);
5763
+ const reverse = style.direction.toLowerCase() === 'rtl' !== (writingMode === 'sideways-lr');
5764
+ return [vertical, reverse];
5765
+ }
5766
+
5767
+ /**
5768
+ * @typedef {object} State
5769
+ * @property {number} level
5770
+ * @property {number} levelValue
5771
+ * @property {boolean} collapsed
5772
+ * @property {boolean} hidden
5773
+ * @property {boolean} droppable
5774
+ * @property {Set<number>} parents
5775
+ * @property {boolean} hasChildren
5776
+ */
5389
5777
 
5778
+ /**
5779
+ *
5780
+ * @param {ArrayStore} store
5781
+ * @param {State[]} states
5782
+ * @param {Signal.State<number>} drag
5783
+ * @param {string} levelKey
5784
+ * @param {*} index
5785
+ * @returns {State}
5786
+ */
5787
+ function createState(store, states, drag, levelKey, index) {
5788
+ const levelValue = new Signal.Computed(() => {
5789
+ const child = store.child(index);
5790
+ if (!child) { return 0; }
5791
+ return Math.max(0, Math.floor(child.value?.[levelKey]) || 0);
5792
+ });
5793
+ const parentIndex = new Signal.Computed(() => {
5794
+ const child = store.child(index);
5795
+ if (!child) { return -1; }
5796
+ const level = levelValue.get();
5797
+ if (!level) { return -1; }
5798
+ for (let k = index - 1; k >= 0; k--) {
5799
+ if (level > states[k]?.levelValue) { return k; }
5800
+ }
5801
+ return -1;
5802
+ });
5803
+ const hasChildren = new Signal.Computed(() => {
5804
+ const children = store.children;
5805
+ const next = children[index + 1];
5806
+ if (!next) { return false; }
5807
+ const child = children[index];
5808
+ if (!child) { return false; }
5809
+ const level = levelValue.get();
5810
+ return Math.floor(next.child(levelKey)?.value) > level;
5811
+ });
5812
+ const droppable = new Signal.Computed(() => {
5813
+ const dragRow = drag.get();
5814
+ if (dragRow === index) { return false; }
5815
+ const pIndex = parentIndex.get();
5816
+ const parent = states[pIndex];
5817
+ if (!parent) { return true; }
5818
+ return parent.droppable;
5819
+ });
5820
+ const level = new Signal.Computed(() => {
5821
+ const pIndex = parentIndex.get();
5822
+ const parent = states[pIndex];
5823
+ if (!parent) { return 0; }
5824
+ return parent.level + 1;
5825
+ });
5826
+ const collapsed = new Signal.State(false);
5827
+ const hidden = new Signal.Computed(() => {
5828
+ const pIndex = parentIndex.get();
5829
+ const parent = states[pIndex];
5830
+ if (!parent) { return false; }
5831
+ return parent.collapsed || parent.hidden;
5832
+ });
5833
+ /** @type {Signal.Computed<Set<number>>} */
5834
+ const parents = new Signal.Computed(() => {
5835
+ const pIndex = parentIndex.get();
5836
+ const parent = states[pIndex];
5837
+ if (!parent) { return new Set(); }
5838
+ const set = new Set(parent.parents);
5839
+ set.add(pIndex);
5840
+ return set;
5841
+ });
5842
+ return {
5843
+ get level() { return level.get(); },
5844
+ get levelValue() { return levelValue.get(); },
5845
+ get collapsed() { return collapsed.get(); },
5846
+ set collapsed(v) { collapsed.set(v); },
5847
+ get hidden() { return hidden.get(); },
5848
+ get hasChildren() { return hasChildren.get(); },
5849
+ get droppable() { return droppable.get(); },
5850
+ get parents() { return parents.get(); },
5851
+ };
5852
+ }
5390
5853
  /**
5391
5854
  *
5392
- * @param {HTMLElement} root
5393
- * @param {StoreLayout.Item?} layout
5855
+ * @param {ArrayStore} store
5856
+ * @param {StoreLayout.Renderer} fieldRenderer
5857
+ * @param {StoreLayout.Field?} layout
5858
+ * @param {StoreLayout.Options?} options
5859
+ * @returns {[HTMLElement, () => void]}
5394
5860
  */
5395
- function bindGrid(root, layout) {
5396
- const { colStart, colSpan, colEnd, rowStart, rowSpan, rowEnd } = layout || {};
5397
- root.classList.add(`NeeloongForm-item-grid`);
5398
- if (colStart && colEnd) {
5399
- root.style.gridColumn = `${colStart} / ${colEnd}`;
5400
- } else if (colStart && colSpan) {
5401
- root.style.gridColumn = `${colStart} / span ${colSpan}`;
5402
- } else if (colSpan) {
5403
- root.style.gridColumn = `span ${colSpan}`;
5861
+ function Tree(store, fieldRenderer, layout, options) {
5862
+ const headerColumns = layout?.columns;
5863
+ const fieldList = Object.entries(store.type || {})
5864
+ .filter(([k, v]) => typeof v?.type !== 'object')
5865
+ .map(([field, { width, label }]) => ({ field, width, label }));
5866
+ /** @type {StoreLayout.Column[]} */
5867
+ let columns = [];
5868
+ if (Array.isArray(headerColumns)) {
5869
+ const map = new Map(fieldList.map(v => [v.field, v]));
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
+ }
5890
+ const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
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));
5404
5904
  }
5405
- if (rowStart && rowEnd) {
5406
- root.style.gridRow = `${rowStart} / ${rowEnd}`;
5407
- } else if (rowStart && rowSpan) {
5408
- root.style.gridRow = `${rowStart} / span ${rowSpan}`;
5409
- } else if (rowSpan) {
5410
- root.style.gridRow = `span ${rowSpan}`;
5905
+ if (!columns.length) {
5906
+ columns = [
5907
+ { actions: ['collapse', 'move'] },
5908
+ fieldList[0],
5909
+ { actions: ['add', 'remove'] },
5910
+ ];
5411
5911
  }
5412
5912
 
5413
5913
 
5914
+ const root = document.createElement('div');
5915
+ root.className = 'NeeloongForm-tree';
5916
+ const main = root.appendChild(document.createElement('div'));
5917
+ main.className = 'NeeloongForm-tree-main';
5918
+ const splitter = root.appendChild(document.createElement('div'));
5919
+ splitter.className = 'NeeloongForm-tree-splitter';
5920
+ const details = root.appendChild(document.createElement('div'));
5921
+ details.className = 'NeeloongForm-tree-details';
5922
+ splitter.hidden = true;
5923
+ details.hidden = true;
5924
+ /** @type {number?} */
5925
+ let splitterPointerId = null;
5926
+ let splitterOffset = 0;
5927
+ function stopMove() {
5928
+ const pointerId = splitterPointerId;
5929
+ if (pointerId === null) { return; }
5930
+ splitterPointerId = null;
5931
+ splitter.releasePointerCapture(pointerId);
5932
+ }
5933
+
5934
+ splitter.addEventListener('pointerdown', e => {
5935
+ const { pointerId } = e;
5936
+ if (![null, pointerId].includes(splitterPointerId)) { return; }
5937
+ splitterPointerId = pointerId;
5938
+ splitter.setPointerCapture(pointerId);
5939
+ switch (getLayout(splitter).map((v, i) => v ? 2 ** i : 0).reduce((a, b) => a + b)) {
5940
+ case 0: splitterOffset = -e.offsetX; break;
5941
+ case 1: splitterOffset = -e.offsetY; break;
5942
+ case 2: splitterOffset = e.offsetX - splitter.offsetWidth; break;
5943
+ case 3: splitterOffset = e.offsetY - splitter.offsetHeight; break;
5944
+ }
5945
+ });
5946
+ /**
5947
+ * @param {boolean} vertical
5948
+ * @returns {number}
5949
+ */
5950
+ const getSize = vertical => vertical
5951
+ ? root.clientHeight - splitter.offsetHeight
5952
+ : root.clientWidth - splitter.offsetWidth;
5953
+ /** @param {PointerEvent} e */
5954
+ const updateMove = e => {
5955
+ const [vertical, reverse] = getLayout(splitter);
5956
+ let os = 0;
5957
+ switch ([vertical, reverse].map((v, i) => v ? 2 ** i : 0).reduce((a, b) => a + b)) {
5958
+ case 0: os = e.clientX - root.getBoundingClientRect().left; break;
5959
+ case 1: os = e.clientY - root.getBoundingClientRect().top; break;
5960
+ case 2: os = root.getBoundingClientRect().right - e.clientX; break;
5961
+ case 3: os = root.getBoundingClientRect().bottom - e.clientY; break;
5962
+ }
5963
+ const value = 1 - Math.max(0, Math.min((os + splitterOffset) / getSize(vertical), 1));
5964
+ details.style.inlineSize = `${value * 100}%`;
5965
+ };
5966
+ splitter.addEventListener('pointermove', e => {
5967
+ const { pointerId } = e;
5968
+ if (!splitter.hasPointerCapture(pointerId)) { return; }
5969
+ updateMove(e);
5970
+ });
5971
+ splitter.addEventListener('pointerup', e => {
5972
+ const { pointerId } = e;
5973
+ if (pointerId !== splitterPointerId) { return; }
5974
+ updateMove(e);
5975
+ stopMove();
5976
+ });
5977
+ splitter.addEventListener('pointercancel', e => {
5978
+ const { pointerId } = e;
5979
+ if (pointerId !== splitterPointerId) { return; }
5980
+ updateMove(e);
5981
+ stopMove();
5982
+ });
5414
5983
 
5415
5984
 
5416
- }
5417
-
5418
- /** @import { CellValues } from './createCell.mjs' */
5419
-
5420
- /**
5421
- *
5422
- * @param {CellValues} [values]
5423
- * @returns {[HTMLDivElement, () => void, HTMLDivElement, (() => void)[]]}
5424
- */
5425
- function createNullCell(values) {
5426
5985
  /** @type {(() => void)[]} */
5427
5986
  const destroyList = [];
5428
- const root = document.createElement('div');
5429
- root.className = 'NeeloongForm-item';
5430
- destroyList.push(bindRequired(root, values));
5431
- destroyList.push(bindErrored(root, values));
5987
+ let destroyDetails = () => { };
5988
+ const detailsStore = new Signal.State(/** @type{Store<any, any>?}*/(null));
5989
+ /**
5990
+ *
5991
+ * @param {Store<any, any>} store
5992
+ * @returns
5993
+ */
5994
+ function createDetails(store) {
5995
+ if (detailsStore.get() === store) { return destroyDetails; }
5996
+ destroyDetails();
5997
+ detailsStore.set(store);
5998
+ const [form, destroy] = Form(store, fieldRenderer, layout, options);
5999
+ details.appendChild(form);
6000
+ details.hidden = false;
6001
+ splitter.hidden = false;
6002
+ let done = false;
6003
+ destroyDetails = () => {
6004
+ if (done) { return; }
6005
+ done = true;
6006
+ detailsStore.set(null);
6007
+ form.remove();
6008
+ destroy();
6009
+ stopMove();
6010
+ splitter.hidden = true;
6011
+ details.hidden = true;
6012
+ };
6013
+ return destroyDetails;
5432
6014
 
6015
+ }
5433
6016
 
5434
- return [root, () => {
5435
- for (const destroy of destroyList) {
5436
- destroy();
6017
+
6018
+ const levelKey = layout?.levelKey || 'level';
6019
+ const addable = new Signal.Computed(() => store.addable);
6020
+ const deletable = { get: () => Boolean(options?.editable) };
6021
+ /**
6022
+ *
6023
+ * @param {number} parent
6024
+ */
6025
+ function addNode(parent) {
6026
+ /** @type {Record<string, any>} */
6027
+ const data = {};
6028
+ if (parent === -2) {
6029
+ data[levelKey] = 0;
6030
+ store.add(data);
6031
+ return;
5437
6032
  }
5438
- }, root, destroyList];
6033
+ data[levelKey] = (states[parent]?.levelValue ?? -1) + 1;
6034
+ store.insert(parent + 1, data);
6035
+
6036
+ }
6037
+ /**
6038
+ *
6039
+ * @param {Store} child
6040
+ */
6041
+ function remove(child) {
6042
+ store.remove(Number(child.index));
6043
+ }
6044
+ let dragRow = -1;
6045
+ let drag = new Signal.State(dragRow);
6046
+ /**
6047
+ *
6048
+ * @param {Store} [child]
6049
+ * @param {boolean} [inChildren]
6050
+ */
6051
+ function getLevel(child, inChildren) {
6052
+ if (!child) { return 0; }
6053
+ const newLevel = Number(states[Number(child.index)]?.levelValue) || 0;
6054
+ if (!inChildren) { return newLevel; }
6055
+ return newLevel + 1;
6056
+ }
6057
+ /**
6058
+ *
6059
+ * @param {number} start
6060
+ */
6061
+ function getQuantity(start) {
6062
+ let last = start + 1;
6063
+ const level = states[dragRow]?.level ?? 0;
6064
+ for (; (states[last]?.level ?? -1) > level; last++) { }
6065
+ return last - dragRow;
6066
+ }
6067
+ /**
6068
+ *
6069
+ * @param {Store} [child]
6070
+ * @param {boolean} [inChildren]
6071
+ */
6072
+ function drop(child, inChildren) {
6073
+ if (dragRow < 0) { return; }
6074
+ const newLevel = Number(getLevel(child, inChildren)) || 0;
6075
+ let quantity = getQuantity(dragRow);
6076
+ let index = -1;
6077
+ if (child) {
6078
+ index = Number(child.index);
6079
+ if (dragRow === index || states[index]?.parents?.has(dragRow)) { return; }
6080
+ if (inChildren || index !== dragRow + quantity) {
6081
+ if (inChildren) {
6082
+ for (const currentLevel = states[index]?.level || 0; states[index + 1]?.level > currentLevel; index++);
6083
+ if (index < dragRow) { index += 1; }
6084
+ } else {
6085
+ if (index > dragRow) { index -= 1; }
6086
+ }
6087
+ if (index < 0) { return; }
6088
+ } else {
6089
+ index = -1;
6090
+ }
5439
6091
 
6092
+ } else {
6093
+ index = states.length - 1;
6094
+ if (index < 0) { return; }
6095
+ }
6096
+ if (index >= 0 && dragRow !== index) {
6097
+ quantity = store.move(dragRow, index, quantity);
6098
+ if (!quantity) { return; }
6099
+ dragRow = quantity > 1 && index > dragRow ? index - quantity + 1 : index;
6100
+ drag.set(dragRow);
6101
+ }
6102
+ const levelSub = Array(quantity).fill(states[dragRow]?.level ?? 0).map((l, i) => Math.max((states[i + dragRow]?.level ?? 0) - l, 0));
6103
+ for (let i = 0; i < quantity; i++) {
6104
+ const c = store.children[dragRow + i]?.child(levelKey);
6105
+ if (c) {
6106
+ c.value = newLevel + levelSub[i];
6107
+ }
6108
+ }
5440
6109
 
5441
- }
5442
6110
 
5443
- /** @import { CellValues } from './createCell.mjs' */
6111
+ }
6112
+ /**
6113
+ *
6114
+ * @param {Store} child
6115
+ */
6116
+ function dragstart(child) {
6117
+ dragRow = Number(child.index);
6118
+ drag.set(dragRow);
6119
+ main.classList.add('NeeloongForm-tree-moving');
6120
+
6121
+ }
6122
+ /** @type {HTMLElement?} */
6123
+ let dragenterEl = null;
6124
+ /** @param {HTMLElement} el */
6125
+ function dragenter(el) {
6126
+ if (dragRow === -1) { return () => { }; }
6127
+ dragenterEl?.classList.remove('NeeloongForm-tree-drag-over');
6128
+ dragenterEl = el;
6129
+ dragenterEl?.classList.add('NeeloongForm-tree-drag-over');
6130
+ return () => {
6131
+ if (dragenterEl !== el) { return; }
6132
+ dragenterEl?.classList.remove('NeeloongForm-tree-drag-over');
6133
+ dragenterEl = null;
6134
+ };
5444
6135
 
5445
- /**
5446
- *
5447
- * @param {CellValues} [values]
5448
- * @returns {[HTMLDivElement, () => void, HTMLDivElement, (() => void)[]]}
5449
- */
5450
- function createStdCell(values) {
5451
- /** @type {(() => void)[]} */
5452
- const destroyList = [];
5453
- const root = document.createElement('div');
5454
- root.className = 'NeeloongForm-item';
5455
- destroyList.push(bindRequired(root, values));
6136
+ }
6137
+ function dragend() {
6138
+ dragRow = -1;
6139
+ drag.set(dragRow);
6140
+ main.classList.remove('NeeloongForm-tree-moving');
6141
+ dragenterEl?.classList.remove('NeeloongForm-tree-drag-over');
6142
+ dragenterEl = null;
5456
6143
 
5457
- const label = root.appendChild(document.createElement('div'));
5458
- label.className = 'NeeloongForm-item-label';
5459
- destroyList.push(effect(() => label.innerText = values?.label || ''));
6144
+ }
6145
+ if (options?.editable) {
6146
+ const button = main.appendChild(document.createElement('button'));
6147
+ button.addEventListener('click', () => addNode(-1));
6148
+ button.classList.add('NeeloongForm-tree-head-add');
6149
+ destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
6150
+ }
6151
+ const start = main.appendChild(document.createComment(''));
6152
+ if (options?.editable) {
6153
+ const foot = main.appendChild(document.createElement('div'));
6154
+ foot.classList.add('NeeloongForm-tree-foot');
6155
+ const button = foot.appendChild(document.createElement('button'));
6156
+ button.addEventListener('click', () => addNode(-2));
6157
+ button.classList.add('NeeloongForm-tree-foot-add');
6158
+ const dropFront = foot.appendChild(document.createElement('div'));
6159
+ dropFront.classList.add('NeeloongForm-tree-drop');
5460
6160
 
5461
- const content = root.appendChild(document.createElement('div'));
5462
- content.className = 'NeeloongForm-item-content';
5463
6161
 
5464
- const description = root.appendChild(document.createElement('div'));
5465
- description.className = 'NeeloongForm-item-description';
5466
- destroyList.push(effect(() => description.innerText = values?.description || ''));
5467
- const error = root.appendChild(document.createElement('div'));
5468
- error.className = 'NeeloongForm-item-error';
5469
- destroyList.push(effect(() => error.innerText = values?.error || ''));
5470
- destroyList.push(bindErrored(root, values));
6162
+ let dragleave = () => { };
6163
+ dropFront.addEventListener('dragover', (e) => e.preventDefault());
6164
+ dropFront.addEventListener('dragenter', () => dragleave = dragenter(dropFront));
6165
+ dropFront.addEventListener('dragleave', () => dragleave());
6166
+ dropFront.addEventListener('drop', () => drop());
5471
6167
 
5472
- return [root, () => {
5473
- for (const destroy of destroyList) {
6168
+
6169
+ destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
6170
+ }
6171
+ /** @type {Map<Store, [HTMLElement, () => void, (s: State) => void]>} */
6172
+ let seMap = new Map();
6173
+ /** @param {Map<Store, [tbody: HTMLElement, destroy: () => void, (s: State) => void]>} map */
6174
+ function destroyMap(map) {
6175
+ for (const [el, destroy] of map.values()) {
5474
6176
  destroy();
6177
+ el.remove();
5475
6178
  }
5476
- }, content, destroyList];
5477
6179
 
6180
+ }
6181
+ /** @type {State[]} */
6182
+ const states = [];
6183
+ const childrenResult = watch(() => store.children, function render(children) {
6184
+ let nextNode = start.nextSibling;
6185
+ const oldSeMap = seMap;
6186
+ seMap = new Map();
6187
+ const childrenLength = children.length;
6188
+ for (let i = states.length; i < childrenLength; i++) {
6189
+ states[i] = createState(store, states, drag, 'level', i);
6190
+ }
5478
6191
 
5479
- }
6192
+ let i = -1;
6193
+ for (let child of children) {
6194
+ i++;
6195
+ const state = states[i];
6196
+ const old = oldSeMap.get(child);
6197
+ if (!old) {
6198
+ const [el, destroy, setState] = TreeLine(child, detailsStore, fieldRenderer, layout, state, {
6199
+ columns,
6200
+ remove: remove.bind(null, child),
6201
+ dragenter,
6202
+ dragstart: dragstart.bind(null, child),
6203
+ dragend,
6204
+ deletable,
6205
+ addNode: () => addNode(Number(child.index)),
6206
+ createDetails,
6207
+ drop: drop.bind(null, child),
6208
+ }, options);
6209
+ main.insertBefore(el, nextNode);
6210
+ seMap.set(child, [el, destroy, setState]);
6211
+ continue;
6212
+ }
6213
+ oldSeMap.delete(child);
6214
+ seMap.set(child, old);
6215
+ old[2](state);
6216
+ if (nextNode === old[0]) {
6217
+ nextNode = nextNode.nextSibling;
6218
+ continue;
6219
+ }
6220
+ main.insertBefore(old[0], nextNode);
6221
+ }
6222
+ states.splice(childrenLength);
6223
+ destroyMap(oldSeMap);
6224
+ }, true);
5480
6225
 
5481
- /** @import { StoreLayout } from '../types.mjs' */
6226
+ return [root, () => {
6227
+ start.remove();
6228
+ destroyMap(seMap);
6229
+ childrenResult();
6230
+ destroyDetails?.();
6231
+ for (const destroy of destroyList) {
6232
+ destroy();
6233
+ }
6234
+ }];
6235
+ }
5482
6236
 
5483
6237
  /**
5484
- * @typedef {object} CellValues
5485
- * @property {string?} [label]
5486
- * @property {string?} [description]
5487
- * @property {string?} [error]
5488
- * @property {boolean?} [required]
6238
+ *
6239
+ * @param {string | ParentNode} html
6240
+ * @param {Store<any, any>} store
6241
+ * @param {StoreLayout.Renderer} fieldRenderer
6242
+ * @param {StoreLayout.Options?} options
6243
+ * @param {StoreLayout.Field?} layout
6244
+ * @returns
5489
6245
  */
6246
+ function Html(html, store, fieldRenderer, options, layout) {
6247
+ const htmlContent = getHtmlContent(html);
6248
+ const destroy = renderHtml(store, fieldRenderer, htmlContent, options, layout);
6249
+ return [htmlContent, destroy];
6250
+ }
6251
+
5490
6252
  /**
5491
- *
5492
- * @param {StoreLayout.Item} layout
5493
- * @param {CellValues} [values]
5494
- * @param {string?} [defCell]
5495
- * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]}
6253
+ *
6254
+ * @param {StoreLayout.Field['arrayStyle']?} arrayStyle
5496
6255
  */
5497
- function createCell(layout, values, defCell) {
5498
- switch (layout.cell || defCell) {
5499
- default:
5500
- case 'block': return createStdCell(values);
5501
- case 'collapse': return createCollapseCell(values);
5502
- case 'inline': {
5503
- const result = createStdCell(values);
5504
- bindGrid(result[0], layout);
5505
- return result;
5506
- }
5507
- case 'base': {
5508
- const result = createNullCell(values);
5509
- bindGrid(result[0], layout);
5510
- return result;
5511
- }
6256
+ function getArrayCell(arrayStyle) {
6257
+ switch(arrayStyle) {
6258
+ case 'tree': return Tree;
6259
+ default: return Table;
5512
6260
  }
5513
6261
 
5514
6262
  }
@@ -5519,66 +6267,28 @@ function createCell(layout, values, defCell) {
5519
6267
  * @param {StoreLayout.Renderer} fieldRenderer
5520
6268
  * @param {StoreLayout.Field?} layout
5521
6269
  * @param {StoreLayout.Options?} options
5522
- * @param {boolean} [inline]
5523
6270
  * @returns {[ParentNode, () => void]}
5524
6271
  */
5525
- function FormField(store, fieldRenderer, layout, options, inline = false) {
6272
+ function FormField(store, fieldRenderer, layout, options) {
5526
6273
  const { type, component } = store;
5527
- if (inline) {
5528
- const html = layout?.inlineHtml;
5529
- if (html) {
5530
- const content = getHtmlContent(html);
5531
- const destroy = renderHtml(store, fieldRenderer, content, options, layout);
5532
- return [content, destroy];
5533
- }
5534
- return component
5535
- && fieldRenderer(store, component, options)
5536
- || [document.createElement('div'), () => { }];
5537
- }
5538
6274
  const isObject = type && typeof type === 'object';
5539
6275
  const html = layout?.html;
5540
- if (html) {
5541
- const [root, destroy, content, destroyList] = createCell(layout, store, isObject ? 'collapse' : 'base');
5542
- const htmlContent = getHtmlContent(html);
5543
- destroyList.push(renderHtml(store, fieldRenderer, htmlContent, options, layout));
5544
- content.appendChild(htmlContent);
5545
- return [root, destroy];
5546
- }
5547
- if (isObject) {
5548
- const [root, destroy, content, destroyList] = createCollapseCell(store);
5549
- destroyList.push(effect(() => root.hidden = store.hidden));
5550
- if (typeof component === 'function') {
5551
- const r = fieldRenderer(store, component, options);
5552
- if (r) {
5553
- const [el, destroy] = r;
5554
- content.appendChild(el);
5555
- destroyList.push(destroy);
5556
- }
5557
- } else if (store instanceof ArrayStore) {
5558
- const [table, destroy] = Table(store, fieldRenderer, layout, options);
5559
- content.appendChild(table);
5560
- destroyList.push(destroy);
5561
- } else {
5562
- const [form, destroy] = Form(store, fieldRenderer, layout, options);
5563
- content.appendChild(form);
5564
- destroyList.push(destroy);
5565
- }
5566
- return [root, destroy];
5567
- }
5568
-
5569
-
5570
- const [root, destroy, content, destroyList] = layout?.cell === 'base'
5571
- ? createNullCell(store)
5572
- : createStdCell(store);
5573
- bindGrid(root, layout);
6276
+ /** @type {StoreLayout.Grid['cell']} */
6277
+ const cellType = isObject
6278
+ ? store instanceof ArrayStore ? 'collapse' : 'fieldset'
6279
+ : html ? 'base' : 'inline';
6280
+ const [root, destroy, content, destroyList] = createCell(layout, store, cellType, isObject);
5574
6281
  destroyList.push(effect(() => root.hidden = store.hidden));
5575
- if (typeof component === 'function') {
5576
- const r = fieldRenderer(store, component, options);
5577
- if (r) {
5578
- const [el, destroy] = r;
5579
- content.appendChild(el);
5580
- destroyList.push(destroy);
5581
- }
6282
+
6283
+ const r =
6284
+ html && Html(html, store, fieldRenderer, options, layout)
6285
+ || typeof component === 'function' && fieldRenderer(store, component, options)
6286
+ || store instanceof ArrayStore && getArrayCell(layout?.arrayStyle)(store, fieldRenderer, layout, options)
6287
+ || isObject && Form(store, fieldRenderer, layout, options);
6288
+ if (r) {
6289
+ const [el, destroy] = r;
6290
+ content.appendChild(el);
6291
+ destroyList.push(destroy);
5582
6292
  }
5583
6293
  return [root, destroy];
5584
6294
  }