@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.d.mts +61 -30
- package/index.full.js +1530 -820
- package/index.full.min.js +9 -9
- package/index.full.min.mjs +7 -7
- package/index.min.mjs +3 -3
- package/index.mjs +1530 -820
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @neeloong/form v0.
|
|
3
|
-
* (c) 2024-
|
|
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) |
|
|
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.
|
|
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,
|
|
407
|
-
setValue,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
#
|
|
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()
|
|
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
|
-
|
|
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
|
|
878
|
-
|
|
879
|
-
if(
|
|
880
|
-
this.#value.
|
|
881
|
-
this.#
|
|
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
|
|
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
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
967
|
+
parent, index, new: isNew, onUpdate,
|
|
998
968
|
size: childrenTypes.length,
|
|
999
969
|
setValue(v) { return typeof v === 'object' ? v : null; },
|
|
1000
|
-
|
|
1001
|
-
|
|
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 ?? (() =>
|
|
1008
|
-
Object.entries(children)
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1094
|
-
|
|
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,
|
|
1063
|
+
onUpdate: (value, index, store) => {
|
|
1105
1064
|
updateChildren(value);
|
|
1106
|
-
onUpdate?.(value, index,
|
|
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,
|
|
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
|
|
1174
|
+
if (!Array.isArray(data)) { return 0; }
|
|
1175
|
+
|
|
1234
1176
|
const children = [...this.#children.get()];
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
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
|
|
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
|
-
|
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
-
|
|
4822
|
-
if (
|
|
4823
|
-
|
|
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
|
-
|
|
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 {
|
|
4884
|
-
* @param {
|
|
4885
|
-
* @param {
|
|
4886
|
-
* @param {
|
|
4887
|
-
* @param {
|
|
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
|
|
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
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
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
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
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 {
|
|
5347
|
-
* @param {
|
|
5348
|
-
* @
|
|
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
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
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 {
|
|
4931
|
+
/** @import { Store } from '../Store/index.mjs' */
|
|
4932
|
+
/** @import { StoreLayout } from '../types.mjs' */
|
|
5361
4933
|
|
|
5362
4934
|
/**
|
|
5363
|
-
*
|
|
5364
|
-
* @param {
|
|
5365
|
-
* @
|
|
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 {
|
|
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 {
|
|
5393
|
-
* @param {StoreLayout.
|
|
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
|
|
5396
|
-
const
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
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 (
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
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
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
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
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
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
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
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
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
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
|
-
|
|
5473
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
5485
|
-
* @
|
|
5486
|
-
* @
|
|
5487
|
-
* @
|
|
5488
|
-
* @
|
|
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.
|
|
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
|
|
5498
|
-
switch
|
|
5499
|
-
|
|
5500
|
-
|
|
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
|
|
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
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
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
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
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
|
}
|