@neeloong/form 0.15.0 → 0.17.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.15.0
2
+ * @neeloong/form v0.17.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -1174,13 +1174,22 @@ declare class ArrayStore<T = any, M = any> extends Store<(T | null)[], M> {
1174
1174
  #private;
1175
1175
  }
1176
1176
 
1177
- type GridFormItemTemplate = {
1177
+ type GridFormItemTemplateTableAction = "add" | "move" | "trigger" | "remove" | "serial";
1178
+ type GridFieldLayout = {
1178
1179
  field: string;
1179
- subFields?: GridFormTemplate | null | undefined;
1180
- span: number;
1181
- headers?: string[] | undefined;
1180
+ fields?: GridFieldLayout[] | null | undefined;
1181
+ tableFoot?: "add" | "header" | "none" | undefined;
1182
+ columns?: (string | GridFormItemTemplateTableAction[])[] | undefined;
1183
+ colStart?: number | undefined;
1184
+ colSpan?: number | undefined;
1185
+ colEnd?: number | undefined;
1186
+ rowStart?: number | undefined;
1187
+ rowSpan?: number | undefined;
1188
+ rowEnd?: number | undefined;
1189
+ };
1190
+ type GridLayout = {
1191
+ fields?: GridFieldLayout[] | null | undefined;
1182
1192
  };
1183
- type GridFormTemplate = GridFormItemTemplate[];
1184
1193
  type Options = object;
1185
1194
  type FieldRenderer = (store: Store<any, any>, component: any, Options: Options) => [HTMLElement, () => void];
1186
1195
 
@@ -1190,9 +1199,9 @@ type FieldRenderer = (store: Store<any, any>, component: any, Options: Options)
1190
1199
  * @param {HTMLElement} root
1191
1200
  * @param {FieldRenderer} fieldRenderer
1192
1201
  * @param {boolean} editable
1193
- * @param {GridFormTemplate?} [template]
1202
+ * @param {GridLayout?} [layout]
1194
1203
  */
1195
- declare function _default(store: Store, root: HTMLElement, fieldRenderer: FieldRenderer, editable: boolean, template?: GridFormTemplate | null): () => void;
1204
+ declare function _default(store: Store, root: HTMLElement, fieldRenderer: FieldRenderer, editable: boolean, layout?: GridLayout | null): () => void;
1196
1205
 
1197
1206
  /**
1198
1207
  * @param {Store} store 存储实例
@@ -1244,4 +1253,4 @@ declare function effect(fn: () => void): () => void;
1244
1253
  */
1245
1254
  declare function renderHtml(renderField: (store: Store<any, any>, component: any) => [HTMLElement, () => void] | null, store: Store, root: HTMLElement, html?: string | ParentNode, clone?: boolean): () => void;
1246
1255
 
1247
- export { ArrayStore, type AsyncValidator, Component, Enhancement, type FieldRenderer, type GridFormItemTemplate, type GridFormTemplate, index_d as Layout, ObjectStore, type Options, type Ref, type Relatedness, Schema, Store, type Validator, type VerifyError, effect, render, _default as renderGrid, renderHtml, watch };
1256
+ export { ArrayStore, type AsyncValidator, Component, Enhancement, type FieldRenderer, type GridFieldLayout, type GridFormItemTemplateTableAction, type GridLayout, index_d as Layout, ObjectStore, type Options, type Ref, type Relatedness, Schema, Store, type Validator, type VerifyError, effect, render, _default as renderGrid, renderHtml, watch };
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @neeloong/form v0.15.0
2
+ * @neeloong/form v0.17.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -5501,16 +5501,16 @@
5501
5501
 
5502
5502
  /** @import { Store } from '../Store/index.mjs' */
5503
5503
  /** @import { Relatedness } from '../types.mjs' */
5504
- /** @import { FieldRenderer, GridFormItemTemplate } from './types.mjs' */
5504
+ /** @import { FieldRenderer, GridFieldLayout, GridFormItemTemplateTableAction } from './types.mjs' */
5505
5505
 
5506
5506
  /**
5507
5507
  *
5508
5508
  * @param {Store<any, any>} store
5509
5509
  * @param {FieldRenderer} fieldRenderer
5510
5510
  * @param {boolean} editable
5511
- * @param {GridFormItemTemplate?} template
5511
+ * @param {GridFieldLayout?} layout
5512
5512
  * @param {object} option
5513
- * @param {string[]} option.columns
5513
+ * @param {(string | GridFormItemTemplateTableAction[])[]} option.columns
5514
5514
  * @param {() => void} option.remove
5515
5515
  * @param {() => void} option.dragenter
5516
5516
  * @param {() => void} option.dragstart
@@ -5521,8 +5521,8 @@
5521
5521
 
5522
5522
  * @returns {[HTMLTableSectionElement, () => void]}
5523
5523
  */
5524
- function Line(store, fieldRenderer, editable, template, {
5525
- columns,
5524
+ function Line(store, fieldRenderer, editable, layout, {
5525
+ columns,
5526
5526
  remove, dragenter, dragstart, dragend, deletable
5527
5527
  }, options) {
5528
5528
  const root = document.createElement('tbody');
@@ -5536,72 +5536,109 @@
5536
5536
  root.addEventListener('dragend', dragend);
5537
5537
  const head = root.appendChild(document.createElement('tr'));
5538
5538
 
5539
- const handle = head.appendChild(document.createElement('th'));
5540
- handle.className = 'button-group';
5541
5539
  /** @type {(() => void)[]} */
5542
5540
  const destroyList = [];
5543
- for (const name of columns) {
5544
- const td = head.appendChild(document.createElement('td'));
5545
- const child = store.child(name);
5546
- if (!child) { continue }
5547
- const [el, destroy] = FormItem(child, fieldRenderer, editable, null, options, true);
5548
- destroyList.push(destroy);
5549
- td.appendChild(el);
5550
- }
5551
5541
 
5552
5542
 
5543
+ let trigger = () => { };
5544
+ /** @type {HTMLButtonElement[]} */
5545
+ const triggerList = [];
5546
+ if (columns.find(v => Array.isArray(v) && v.includes('trigger'))) {
5547
+ const body = root.appendChild(document.createElement('tr'));
5548
+ const main = body.appendChild(document.createElement('td'));
5549
+ main.colSpan = columns.length;
5553
5550
 
5554
- const body = root.appendChild(document.createElement('tr'));
5555
- const main = body.appendChild(document.createElement('td'));
5556
- main.colSpan = columns.length + 1;
5557
-
5558
- const subFields = template?.subFields;
5559
- const [form, destroy] = Form(store, fieldRenderer, editable, Array.isArray(subFields) ? subFields : null, options);
5560
- main.appendChild(form);
5561
- destroyList.push(destroy);
5551
+ const [form, destroy] = Form(store, fieldRenderer, editable, layout, options);
5552
+ main.appendChild(form);
5553
+ destroyList.push(destroy);
5554
+ body.hidden = true;
5555
+ trigger = function click() {
5556
+ if (body.hidden) {
5557
+ body.hidden = false;
5558
+ for (const ext of triggerList) {
5559
+ ext.classList.remove('NeeloongFormGrid-table-line-open');
5560
+ ext.classList.add('NeeloongFormGrid-table-line-close');
5561
+ }
5562
+ } else {
5563
+ body.hidden = true;
5564
+ for (const ext of triggerList) {
5565
+ ext.classList.remove('NeeloongFormGrid-table-line-close');
5566
+ ext.classList.add('NeeloongFormGrid-table-line-open');
5567
+ }
5568
+ }
5569
+ };
5562
5570
 
5571
+ }
5563
5572
 
5573
+ /**
5574
+ *
5575
+ * @param {PointerEvent} event
5576
+ */
5577
+ function pointerdown({ pointerId }) {
5578
+ root.draggable = true;
5579
+ /** @param {PointerEvent} event */
5580
+ function pointerup(event) {
5581
+ if (event.pointerId !== pointerId) { return; }
5582
+ if (!root) { return; }
5583
+ root.draggable = false;
5584
+ window.removeEventListener('pointerup', pointerup, { capture: true });
5585
+ window.removeEventListener('pointercancel', pointerup, { capture: true });
5586
+ }
5587
+ window.addEventListener('pointerup', pointerup, { capture: true });
5588
+ window.addEventListener('pointercancel', pointerup, { capture: true });
5564
5589
 
5565
- const ext = handle.appendChild(document.createElement('button'));
5566
- body.hidden = true;
5567
- ext.innerText = '+';
5568
- ext.addEventListener('click', () => {
5569
- if(body.hidden) {
5570
- body.hidden = false;
5571
- ext.innerText = '-';
5572
- } else {
5573
- body.hidden = true;
5574
- ext.innerText = '+';
5590
+ }
5575
5591
 
5592
+ for (const name of columns) {
5593
+ if (!Array.isArray(name)) {
5594
+ const td = head.appendChild(document.createElement('td'));
5595
+ const child = store.child(name);
5596
+ if (!child) { continue; }
5597
+ const [el, destroy] = FormItem(child, fieldRenderer, editable, null, options, true);
5598
+ destroyList.push(destroy);
5599
+ td.appendChild(el);
5600
+ continue;
5576
5601
  }
5577
- });
5578
- if (editable) {
5579
- const move = handle.appendChild(document.createElement('button'));
5580
- move.classList.add('GridForm-table-move');
5581
- move.addEventListener('pointerdown', ({pointerId}) => {
5582
- root.draggable = true;
5583
- /** @param {PointerEvent} event */
5584
- function pointerup(event) {
5585
- if (event.pointerId !== pointerId) { return }
5586
- if (!root) { return }
5587
- root.draggable = false;
5588
- window.removeEventListener('pointerup', pointerup, {capture: true});
5589
- window.removeEventListener('pointercancel', pointerup, {capture: true});
5602
+ const handle = head.appendChild(document.createElement('th'));
5603
+ handle.classList.add('NeeloongFormGrid-table-line-handle');
5604
+ for (const k of name) {
5605
+ switch (k) {
5606
+ case 'trigger': {
5607
+ const ext = handle.appendChild(document.createElement('button'));
5608
+ ext.classList.add('NeeloongFormGrid-table-line-open');
5609
+ triggerList.push(ext);
5610
+ ext.addEventListener('click', trigger);
5611
+ continue;
5612
+ }
5613
+ case 'move': {
5614
+ if (!editable) { continue; }
5615
+ const move = handle.appendChild(document.createElement('button'));
5616
+ move.classList.add('NeeloongFormGrid-table-move');
5617
+ move.addEventListener('pointerdown', pointerdown);
5618
+ destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5619
+ move.disabled = disabled;
5620
+ }, true));
5621
+ continue;
5622
+ }
5623
+ case 'remove': {
5624
+ if (!editable) { continue; }
5625
+ const del = handle.appendChild(document.createElement('button'));
5626
+ del.classList.add('NeeloongFormGrid-table-remove');
5627
+ del.addEventListener('click', remove);
5628
+ destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5629
+ del.disabled = disabled;
5630
+ }, true));
5631
+ continue;
5632
+ }
5633
+ case 'serial': {
5634
+ const serial = handle.appendChild(document.createElement('span'));
5635
+ serial.classList.add('NeeloongFormGrid-table-serial');
5636
+ continue;
5637
+ }
5590
5638
  }
5591
- window.addEventListener('pointerup', pointerup, {capture: true});
5592
- window.addEventListener('pointercancel', pointerup, {capture: true});
5593
- });
5594
- destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5595
- move.disabled = disabled;
5596
- }, true));
5597
- const del = handle.appendChild(document.createElement('button'));
5598
- del.classList.add('GridForm-table-remove');
5599
- // @ts-ignore
5600
- destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5601
- move.disabled = disabled;
5602
- }, true));
5603
- del.addEventListener('click', remove);
5639
+ }
5604
5640
  }
5641
+
5605
5642
  return [root, () => {
5606
5643
  for (const destroy of destroyList) {
5607
5644
  destroy();
@@ -5611,70 +5648,90 @@
5611
5648
 
5612
5649
  /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5613
5650
  /** @import { Relatedness } from '../types.mjs' */
5614
- /** @import { FieldRenderer, GridFormItemTemplate } from './types.mjs' */
5651
+ /** @import { FieldRenderer, GridFieldLayout, GridFormItemTemplateTableAction } from './types.mjs' */
5615
5652
 
5616
5653
  /**
5617
5654
  *
5618
5655
  * @param {HTMLElement} parent
5619
- * @param {[string, any][]} columns
5656
+ * @param {({ field: string; width: any; label: any; } | GridFormItemTemplateTableAction[])[]} columns
5620
5657
  * @param {() => any} add
5621
5658
  * @param {{get(): boolean}} addable
5622
5659
  * @param {boolean?} [editable]
5623
5660
  */
5624
5661
  function renderHead(parent, columns, add, addable, editable) {
5625
5662
  const tr = parent.appendChild(document.createElement('tr'));
5626
- const th = tr.appendChild(document.createElement('th'));
5627
- for (const [, { width, label }] of columns) {
5628
- const td = tr.appendChild(document.createElement('td'));
5629
- if (width) {
5630
- td.setAttribute('width', width);
5663
+ /** @type {(() => void)[]} */
5664
+ const destroyList = [];
5665
+ for (const col of columns) {
5666
+ if (!Array.isArray(col)) {
5667
+ const { width, label } = col;
5668
+ const td = tr.appendChild(document.createElement('th'));
5669
+ if (width) {
5670
+ td.setAttribute('width', width);
5671
+ }
5672
+ td.innerText = label;
5673
+ continue;
5674
+ }
5675
+ const th = tr.appendChild(document.createElement('th'));
5676
+ if (!editable) { continue; }
5677
+ for (const it of col) {
5678
+ switch (it) {
5679
+ case 'add':
5680
+ const button = th.appendChild(document.createElement('button'));
5681
+ button.addEventListener('click', add);
5682
+ button.classList.add('NeeloongFormGrid-table-add');
5683
+ destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5684
+ continue;
5685
+ }
5631
5686
  }
5632
- td.innerText = label;
5633
- }
5634
- if (!editable) {
5635
- return () => {}
5636
5687
  }
5637
- const button = th.appendChild(document.createElement('button'));
5638
- button.addEventListener('click', add);
5639
- button.classList.add('GridForm-table-add');
5640
- return watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true);
5688
+ return () => {
5689
+ for (const destroy of destroyList) {
5690
+ destroy();
5691
+ }
5692
+ };
5641
5693
  }
5642
5694
  /**
5643
5695
  *
5644
5696
  * @param {ArrayStore} store
5645
5697
  * @param {FieldRenderer} fieldRenderer
5646
5698
  * @param {boolean} editable
5647
- * @param {GridFormItemTemplate?} template
5699
+ * @param {GridFieldLayout?} layout
5648
5700
  * @param {object} options
5649
5701
  * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate]
5650
5702
  * @returns {[HTMLTableElement, () => void]}
5651
5703
  */
5652
- function Table(store, fieldRenderer, editable, template, options) {
5653
- const headerColumns = template?.headers;
5654
-
5655
- const fieldList = Object.entries(store.type || {});
5656
- /** @type {typeof fieldList} */
5704
+ function Table(store, fieldRenderer, editable, layout, options) {
5705
+ const headerColumns = layout?.columns;
5706
+ const fieldList = Object.entries(store.type || {})
5707
+ .filter(([k, v]) => typeof v?.type !== 'object')
5708
+ .map(([field, {width, label}]) => ({field, width, label}));
5709
+ /** @type {({ field: string; width: any; label: any; } | GridFormItemTemplateTableAction[])[]} */
5657
5710
  let columns = [];
5658
- if (headerColumns) {
5659
- const map = new Map(fieldList.map(v => [v[0], v]));
5660
- for (const c of headerColumns) {
5661
- const f = map.get(c);
5662
- if (f) { columns.push(f); }
5663
- }
5711
+ if (Array.isArray(headerColumns)) {
5712
+ const map = new Map(fieldList.map(v => [v.field, v]));
5713
+ columns = headerColumns.map(v => {
5714
+ if (typeof v === 'string') { return map.get(v) || [] }
5715
+ if (!Array.isArray(v)) { return []; }
5716
+ /** @type {Set<GridFormItemTemplateTableAction>} */
5717
+ const options = new Set(['add', 'move', 'trigger', 'remove', 'serial']);
5718
+ return v.filter(v => options.delete(v));
5719
+ }).filter(v => !Array.isArray(v) || v.length);
5664
5720
  }
5665
5721
  if (!columns.length) {
5666
- columns = fieldList
5667
- .filter(([, v]) => v.meta.headOrder)
5668
- .sort(([, a], [, b]) => (a.headOrder || 0) - (b.headOrder || 0));
5722
+ columns = [['add', 'trigger', 'move', 'remove', 'serial']];
5723
+ }
5724
+ if (!columns.find(v => !Array.isArray(v))) {
5725
+ columns.push(...fieldList.slice(0, 3));
5669
5726
  }
5670
- if (!columns.length) { columns = fieldList.filter(([k, v]) => typeof v?.type !== 'object').slice(0, 3); }
5727
+
5728
+
5671
5729
 
5672
5730
  const table = document.createElement('table');
5673
- table.className = 'GridForm-table';
5731
+ table.className = 'NeeloongFormGrid-table';
5674
5732
  const thead = table.appendChild(document.createElement('thead'));
5675
5733
 
5676
5734
 
5677
- const tfoot = table.appendChild(document.createElement('tfoot'));
5678
5735
 
5679
5736
  const addable = new exports.Signal.Computed(() => store.addable);
5680
5737
  const deletable = { get: () => editable };
@@ -5703,7 +5760,6 @@
5703
5760
  dragRow = index;
5704
5761
  }
5705
5762
  }
5706
- tfoot.addEventListener('dragenter', () => {dragenter();});
5707
5763
  /**
5708
5764
  *
5709
5765
  * @param {Store} child
@@ -5716,8 +5772,31 @@
5716
5772
  dragRow = -1;
5717
5773
 
5718
5774
  }
5719
- renderHead(thead, columns, add, addable, editable);
5720
- renderHead(tfoot, columns, add, addable, editable);
5775
+ /** @type {(() => void)[]} */
5776
+ const destroyList = [];
5777
+ destroyList.push(renderHead(thead, columns, add, addable, editable));
5778
+ switch (layout?.tableFoot) {
5779
+ default:
5780
+ case 'header': {
5781
+ const tfoot = table.appendChild(document.createElement('tfoot'));
5782
+ tfoot.addEventListener('dragenter', () => { dragenter(); });
5783
+ destroyList.push(renderHead(tfoot, columns, add, addable, editable));
5784
+ break;
5785
+ }
5786
+ case 'add': {
5787
+ const tfoot = table.appendChild(document.createElement('tfoot'));
5788
+ tfoot.addEventListener('dragenter', () => { dragenter(); });
5789
+ const tr = tfoot.appendChild(document.createElement('tr'));
5790
+ const th = tr.appendChild(document.createElement('th'));
5791
+ th.colSpan = columns.length;
5792
+ const button = th.appendChild(document.createElement('button'));
5793
+ button.addEventListener('click', add);
5794
+ button.classList.add('NeeloongFormGrid-table-foot-add');
5795
+ destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5796
+ break;
5797
+ }
5798
+ case 'none':
5799
+ }
5721
5800
  const start = thead;
5722
5801
  /** @type {Map<Store, [HTMLTableSectionElement, () => void]>} */
5723
5802
  let seMap = new Map();
@@ -5729,7 +5808,7 @@
5729
5808
  }
5730
5809
 
5731
5810
  }
5732
- const columnNames = columns.map(([v]) => v);
5811
+ const columnNames = columns.map((v) => Array.isArray(v) ? v : v.field);
5733
5812
  const childrenResult = watch(() => store.children, function render(children) {
5734
5813
  let nextNode = thead.nextSibling;
5735
5814
  const oldSeMap = seMap;
@@ -5737,12 +5816,12 @@
5737
5816
  for (let child of children) {
5738
5817
  const old = oldSeMap.get(child);
5739
5818
  if (!old) {
5740
- const [el, destroy] = Line(child, fieldRenderer, editable, template, {
5819
+ const [el, destroy] = Line(child, fieldRenderer, editable, layout, {
5741
5820
  columns: columnNames,
5742
5821
  remove: remove.bind(null, child),
5743
5822
  dragenter: dragenter.bind(null, child),
5744
5823
  dragstart: dragstart.bind(null, child),
5745
- dragend,
5824
+ dragend,
5746
5825
  deletable,
5747
5826
  }, options);
5748
5827
  table.insertBefore(el, nextNode);
@@ -5755,7 +5834,6 @@
5755
5834
  nextNode = nextNode.nextSibling;
5756
5835
  continue;
5757
5836
  }
5758
- console.log(table, old[0], nextNode);
5759
5837
  table.insertBefore(old[0], nextNode);
5760
5838
  }
5761
5839
  destroyMap(oldSeMap);
@@ -5766,6 +5844,9 @@
5766
5844
  thead.remove();
5767
5845
  destroyMap(seMap);
5768
5846
  childrenResult();
5847
+ for (const destroy of destroyList) {
5848
+ destroy();
5849
+ }
5769
5850
  }];
5770
5851
  }
5771
5852
 
@@ -5774,92 +5855,112 @@
5774
5855
  * @param {Store<any, any>} store
5775
5856
  * @param {FieldRenderer} fieldRenderer
5776
5857
  * @param {boolean} editable
5777
- * @param {GridFormItemTemplate?} template
5858
+ * @param {GridFieldLayout?} layout
5778
5859
  * @param {object} options
5779
5860
  * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate]
5780
5861
  * @param {boolean} [inline]
5781
5862
  * @returns {[HTMLElement, () => void]}
5782
5863
  */
5783
- function FormItem(store, fieldRenderer, editable, template, options, inline = false) {
5784
- const subFields = template?.subFields;
5864
+ function FormItem(store, fieldRenderer, editable, layout, options, inline = false) {
5785
5865
  const { type, component } = store;
5786
5866
  if (inline) {
5787
5867
  if (component) {
5788
5868
  return fieldRenderer(store, component, options);
5789
5869
  }
5790
- return [document.createElement('div'), () => {}];
5870
+ return [document.createElement('div'), () => { }];
5791
5871
  }
5872
+ /** @type {(() => void)[]} */
5873
+ const destroyList = [];
5792
5874
  if (type && typeof type === 'object') {
5793
5875
  const root = document.createElement('details');
5794
5876
  root.open = true;
5795
5877
  const summary = root.appendChild(document.createElement('summary'));
5796
- summary.innerText = store.label || '';
5797
- root.hidden = store.hidden;
5798
- if (!Array.isArray(subFields) && typeof component === 'function') {
5878
+ destroyList.push(effect(() => summary.innerText = store.label || ''));
5879
+ destroyList.push(effect(() => root.hidden = store.hidden));
5880
+ if (typeof component === 'function') {
5799
5881
  const [el, destroy] = fieldRenderer(store, component, options);
5800
5882
  root.appendChild(el);
5801
- return [root, destroy];
5802
- }
5803
- if (store instanceof ArrayStore) {
5804
- const [table, destroy] = Table(store, fieldRenderer, editable, template, options);
5883
+ destroyList.push(destroy);
5884
+ } else if (store instanceof ArrayStore) {
5885
+ const [table, destroy] = Table(store, fieldRenderer, editable, layout, options);
5805
5886
  root.appendChild(table);
5806
- return [root, destroy];
5887
+ destroyList.push(destroy);
5807
5888
  } else {
5808
- const [form, destroy] = Form(store, fieldRenderer, editable, Array.isArray(subFields) ? subFields : null, options);
5889
+ const [form, destroy] = Form(store, fieldRenderer, editable, layout, options);
5809
5890
  root.appendChild(form);
5810
- return [root, destroy];
5891
+ destroyList.push(destroy);
5811
5892
  }
5893
+ return [root, () => {
5894
+ for (const destroy of destroyList) {
5895
+ destroy();
5896
+ }
5897
+ }];
5812
5898
  }
5813
5899
 
5814
5900
  const root = document.createElement('div');
5815
- root.className = "GridForm-item";
5816
- root.hidden = store.hidden;
5817
- const colSpan = store.meta.colSpan;
5818
- if (colSpan) {
5901
+ root.className = "NeeloongFormGrid-item";
5902
+ destroyList.push(effect(() => root.hidden = store.hidden));
5903
+ const { colStart, colSpan, colEnd, rowStart, rowSpan, rowEnd } = layout || {};
5904
+ if (colStart && colEnd) {
5905
+ root.style.gridColumn = `${colStart} / ${colEnd}`;
5906
+ } else if (colStart && colSpan) {
5907
+ root.style.gridColumn = `${colStart} / span ${colSpan}`;
5908
+ } else if (colSpan) {
5819
5909
  root.style.gridColumn = `span ${colSpan}`;
5820
5910
  }
5911
+ if (rowStart && rowEnd) {
5912
+ root.style.gridRow = `${rowStart} / ${rowEnd}`;
5913
+ } else if (rowStart && rowSpan) {
5914
+ root.style.gridRow = `${rowStart} / span ${rowSpan}`;
5915
+ } else if (rowSpan) {
5916
+ root.style.gridRow = `span ${rowSpan}`;
5917
+ }
5821
5918
  const label = root.appendChild(document.createElement('div'));
5822
- label.className = 'GridForm-item-label';
5823
- label.innerText = store.label || '';
5919
+ label.className = 'NeeloongFormGrid-item-label';
5920
+ destroyList.push(effect(() => label.innerText = store.label || ''));
5824
5921
 
5825
5922
 
5826
5923
  const content = root.appendChild(document.createElement('div'));
5827
- content.className = 'GridForm-item-content';
5924
+ content.className = 'NeeloongFormGrid-item-content';
5828
5925
 
5829
5926
  const description = root.appendChild(document.createElement('div'));
5830
- description.className = 'GridForm-item-description';
5831
- description.innerText = store.description || '';
5927
+ description.className = 'NeeloongFormGrid-item-description';
5928
+ destroyList.push(effect(() => description.innerText = store.description || ''));
5832
5929
  if (typeof component === 'function') {
5833
5930
  const [el, destroy] = fieldRenderer(store, component, options);
5834
5931
  content.appendChild(el);
5835
- return [root, destroy];
5932
+ destroyList.push(destroy);
5836
5933
  }
5837
- return [root, () => {}];
5934
+ return [root, () => {
5935
+ for (const destroy of destroyList) {
5936
+ destroy();
5937
+ }
5938
+ }];
5838
5939
  }
5839
5940
 
5840
5941
  /** @import { Store } from '../Store/index.mjs' */
5841
5942
  /** @import { Relatedness } from '../types.mjs' */
5842
- /** @import { FieldRenderer, GridFormTemplate } from './types.mjs' */
5943
+ /** @import { FieldRenderer, GridLayout } from './types.mjs' */
5843
5944
 
5844
5945
  /**
5845
5946
  *
5846
5947
  * @param {Store<any, any>} store
5847
5948
  * @param {FieldRenderer} fieldRenderer
5848
5949
  * @param {boolean} editable
5849
- * @param {GridFormTemplate?} template
5950
+ * @param {GridLayout?} layout
5850
5951
  * @param {object} options
5851
5952
  * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate]
5852
5953
  * @param {HTMLElement} [options.parent]
5853
5954
  * @returns {[HTMLElement, () => void]}
5854
5955
  */
5855
- function Form(store, fieldRenderer, editable, template, {parent, relate}) {
5956
+ function Form(store, fieldRenderer, editable, layout, {parent, relate}) {
5856
5957
  const root = parent instanceof HTMLElement ? parent : document.createElement('div');
5857
- root.className = 'GridForm';
5958
+ root.className = 'NeeloongFormGrid';
5858
5959
  /** @type {(() => void)[]} */
5859
5960
  const destroyList = [];
5860
-
5861
- if (template) {
5862
- for (const fieldTemplate of template) {
5961
+ const fieldLayouts = layout?.fields;
5962
+ if (fieldLayouts) {
5963
+ for (const fieldTemplate of fieldLayouts) {
5863
5964
  const fieldStore = store.child(fieldTemplate.field);
5864
5965
  if (!fieldStore) { continue; }
5865
5966
  const [el, destroy] = FormItem(fieldStore, fieldRenderer, editable, fieldTemplate, {relate});
@@ -5867,8 +5968,7 @@
5867
5968
  destroyList.push(destroy);
5868
5969
  }
5869
5970
  } else {
5870
- const fields = [...store].map(([, v]) => v)
5871
- .sort((a, b) => (a.meta.layoutOrder || 0) - (b.meta.layoutOrder || 0));
5971
+ const fields = [...store].map(([, v]) => v);
5872
5972
 
5873
5973
 
5874
5974
  for (const field of fields) {
@@ -5886,7 +5986,7 @@
5886
5986
  }
5887
5987
 
5888
5988
  /** @import { Store } from '../Store/index.mjs' */
5889
- /** @import { FieldRenderer, GridFormTemplate } from './types.mjs' */
5989
+ /** @import { FieldRenderer, GridLayout } from './types.mjs' */
5890
5990
 
5891
5991
  /**
5892
5992
  *
@@ -5894,10 +5994,10 @@
5894
5994
  * @param {HTMLElement} root
5895
5995
  * @param {FieldRenderer} fieldRenderer
5896
5996
  * @param {boolean} editable
5897
- * @param {GridFormTemplate?} [template]
5997
+ * @param {GridLayout?} [layout]
5898
5998
  */
5899
- function index (store, root, fieldRenderer, editable, template) {
5900
- const s = Form(store, fieldRenderer, editable, template || null, {parent: root});
5999
+ function index (store, root, fieldRenderer, editable, layout) {
6000
+ const s = Form(store, fieldRenderer, editable, layout || null, {parent: root});
5901
6001
  return s[1];
5902
6002
  }
5903
6003