@neeloong/form 0.23.0 → 0.25.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.full.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.23.0
2
+ * @neeloong/form v0.25.0
3
3
  * (c) 2024-2026 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -614,7 +614,7 @@
614
614
 
615
615
  }
616
616
 
617
- /** @import { Schema } from '../types.mjs' */
617
+ /** @import { Schema } from '../Schema.types.mjs' */
618
618
  /** @param {*} v */
619
619
  const string = v => typeof v === 'string' && v || null;
620
620
  /** @param {*} v */
@@ -711,7 +711,7 @@
711
711
  return r;
712
712
  }
713
713
 
714
- /** @import { Schema } from '../types.mjs' */
714
+ /** @import { Schema } from '../Schema.types.mjs' */
715
715
  /** @import ArrayStore from './ArrayStore.mjs' */
716
716
 
717
717
 
@@ -763,7 +763,7 @@
763
763
  }
764
764
 
765
765
  /** @import Store from './Store.mjs' */
766
- /** @import { AsyncValidator, Validator } from '../types.mjs' */
766
+ /** @import { Schema } from '../Schema.types.mjs' */
767
767
  /**
768
768
  *
769
769
  * @param {*} v
@@ -778,7 +778,7 @@
778
778
  /**
779
779
  *
780
780
  * @param {Store} store
781
- * @param {...Validator | undefined | null | (Validator | undefined | null)[]} validators
781
+ * @param {...Schema.Validator | undefined | null | (Schema.Validator | undefined | null)[]} validators
782
782
  * @returns
783
783
  */
784
784
  function createValidator(store, ...validators) {
@@ -801,7 +801,7 @@
801
801
  /**
802
802
  *
803
803
  * @param {Store} store
804
- * @param {...AsyncValidator | undefined | null | (AsyncValidator | undefined | null)[]} validators
804
+ * @param {...Schema.AsyncValidator | undefined | null | (Schema.AsyncValidator | undefined | null)[]} validators
805
805
  * @returns {[exec: () => Promise<string[]>, state: Signal.Computed<string[]>, stop: () => void]}
806
806
  */
807
807
  function createAsyncValidator(store, ...validators) {
@@ -816,7 +816,7 @@
816
816
  const st = new exports.Signal.State(/** @type {string[]} */([]));
817
817
  /**
818
818
  *
819
- * @param {AsyncValidator} validator
819
+ * @param {Schema.AsyncValidator} validator
820
820
  * @param {AbortSignal} signal
821
821
  */
822
822
  async function run(validator, signal) {
@@ -870,7 +870,8 @@
870
870
  }
871
871
 
872
872
  /** @import { Ref } from './ref.mjs' */
873
- /** @import { AsyncValidator, Schema, Validator } from '../types.mjs' */
873
+ /** @import { Schema } from '../Schema.types.mjs' */
874
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
874
875
 
875
876
  /**
876
877
  * 管理单个表单字段的状态和行为
@@ -967,8 +968,8 @@
967
968
  * @param {number} [options.maxLength]
968
969
  * @param {RegExp} [options.pattern]
969
970
  * @param {(Schema.Value.Group | Schema.Value | string | number)[]} [options.values] 可选值
970
- * @param {Validator | Validator[] | null} [options.validator]
971
- * @param {{[k in keyof Schema.Events]?: AsyncValidator | AsyncValidator[] | null}} [options.validators]
971
+ * @param {Schema.Validator | Schema.Validator[] | null} [options.validator]
972
+ * @param {{[k in keyof Schema.Events]?: Schema.AsyncValidator | Schema.AsyncValidator[] | null}} [options.validators]
972
973
  *
973
974
  * @param {Ref?} [options.ref]
974
975
  *
@@ -1000,6 +1001,7 @@
1000
1001
  this.#type = schema.type;
1001
1002
  this.#meta = schema.meta;
1002
1003
  this.#component = schema.component;
1004
+ this.#layout = schema.layout || {};
1003
1005
 
1004
1006
  const selfNewState = new exports.Signal.State(Boolean(isNew));
1005
1007
  this.#selfNew = selfNewState;
@@ -1090,6 +1092,9 @@
1090
1092
  this.listen(k, f);
1091
1093
  }
1092
1094
  }
1095
+ /** @type {StoreLayout.Field<any>} */
1096
+ #layout
1097
+ get layout() { return this.#layout; }
1093
1098
  #createDefault;
1094
1099
  /** @param {any} [value] @returns {any} */
1095
1100
  createDefault(value) { return this.#createDefault(value); }
@@ -1477,7 +1482,7 @@
1477
1482
  /**
1478
1483
  * 异步校验
1479
1484
  * @overload
1480
- * @param {true} [path]
1485
+ * @param {true} [self]
1481
1486
  * @returns {Promise<string[] | null>}
1482
1487
  */
1483
1488
  /**
@@ -1500,6 +1505,7 @@
1500
1505
  });
1501
1506
  }
1502
1507
  const selfPath = Array.isArray(path) ? path : [];
1508
+ if (this.#hidden.get()) { return Promise.resolve([]); }
1503
1509
  const list = [this.validate(true).then(errors => {
1504
1510
  if (!errors?.length) { return []; }
1505
1511
  return [{ path: [...selfPath], store: /** @type {Store} */(this), errors }];
@@ -1511,7 +1517,7 @@
1511
1517
  }
1512
1518
  }
1513
1519
 
1514
- /** @import { Schema } from '../types.mjs' */
1520
+ /** @import { Schema } from '../Schema.types.mjs' */
1515
1521
 
1516
1522
  /**
1517
1523
  * @template {Record<string, any>} [T=Record<string, any>]
@@ -1575,7 +1581,7 @@
1575
1581
  // @ts-ignore
1576
1582
  setObjectStore(ObjectStore);
1577
1583
 
1578
- /** @import { Schema } from '../types.mjs' */
1584
+ /** @import { Schema } from '../Schema.types.mjs' */
1579
1585
 
1580
1586
 
1581
1587
 
@@ -1629,7 +1635,7 @@
1629
1635
  }
1630
1636
 
1631
1637
  };
1632
- super(schema, {
1638
+ super({ ...schema, immutable: false }, {
1633
1639
  index, new: isNew, parent,
1634
1640
  size: new exports.Signal.Computed(() => childrenState.get().length),
1635
1641
  setValue(v) {
@@ -1666,7 +1672,10 @@
1666
1672
  },
1667
1673
  };
1668
1674
  this.#create = (index, isNew) => {
1669
- const child = create(schema, { ...childCommonOptions, index, new: isNew });
1675
+ const child = create(
1676
+ { ...schema, creatable: true },
1677
+ { ...childCommonOptions, index, new: isNew },
1678
+ );
1670
1679
  child.index = index;
1671
1680
  return child;
1672
1681
  };
@@ -4064,8 +4073,7 @@
4064
4073
  }
4065
4074
  }
4066
4075
 
4067
- /** @import { Component, Relatedness } from '../types.mjs' */
4068
- /** @import Store from '../Store/index.mjs' */
4076
+ /** @import { Component } from '../types.mjs' */
4069
4077
  /** @import Environment from './Environment/index.mjs' */
4070
4078
  /** @import * as Layout from '../Layout/index.mjs' */
4071
4079
 
@@ -4235,7 +4243,8 @@
4235
4243
  }
4236
4244
  }
4237
4245
 
4238
- /** @import { Component, Relatedness } from '../types.mjs' */
4246
+ /** @import { Component } from '../types.mjs' */
4247
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
4239
4248
  /** @import { ComponentHandler } from './types.mjs' */
4240
4249
  /** @import Store from '../Store/index.mjs' */
4241
4250
  /** @import Environment from './Environment/index.mjs' */
@@ -4246,7 +4255,7 @@
4246
4255
  * @param {Component | string} component
4247
4256
  * @param {Environment} env
4248
4257
  * @param {Store?} store
4249
- * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
4258
+ * @param {((store: Store, el: Element | StoreLayout.Relatedness) => () => void)?} [relate]
4250
4259
  * @returns
4251
4260
  */
4252
4261
  function createContext(component, env, store, relate) {
@@ -5124,7 +5133,7 @@
5124
5133
  * @param {Record<string, [Layout.Template, Environment]>} templates
5125
5134
  * @param {string[]} componentPath
5126
5135
  * @param {Record<string, Enhancement>} enhancements
5127
- * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
5136
+ * @param {((store: Store, el: Element | StoreLayout.Relatedness) => () => void)?} [relate]
5128
5137
  * @param {Component.Getter?} [getComponent]
5129
5138
  */
5130
5139
  function renderNode(layout, parent, next, env, templates, componentPath, enhancements, relate, getComponent) {
@@ -5206,7 +5215,7 @@
5206
5215
  * @param {Record<string, [Layout.Template, Environment]>} parentTemplates
5207
5216
  * @param {string[]} componentPath
5208
5217
  * @param {Record<string, Enhancement>} enhancements
5209
- * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
5218
+ * @param {((store: Store, el: Element | StoreLayout.Relatedness) => () => void)?} [relate]
5210
5219
  * @param {Component.Getter?} [getComponent]
5211
5220
  * @returns {() => void}
5212
5221
  */
@@ -5271,7 +5280,7 @@
5271
5280
  * @param {Record<string, [Layout.Template, Environment]>} templates
5272
5281
  * @param {string[]} componentPath
5273
5282
  * @param {Record<string, Enhancement>} enhancements
5274
- * @param {((store: Store, el: Element | Relatedness) => () => void)?} [relate]
5283
+ * @param {((store: Store, el: Element | StoreLayout.Relatedness) => () => void)?} [relate]
5275
5284
  * @param {Component.Getter?} [getComponent]
5276
5285
  * @returns {() => void}
5277
5286
  */
@@ -5294,7 +5303,7 @@
5294
5303
  * @param {object} [options] 选项
5295
5304
  * @param {Record<string, Store | {get?(): any; set?(v: any): void; exec?(...p: any[]): any; calc?(...p: any[]): any }>} [options.global] 全局数据
5296
5305
  * @param {Component.Getter?} [options.component] 自定义组件
5297
- * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate] 关联函数
5306
+ * @param {(store: Store, el: Element | StoreLayout.Relatedness) => () => void} [options.relate] 关联函数
5298
5307
  * @param {Record<string, Enhancement>} [options.enhancements] 增强信息
5299
5308
  * @returns {() => void}
5300
5309
  */
@@ -5307,7 +5316,7 @@
5307
5316
  }
5308
5317
 
5309
5318
  /** @import { Store } from '../Store/index.mjs' */
5310
- /** @import { StoreLayout } from '../types.mjs' */
5319
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
5311
5320
 
5312
5321
  /**
5313
5322
  *
@@ -5333,7 +5342,7 @@
5333
5342
  * @param {Store} store
5334
5343
  * @param {Node} node
5335
5344
  * @param {StoreLayout.Options?} options
5336
- * @param {StoreLayout<T>?} [layout]
5345
+ * @param {StoreLayout<T>} layout
5337
5346
  * @param {Node} [anchor]
5338
5347
  * @param {(child?: Store<any, any> | undefined) => void} [dragenter]
5339
5348
  * @returns {void}
@@ -5347,8 +5356,10 @@
5347
5356
  const field = node.getAttribute('name') || '';
5348
5357
  const mode = node.getAttribute('mode') || '';
5349
5358
  const fieldStore = field ? store.child(field) : store;
5350
- const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) || null : null;
5351
5359
  if (!fieldStore) { return; }
5360
+ const fieldLayout = field
5361
+ ? layout?.fields?.find(createFieldFilter(field)) || fieldStore.layout
5362
+ : { ...layout, html: '' };
5352
5363
  switch (mode) {
5353
5364
  case 'grid': {
5354
5365
  const el = Form(store, fieldRenderer, fieldLayout, options);
@@ -5356,7 +5367,7 @@
5356
5367
  return;
5357
5368
  }
5358
5369
  }
5359
- const res = fieldRenderer(fieldStore, layout?.renderer, options);
5370
+ const res = fieldRenderer(fieldStore, layout.renderer, options);
5360
5371
  if (res) {
5361
5372
  node.replaceWith(res);
5362
5373
  return;
@@ -5389,7 +5400,9 @@
5389
5400
  return;
5390
5401
  }
5391
5402
  node.removeAttribute('nl-form-field');
5392
- const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) : layout;
5403
+ const fieldLayout = field
5404
+ ? layout.fields?.find(createFieldFilter(field)) || fieldStore.layout
5405
+ : { ...layout, html: '' };
5393
5406
  if (!array) {
5394
5407
  renderHtml(fieldStore, fieldRenderer, node, options, fieldLayout, anchor);
5395
5408
  return;
@@ -5554,26 +5567,32 @@
5554
5567
  * @template T
5555
5568
  * @param {Store<any, any>} store
5556
5569
  * @param {StoreLayout.Renderer<T>} fieldRenderer
5557
- * @param {StoreLayout.Field<T>?} layout
5570
+ * @param {StoreLayout<T>} layout
5558
5571
  * @param {StoreLayout.Options?} options
5559
5572
  * @returns {ParentNode?}
5560
5573
  */
5561
5574
  function FormFieldInline(store, fieldRenderer, layout, options) {
5562
5575
  if (options?.signal?.aborted) { return null; }
5563
- return fieldRenderer(store, layout?.renderer, options);
5576
+ const html = layout.html;
5577
+ if (html) {
5578
+ const content = getHtmlContent(html);
5579
+ renderHtml(store, fieldRenderer, content, options, layout);
5580
+ return content;
5581
+ }
5582
+ return fieldRenderer(store, layout.renderer, options);
5564
5583
  }
5565
5584
 
5566
5585
  /** @import { Store } from '../Store/index.mjs' */
5567
- /** @import { StoreLayout } from '../types.mjs' */
5586
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
5568
5587
 
5569
5588
  /**
5570
5589
  *
5571
5590
  * @template T
5572
5591
  * @param {Store<any, any>} store
5573
5592
  * @param {StoreLayout.Renderer<T>} fieldRenderer
5574
- * @param {StoreLayout.Field<T>?} layout
5593
+ * @param {StoreLayout.Field<T>} layout
5575
5594
  * @param {object} option
5576
- * @param {StoreLayout.Column[]} option.columns
5595
+ * @param {StoreLayout.Column<T>[]} option.columns
5577
5596
  * @param {() => void} option.remove
5578
5597
  * @param {() => void} option.dragenter
5579
5598
  * @param {() => void} option.dragstart
@@ -5583,8 +5602,8 @@
5583
5602
  * @returns {HTMLTableSectionElement}
5584
5603
  */
5585
5604
  function Line(store, fieldRenderer, layout, {
5586
- columns,
5587
- remove, dragenter, dragstart, dragend, deletable
5605
+ columns, deletable,
5606
+ remove, dragenter, dragstart, dragend,
5588
5607
  }, options) {
5589
5608
  const root = document.createElement('tbody');
5590
5609
  root.addEventListener('dragenter', () => {
@@ -5608,6 +5627,7 @@
5608
5627
  const body = root.appendChild(document.createElement('tr'));
5609
5628
  const main = body.appendChild(document.createElement('td'));
5610
5629
  main.colSpan = columns.length;
5630
+ main.appendChild(form);
5611
5631
  body.hidden = true;
5612
5632
  trigger = () => {
5613
5633
  if (body.hidden) {
@@ -5646,13 +5666,13 @@
5646
5666
 
5647
5667
  }
5648
5668
 
5649
- for (const name of columns) {
5650
- const { actions, field, pattern } = name;
5669
+ for (const column of columns) {
5670
+ const { actions, field, pattern } = column;
5651
5671
  if (!actions?.length) {
5652
5672
  const td = head.appendChild(document.createElement('td'));
5653
5673
  const child = field && store.child(field);
5654
5674
  if (child) {
5655
- const el = FormFieldInline(child, fieldRenderer, null, options);
5675
+ const el = FormFieldInline(child, fieldRenderer, column, options);
5656
5676
  if (el) { td.appendChild(el); }
5657
5677
  }
5658
5678
  continue;
@@ -5683,7 +5703,7 @@
5683
5703
  const del = handle.appendChild(document.createElement('button'));
5684
5704
  del.classList.add('NeeloongForm-table-remove');
5685
5705
  del.addEventListener('click', remove);
5686
- watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5706
+ watch(() => !deletable.get(), disabled => {
5687
5707
  del.disabled = disabled;
5688
5708
  }, true, options.signal);
5689
5709
  continue;
@@ -5700,56 +5720,26 @@
5700
5720
  return root;
5701
5721
  }
5702
5722
 
5703
- /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5704
- /** @import { StoreLayout } from '../types.mjs' */
5723
+ /** @import { ArrayStore } from '../Store/index.mjs' */
5724
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
5705
5725
 
5706
- /**
5707
- *
5708
- * @param {AbortSignal | null | undefined} signal
5709
- * @param {HTMLElement} parent
5710
- * @param {StoreLayout.Column[]} columns
5711
- * @param {() => any} add
5712
- * @param {{get(): boolean}} addable
5713
- * @param {boolean?} [editable]
5714
- * @returns {void}
5715
- */
5716
- function renderHead(signal, parent, columns, add, addable, editable) {
5717
- const tr = parent.appendChild(document.createElement('tr'));
5718
- for (const { action, actions, width, label } of columns) {
5719
- const th = tr.appendChild(document.createElement('th'));
5720
- if (width) { th.setAttribute('width', `${width}`); }
5721
- if (![action, actions].flat().includes('add')) {
5722
- th.innerText = label || '';
5723
- continue;
5724
- }
5725
- if (!editable) { continue; }
5726
- const button = th.appendChild(document.createElement('button'));
5727
- button.addEventListener('click', add);
5728
- button.classList.add('NeeloongForm-table-add');
5729
- watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true, signal);
5730
- }
5731
- }
5732
5726
  /**
5733
5727
  *
5734
5728
  * @template T
5735
5729
  * @param {ArrayStore} store
5736
- * @param {StoreLayout.Renderer<T>} fieldRenderer
5737
- * @param {StoreLayout.Field<T>?} layout
5738
- * @param {StoreLayout.Options?} options
5739
- * @returns {HTMLTableElement?}
5730
+ * @param {StoreLayout.Field<T>} layout
5731
+ * @param {StoreLayout.Action[]} actionOptions
5732
+ * @param {(fields: { field: string; width: any; label: any; }[]) => StoreLayout.Column<T>[]} createDefault
5733
+ * @returns {StoreLayout.Column<T>[]}
5740
5734
  */
5741
- function Table(store, fieldRenderer, layout, options) {
5742
- if (options?.signal?.aborted) { return null; }
5743
- const headerColumns = layout?.columns;
5735
+ function getColumns(store, layout, actionOptions, createDefault) {
5744
5736
  const fieldList = Object.entries(store.type || {})
5745
5737
  .filter(([k, v]) => typeof v?.type !== 'object')
5746
5738
  .map(([field, { width, label }]) => ({ field, width, label }));
5747
- /** @type {StoreLayout.Column[]} */
5748
- let columns = [];
5739
+ const headerColumns = layout.columns;
5749
5740
  if (Array.isArray(headerColumns)) {
5750
5741
  const map = new Map(fieldList.map(v => [v.field, v]));
5751
-
5752
- /** @type {(StoreLayout.Column | null)[]} */
5742
+ /** @type {(StoreLayout.Column<T> | null)[]} */
5753
5743
  const allColumns = headerColumns.map(v => {
5754
5744
  if (!v) { return null; }
5755
5745
  if (typeof v === 'number') { return { placeholder: v }; }
@@ -5757,7 +5747,7 @@
5757
5747
  if (typeof v !== 'object') { return null; }
5758
5748
  if (Array.isArray(v)) {
5759
5749
  /** @type {Set<StoreLayout.Action>} */
5760
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
5750
+ const options = new Set(actionOptions);
5761
5751
  const actions = v.filter(v => options.delete(v));
5762
5752
  if (!actions) { return null; }
5763
5753
  return { actions };
@@ -5769,32 +5759,82 @@
5769
5759
  return { field, placeholder, width, label: label || define.label };
5770
5760
  }
5771
5761
  }
5772
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial']);
5762
+ const options = new Set(actionOptions);
5773
5763
  const allActions = [action, actions].flat().filter(v => v && options.delete(v));
5774
5764
  if (allActions.length) {
5775
5765
  return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label };
5776
5766
  }
5777
- // if (pattern) {
5778
- // return { pattern, placeholder, width, label };
5779
- // }
5767
+ if (pattern) {
5768
+ return { pattern, placeholder, width, label };
5769
+ }
5770
+ if (placeholder || width) {
5771
+ return { placeholder, width, label };
5772
+ }
5780
5773
  return null;
5781
5774
  });
5782
- columns = /** @type {StoreLayout.Column[]} */(allColumns.filter(Boolean));
5775
+ const columns = /** @type {StoreLayout.Column<T>[]} */(allColumns.filter(Boolean));
5776
+ if (columns.length) { return columns; }
5777
+ }
5778
+ return createDefault(fieldList);
5779
+ }
5780
+
5781
+ /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5782
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
5783
5783
 
5784
+ /**
5785
+ *
5786
+ * @template T
5787
+ * @param {AbortSignal | null | undefined} signal
5788
+ * @param {HTMLElement} parent
5789
+ * @param {StoreLayout.Column<T>[]} columns
5790
+ * @param {() => any} add
5791
+ * @param {{get(): boolean}} addable
5792
+ * @param {boolean?} [editable]
5793
+ * @returns {void}
5794
+ */
5795
+ function renderHead(signal, parent, columns, add, addable, editable) {
5796
+ const tr = parent.appendChild(document.createElement('tr'));
5797
+ for (const { action, actions, width, label } of columns) {
5798
+ const th = tr.appendChild(document.createElement('th'));
5799
+ if (width) { th.setAttribute('width', `${width}`); }
5800
+ if (![action, actions].flat().includes('add')) {
5801
+ th.innerText = label || '';
5802
+ continue;
5803
+ }
5804
+ if (!editable) { continue; }
5805
+ const button = th.appendChild(document.createElement('button'));
5806
+ button.addEventListener('click', add);
5807
+ button.classList.add('NeeloongForm-table-add');
5808
+ watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true, signal);
5784
5809
  }
5785
- if (!columns.length) {
5786
- columns = [
5810
+ }
5811
+
5812
+ /**
5813
+ *
5814
+ * @template T
5815
+ * @param {ArrayStore} store
5816
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
5817
+ * @param {StoreLayout.Field<T>} layout
5818
+ * @param {StoreLayout.Options?} options
5819
+ * @returns {HTMLTableElement?}
5820
+ */
5821
+ function Table(store, fieldRenderer, layout, options) {
5822
+ if (options?.signal?.aborted) { return null; }
5823
+ const columns = getColumns(
5824
+ store,
5825
+ layout,
5826
+ ['add', 'move', 'trigger', 'remove', 'serial'],
5827
+ fields => [
5787
5828
  { actions: ['add', 'trigger', 'move', 'remove', 'serial'] },
5788
- ...fieldList.slice(0, 3),
5789
- ];
5790
- }
5829
+ ...fields.slice(0, 3),
5830
+ ],
5831
+ );
5791
5832
 
5792
5833
  const table = document.createElement('table');
5793
5834
  table.classList.add('NeeloongForm-table');
5794
5835
  const thead = table.appendChild(document.createElement('thead'));
5795
5836
 
5796
- const addable = new exports.Signal.Computed(() => store.addable);
5797
- const deletable = { get: () => Boolean(options?.editable) };
5837
+ const addable = new exports.Signal.Computed(() => !store.readonly && !store.disabled && store.addable);
5798
5838
  function add() {
5799
5839
  const data = {};
5800
5840
  store.add(data);
@@ -5832,7 +5872,7 @@
5832
5872
 
5833
5873
  }
5834
5874
  renderHead(options?.signal, thead, columns, add, addable, Boolean(options?.editable));
5835
- switch (layout?.tableFoot) {
5875
+ switch (layout.tableFoot) {
5836
5876
  default:
5837
5877
  case 'header': {
5838
5878
  const tfoot = table.appendChild(document.createElement('tfoot'));
@@ -5866,11 +5906,11 @@
5866
5906
  const ac = new AbortController();
5867
5907
  const el = Line(child, fieldRenderer, layout, {
5868
5908
  columns,
5909
+ deletable: new exports.Signal.Computed(() => !store.readonly && !store.disabled && child.removable),
5869
5910
  remove: remove.bind(null, child),
5870
5911
  dragenter: dragenter.bind(null, child),
5871
5912
  dragstart: dragstart.bind(null, child),
5872
5913
  dragend,
5873
- deletable,
5874
5914
  }, {
5875
5915
  ...options,
5876
5916
  signal: options?.signal ? AbortSignal.any([options?.signal, ac.signal]) : ac.signal,
@@ -5896,16 +5936,16 @@
5896
5936
  return table;
5897
5937
  }
5898
5938
 
5899
- /** @import { StoreLayout } from '../types.mjs' */
5939
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
5900
5940
 
5901
5941
  /**
5902
5942
  *
5903
5943
  * @param {HTMLElement} root
5904
- * @param {StoreLayout.Grid?} [layout]
5944
+ * @param {StoreLayout.Grid} layout
5905
5945
  * @returns {void}
5906
5946
  */
5907
5947
  function bindGrid(root, layout) {
5908
- const { colStart, colSpan, colEnd, rowStart, rowSpan, rowEnd } = layout || {};
5948
+ const { colStart, colSpan, colEnd, rowStart, rowSpan, rowEnd } = layout;
5909
5949
  root.classList.add(`NeeloongForm-item-grid`);
5910
5950
  if (colStart && colEnd) {
5911
5951
  root.style.gridColumn = `${colStart} / ${colEnd}`;
@@ -5923,7 +5963,7 @@
5923
5963
  }
5924
5964
  }
5925
5965
 
5926
- /** @import { StoreLayout } from '../types.mjs' */
5966
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
5927
5967
 
5928
5968
  /**
5929
5969
  * @typedef {object} CellValues
@@ -5961,7 +6001,7 @@
5961
6001
  /**
5962
6002
  *
5963
6003
  * @param {AbortSignal | null | undefined} signal
5964
- * @param {StoreLayout.Grid?} [layout]
6004
+ * @param {StoreLayout.Grid} layout
5965
6005
  * @param {CellValues} [values]
5966
6006
  * @param {StoreLayout.Grid['cell']?} [defCell]
5967
6007
  * @param {boolean?} [blockOnly]
@@ -6007,7 +6047,7 @@
6007
6047
  }
6008
6048
  return null;
6009
6049
  }
6010
- const [root, content] = create(layout?.cell) || create(defCell) || createStdCell(signal, values);
6050
+ const [root, content] = create(layout.cell) || create(defCell) || createStdCell(signal, values);
6011
6051
  root.classList.add('NeeloongForm-item');
6012
6052
  effect(() => {
6013
6053
  if (values?.error) {
@@ -6028,7 +6068,7 @@
6028
6068
 
6029
6069
  /** @import { Store } from '../Store/index.mjs' */
6030
6070
  /** @import { State } from './Tree.mjs' */
6031
- /** @import { StoreLayout } from '../types.mjs' */
6071
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
6032
6072
 
6033
6073
  /**
6034
6074
  *
@@ -6036,10 +6076,11 @@
6036
6076
  * @param {Store<any, any>} store
6037
6077
  * @param {Signal.State<Store<any, any>?>} currentStore
6038
6078
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6039
- * @param {StoreLayout.Field<T>?} layout
6079
+ * @param {StoreLayout.Field<T>} layout
6040
6080
  * @param {Signal.State<State>} state
6041
6081
  * @param {object} option
6042
- * @param {StoreLayout.Column[]} option.columns
6082
+ * @param {{get(): boolean}} option.addable
6083
+ * @param {StoreLayout.Column<T>[]} option.columns
6043
6084
  * @param {() => void} option.remove
6044
6085
  * @param {(el: HTMLElement) => () => void} option.dragenter
6045
6086
  * @param {() => void} option.dragstart
@@ -6053,8 +6094,8 @@
6053
6094
  */
6054
6095
  function TreeLine(
6055
6096
  store, currentStore, fieldRenderer, layout, state, {
6056
- columns,
6057
- remove, dragenter, dragstart, dragend, deletable, addNode, drop, createDetails,
6097
+ columns, addable, deletable,
6098
+ remove, dragenter, dragstart, dragend, addNode, drop, createDetails,
6058
6099
  }, options) {
6059
6100
  const root = document.createElement('div');
6060
6101
  root.addEventListener('dragstart', (event) => {
@@ -6115,9 +6156,9 @@
6115
6156
  }
6116
6157
  close = createDetails(store);
6117
6158
  }
6118
- const moveStart = layout?.mainMethod === 'move' ? pointerdown : null;
6119
- const click = moveStart ? null : layout?.mainMethod === 'collapse' ? switchCollapsed
6120
- : layout?.mainMethod === 'trigger' ? trigger : open;
6159
+ const moveStart = layout.mainMethod === 'move' ? pointerdown : null;
6160
+ const click = moveStart ? null : layout.mainMethod === 'collapse' ? switchCollapsed
6161
+ : layout.mainMethod === 'trigger' ? trigger : open;
6121
6162
 
6122
6163
  const line = root.appendChild(document.createElement('div'));
6123
6164
  line.classList.add('NeeloongForm-tree-line');
@@ -6140,7 +6181,8 @@
6140
6181
  dropFront.addEventListener('dragleave', () => dragleave());
6141
6182
  dropChildren.addEventListener('dragleave', () => dragleave());
6142
6183
 
6143
- for (const { actions, pattern, placeholder, width, field } of columns) {
6184
+ for (const column of columns) {
6185
+ const { actions, pattern, placeholder, width, field } = column;
6144
6186
  if (!actions?.length) {
6145
6187
  const td = line.appendChild(document.createElement('div'));
6146
6188
  td.classList.add('NeeloongForm-tree-cell');
@@ -6149,7 +6191,7 @@
6149
6191
  if (field) {
6150
6192
  const child = store.child(field);
6151
6193
  if (!child) { continue; }
6152
- const el = FormFieldInline(child, fieldRenderer, null, { ...options, editable: false });
6194
+ const el = FormFieldInline(child, fieldRenderer, column, { ...options, editable: false });
6153
6195
  if (el) { td.appendChild(el); }
6154
6196
  continue;
6155
6197
  }
@@ -6198,7 +6240,7 @@
6198
6240
  const move = line.appendChild(document.createElement('button'));
6199
6241
  move.classList.add('NeeloongForm-tree-add');
6200
6242
  move.addEventListener('click', addNode);
6201
- watch(() => store.readonly || store.disabled, disabled => {
6243
+ watch(() => !addable.get(), disabled => {
6202
6244
  move.disabled = disabled;
6203
6245
  }, true, options.signal);
6204
6246
  continue;
@@ -6208,7 +6250,7 @@
6208
6250
  const del = line.appendChild(document.createElement('button'));
6209
6251
  del.classList.add('NeeloongForm-tree-remove');
6210
6252
  del.addEventListener('click', remove);
6211
- watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
6253
+ watch(() => !deletable.get(), disabled => {
6212
6254
  del.disabled = disabled;
6213
6255
  }, true, options.signal);
6214
6256
  continue;
@@ -6248,7 +6290,7 @@
6248
6290
  }
6249
6291
 
6250
6292
  /** @import { Store, ArrayStore } from '../Store/index.mjs' */
6251
- /** @import { StoreLayout } from '../types.mjs' */
6293
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
6252
6294
 
6253
6295
  const verticalWritingMode = new Set([
6254
6296
  'vertical-lr', 'vertical-rl', 'sideways-lr', 'sideways-rl',
@@ -6357,62 +6399,22 @@
6357
6399
  * @template T
6358
6400
  * @param {ArrayStore} store
6359
6401
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6360
- * @param {StoreLayout.Field<T>?} layout
6402
+ * @param {StoreLayout.Field<T>} layout
6361
6403
  * @param {StoreLayout.Options?} options
6362
6404
  * @returns {HTMLElement?}
6363
6405
  */
6364
6406
  function Tree(store, fieldRenderer, layout, options) {
6365
6407
  if (options?.signal?.aborted) { return null; }
6366
- const headerColumns = layout?.columns;
6367
- const fieldList = Object.entries(store.type || {})
6368
- .filter(([k, v]) => typeof v?.type !== 'object')
6369
- .map(([field, { width, label }]) => ({ field, width, label }));
6370
- /** @type {StoreLayout.Column[]} */
6371
- let columns = [];
6372
- if (Array.isArray(headerColumns)) {
6373
- const map = new Map(fieldList.map(v => [v.field, v]));
6374
- /** @type {(StoreLayout.Column | null)[]} */
6375
- const allColumns = headerColumns.map(v => {
6376
- if (!v) { return null; }
6377
- if (typeof v === 'number') { return { placeholder: v }; }
6378
- if (typeof v === 'string') { return map.get(v) || null; }
6379
- if (typeof v !== 'object') { return null; }
6380
- if (Array.isArray(v)) {
6381
- /** @type {Set<StoreLayout.Action>} */
6382
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
6383
- const actions = v.filter(v => options.delete(v));
6384
- if (!actions) { return null; }
6385
- return { actions };
6386
- }
6387
- const { action, actions, field, placeholder, pattern, width, label } = v;
6388
- if (field) {
6389
- const define = map.get(field);
6390
- if (define) {
6391
- return { field, placeholder, width, label: label || define.label };
6392
- }
6393
- }
6394
- const options = new Set(['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse']);
6395
- const allActions = [action, actions].flat().filter(v => v && options.delete(v));
6396
- if (allActions.length) {
6397
- return { actions: /** @type {StoreLayout.Action[]} */(allActions), width, label };
6398
- }
6399
- if (pattern) {
6400
- return { pattern, placeholder, width, label };
6401
- }
6402
- if (placeholder || width) {
6403
- return { placeholder, width, label };
6404
- }
6405
- return null;
6406
- });
6407
- columns = /** @type {StoreLayout.Column[]} */(allColumns.filter(Boolean));
6408
- }
6409
- if (!columns.length) {
6410
- columns = [
6408
+ const columns = getColumns(
6409
+ store,
6410
+ layout,
6411
+ ['add', 'move', 'trigger', 'remove', 'serial', 'open', 'collapse'],
6412
+ fields => [
6411
6413
  { actions: ['collapse', 'move'] },
6412
- fieldList[0],
6414
+ fields[0],
6413
6415
  { actions: ['add', 'remove'] },
6414
- ];
6415
- }
6416
+ ]
6417
+ );
6416
6418
 
6417
6419
 
6418
6420
  const root = document.createElement('div');
@@ -6529,9 +6531,8 @@
6529
6531
  }
6530
6532
 
6531
6533
 
6532
- const levelKey = layout?.levelKey || 'level';
6533
- const addable = new exports.Signal.Computed(() => store.addable);
6534
- const deletable = { get: () => Boolean(options?.editable) };
6534
+ const levelKey = layout.levelKey || 'level';
6535
+ const addable = new exports.Signal.Computed(() => !store.readonly && !store.disabled && store.addable);
6535
6536
  /**
6536
6537
  *
6537
6538
  * @param {number} parent
@@ -6704,12 +6705,12 @@
6704
6705
  const elState = new exports.Signal.State(state);
6705
6706
  const ac = new AbortController();
6706
6707
  const el = TreeLine(child, detailsStore, fieldRenderer, layout, elState, {
6707
- columns,
6708
+ columns, addable,
6709
+ deletable: new exports.Signal.Computed(() => !store.readonly && !store.disabled && child.removable),
6708
6710
  remove: remove.bind(null, child),
6709
6711
  dragenter,
6710
6712
  dragstart: dragstart.bind(null, child),
6711
6713
  dragend,
6712
- deletable,
6713
6714
  addNode: () => addNode(Number(child.index)),
6714
6715
  createDetails,
6715
6716
  drop: drop.bind(null, child),
@@ -6747,7 +6748,7 @@
6747
6748
  * @param {Store<any, any>} store
6748
6749
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6749
6750
  * @param {StoreLayout.Options?} options
6750
- * @param {StoreLayout.Field<T>?} layout
6751
+ * @param {StoreLayout.Field<T>} layout
6751
6752
  * @returns {ParentNode}
6752
6753
  */
6753
6754
  function Html(html, store, fieldRenderer, options, layout) {
@@ -6761,7 +6762,7 @@
6761
6762
  * @param {StoreLayout.Field<T>['arrayStyle']?} arrayStyle
6762
6763
  * @param {ArrayStore} store
6763
6764
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6764
- * @param {StoreLayout.Field<T>?} layout
6765
+ * @param {StoreLayout.Field<T>} layout
6765
6766
  * @param {StoreLayout.Options?} options
6766
6767
  * @returns {HTMLElement?}
6767
6768
  */
@@ -6778,14 +6779,14 @@
6778
6779
  * @template T
6779
6780
  * @param {Store<any, any>} store
6780
6781
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6781
- * @param {StoreLayout.Field<T>?} layout
6782
+ * @param {StoreLayout.Field<T>} layout
6782
6783
  * @param {StoreLayout.Options?} options
6783
6784
  * @returns {ParentNode}
6784
6785
  */
6785
6786
  function FormField(store, fieldRenderer, layout, options) {
6786
6787
  const { type } = store;
6787
6788
  const isObject = Boolean(type && typeof type === 'object');
6788
- const html = layout?.html;
6789
+ const html = layout.html;
6789
6790
  /** @type {StoreLayout.Grid['cell']} */
6790
6791
  const cellType = isObject
6791
6792
  ? store instanceof ArrayStore ? 'collapse' : 'fieldset'
@@ -6796,8 +6797,8 @@
6796
6797
  /** @type {false | ParentNode | null} */
6797
6798
  const r =
6798
6799
  html && Html(html, store, fieldRenderer, options, layout)
6799
- || fieldRenderer(store, layout?.renderer, options)
6800
- || store instanceof ArrayStore && renderArrayCell(layout?.arrayStyle, store, fieldRenderer, layout, options)
6800
+ || fieldRenderer(store, layout.renderer, options)
6801
+ || store instanceof ArrayStore && renderArrayCell(layout.arrayStyle, store, fieldRenderer, layout, options)
6801
6802
  || isObject && Form(store, fieldRenderer, layout, options);
6802
6803
  if (r) {
6803
6804
  content.appendChild(r);
@@ -6806,7 +6807,7 @@
6806
6807
  }
6807
6808
 
6808
6809
  /** @import { Store } from '../Store/index.mjs' */
6809
- /** @import { StoreLayout } from '../types.mjs' */
6810
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
6810
6811
 
6811
6812
  /**
6812
6813
  *
@@ -6843,7 +6844,7 @@
6843
6844
  }
6844
6845
 
6845
6846
  /** @import { Store } from '../Store/index.mjs' */
6846
- /** @import { StoreLayout } from '../types.mjs' */
6847
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
6847
6848
 
6848
6849
  /**
6849
6850
  *
@@ -6865,7 +6866,7 @@
6865
6866
  }
6866
6867
 
6867
6868
  /** @import { Store } from '../Store/index.mjs' */
6868
- /** @import { StoreLayout } from '../types.mjs' */
6869
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
6869
6870
 
6870
6871
  /**
6871
6872
  *
@@ -6883,7 +6884,9 @@
6883
6884
  if (item.type === 'html') {
6884
6885
  return FormHtml(store, fieldRenderer, item, options);
6885
6886
  }
6886
- const fieldStore = store.child(item.field);
6887
+ const field = item.field;
6888
+ if (!field) { return null; }
6889
+ const fieldStore = store.child(field);
6887
6890
  if (fieldStore) {
6888
6891
  return FormField(fieldStore, fieldRenderer, item, options);
6889
6892
  }
@@ -6895,7 +6898,7 @@
6895
6898
  * @template T
6896
6899
  * @param {Store<any, any>} store
6897
6900
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6898
- * @param {StoreLayout<T>?} layout
6901
+ * @param {StoreLayout<T>} layout
6899
6902
  * @param {StoreLayout.Options?} options
6900
6903
  * @param {HTMLElement} [parent]
6901
6904
  * @returns {HTMLElement?}
@@ -6904,8 +6907,8 @@
6904
6907
  if (options?.signal?.aborted) { return null; }
6905
6908
  const root = parent instanceof HTMLElement ? parent : document.createElement('div');
6906
6909
  root.classList.add('NeeloongForm');
6907
- const fieldLayouts = layout?.fields;
6908
- if (fieldLayouts) {
6910
+ const fieldLayouts = layout.fields || store.layout.fields;
6911
+ if (fieldLayouts?.length) {
6909
6912
  for (const fieldTemplate of fieldLayouts) {
6910
6913
  const el = FormItem(store, fieldRenderer, fieldTemplate, options);
6911
6914
  if (el) { root.appendChild(el); }
@@ -6913,7 +6916,7 @@
6913
6916
  } else {
6914
6917
  const fields = [...store].map(([, v]) => v);
6915
6918
  for (const field of fields) {
6916
- const el = FormField(field, fieldRenderer, null, options);
6919
+ const el = FormField(field, fieldRenderer, field.layout, options);
6917
6920
  if (el) { root.appendChild(el); }
6918
6921
  }
6919
6922
  }
@@ -6922,7 +6925,7 @@
6922
6925
  }
6923
6926
 
6924
6927
  /** @import { Store } from '../Store/index.mjs' */
6925
- /** @import { StoreLayout } from '../types.mjs' */
6928
+ /** @import { StoreLayout } from '../StoreLayout.types.mjs' */
6926
6929
 
6927
6930
  /**
6928
6931
  *
@@ -6930,19 +6933,20 @@
6930
6933
  * @param {Store} store
6931
6934
  * @param {StoreLayout.Renderer<T>} fieldRenderer
6932
6935
  * @param {HTMLElement} root
6933
- * @param {StoreLayout<T>?} [layout]
6936
+ * @param {StoreLayout<T>} layout
6934
6937
  * @param {StoreLayout.Options & {clone?: boolean} | null} [options]
6935
6938
  * @returns {void}
6936
6939
  */
6937
6940
  function renderStore(store, fieldRenderer, root, layout, options) {
6938
6941
  if (options?.signal?.aborted) { return; }
6939
- const html = layout?.html;
6942
+ const storeLayout = layout || store.layout;
6943
+ const html = storeLayout.html;
6940
6944
  if (!html) {
6941
- Form(store, fieldRenderer, layout || null, options || null, root);
6945
+ Form(store, fieldRenderer, storeLayout, options || null, root);
6942
6946
  return;
6943
6947
  }
6944
6948
  const content = getHtmlContent(html);
6945
- renderHtml(store, fieldRenderer, content, options || null, layout);
6949
+ renderHtml(store, fieldRenderer, content, options || null, storeLayout);
6946
6950
  root.appendChild(content);
6947
6951
  options?.signal?.addEventListener('abort', () => {
6948
6952
  root.removeChild(content);