@neeloong/form 0.16.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.16.0
2
+ * @neeloong/form v0.17.0
3
3
  * (c) 2024-2025 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -1174,11 +1174,12 @@ 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;
1182
1183
  colStart?: number | undefined;
1183
1184
  colSpan?: number | undefined;
1184
1185
  colEnd?: number | undefined;
@@ -1186,7 +1187,9 @@ type GridFormItemTemplate = {
1186
1187
  rowSpan?: number | undefined;
1187
1188
  rowEnd?: number | undefined;
1188
1189
  };
1189
- type GridFormTemplate = GridFormItemTemplate[];
1190
+ type GridLayout = {
1191
+ fields?: GridFieldLayout[] | null | undefined;
1192
+ };
1190
1193
  type Options = object;
1191
1194
  type FieldRenderer = (store: Store<any, any>, component: any, Options: Options) => [HTMLElement, () => void];
1192
1195
 
@@ -1196,9 +1199,9 @@ type FieldRenderer = (store: Store<any, any>, component: any, Options: Options)
1196
1199
  * @param {HTMLElement} root
1197
1200
  * @param {FieldRenderer} fieldRenderer
1198
1201
  * @param {boolean} editable
1199
- * @param {GridFormTemplate?} [template]
1202
+ * @param {GridLayout?} [layout]
1200
1203
  */
1201
- 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;
1202
1205
 
1203
1206
  /**
1204
1207
  * @param {Store} store 存储实例
@@ -1250,4 +1253,4 @@ declare function effect(fn: () => void): () => void;
1250
1253
  */
1251
1254
  declare function renderHtml(renderField: (store: Store<any, any>, component: any) => [HTMLElement, () => void] | null, store: Store, root: HTMLElement, html?: string | ParentNode, clone?: boolean): () => void;
1252
1255
 
1253
- 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.16.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,7 +5521,7 @@
5521
5521
 
5522
5522
  * @returns {[HTMLTableSectionElement, () => void]}
5523
5523
  */
5524
- function Line(store, fieldRenderer, editable, template, {
5524
+ function Line(store, fieldRenderer, editable, layout, {
5525
5525
  columns,
5526
5526
  remove, dragenter, dragstart, dragend, deletable
5527
5527
  }, options) {
@@ -5536,74 +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.classList.add('NeeloongFormGrid-table-line-handle');
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.classList.add('NeeloongFormGrid-table-line-open');
5568
- ext.addEventListener('click', () => {
5569
- if (body.hidden) {
5570
- body.hidden = false;
5571
- ext.classList.remove('NeeloongFormGrid-table-line-open');
5572
- ext.classList.add('NeeloongFormGrid-table-line-close');
5573
- } else {
5574
- body.hidden = true;
5575
- ext.classList.remove('NeeloongFormGrid-table-line-close');
5576
- ext.classList.add('NeeloongFormGrid-table-line-open');
5590
+ }
5577
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;
5578
5601
  }
5579
- });
5580
- if (editable) {
5581
- const move = handle.appendChild(document.createElement('button'));
5582
- move.classList.add('NeeloongFormGrid-table-move');
5583
- move.addEventListener('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 });
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
+ }
5592
5638
  }
5593
- window.addEventListener('pointerup', pointerup, { capture: true });
5594
- window.addEventListener('pointercancel', pointerup, { capture: true });
5595
- });
5596
- destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5597
- move.disabled = disabled;
5598
- }, true));
5599
- const del = handle.appendChild(document.createElement('button'));
5600
- del.classList.add('NeeloongFormGrid-table-remove');
5601
- // @ts-ignore
5602
- destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5603
- move.disabled = disabled;
5604
- }, true));
5605
- del.addEventListener('click', remove);
5639
+ }
5606
5640
  }
5641
+
5607
5642
  return [root, () => {
5608
5643
  for (const destroy of destroyList) {
5609
5644
  destroy();
@@ -5613,65 +5648,90 @@
5613
5648
 
5614
5649
  /** @import { Store, ArrayStore } from '../Store/index.mjs' */
5615
5650
  /** @import { Relatedness } from '../types.mjs' */
5616
- /** @import { FieldRenderer, GridFormItemTemplate } from './types.mjs' */
5651
+ /** @import { FieldRenderer, GridFieldLayout, GridFormItemTemplateTableAction } from './types.mjs' */
5617
5652
 
5618
5653
  /**
5619
5654
  *
5620
5655
  * @param {HTMLElement} parent
5621
- * @param {[string, any][]} columns
5656
+ * @param {({ field: string; width: any; label: any; } | GridFormItemTemplateTableAction[])[]} columns
5622
5657
  * @param {() => any} add
5623
5658
  * @param {{get(): boolean}} addable
5624
5659
  * @param {boolean?} [editable]
5625
5660
  */
5626
5661
  function renderHead(parent, columns, add, addable, editable) {
5627
5662
  const tr = parent.appendChild(document.createElement('tr'));
5628
- const th = tr.appendChild(document.createElement('th'));
5629
- for (const [, { width, label }] of columns) {
5630
- const td = tr.appendChild(document.createElement('td'));
5631
- if (width) {
5632
- 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
+ }
5633
5686
  }
5634
- td.innerText = label;
5635
- }
5636
- if (!editable) {
5637
- return () => {}
5638
5687
  }
5639
- const button = th.appendChild(document.createElement('button'));
5640
- button.addEventListener('click', add);
5641
- button.classList.add('NeeloongFormGrid-table-add');
5642
- return watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true);
5688
+ return () => {
5689
+ for (const destroy of destroyList) {
5690
+ destroy();
5691
+ }
5692
+ };
5643
5693
  }
5644
5694
  /**
5645
5695
  *
5646
5696
  * @param {ArrayStore} store
5647
5697
  * @param {FieldRenderer} fieldRenderer
5648
5698
  * @param {boolean} editable
5649
- * @param {GridFormItemTemplate?} template
5699
+ * @param {GridFieldLayout?} layout
5650
5700
  * @param {object} options
5651
5701
  * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate]
5652
5702
  * @returns {[HTMLTableElement, () => void]}
5653
5703
  */
5654
- function Table(store, fieldRenderer, editable, template, options) {
5655
- const headerColumns = template?.headers;
5656
-
5657
- const fieldList = Object.entries(store.type || {});
5658
- /** @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[])[]} */
5659
5710
  let columns = [];
5660
- if (headerColumns) {
5661
- const map = new Map(fieldList.map(v => [v[0], v]));
5662
- for (const c of headerColumns) {
5663
- const f = map.get(c);
5664
- if (f) { columns.push(f); }
5665
- }
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);
5720
+ }
5721
+ if (!columns.length) {
5722
+ columns = [['add', 'trigger', 'move', 'remove', 'serial']];
5666
5723
  }
5667
- if (!columns.length) { columns = fieldList.filter(([k, v]) => typeof v?.type !== 'object').slice(0, 3); }
5724
+ if (!columns.find(v => !Array.isArray(v))) {
5725
+ columns.push(...fieldList.slice(0, 3));
5726
+ }
5727
+
5728
+
5668
5729
 
5669
5730
  const table = document.createElement('table');
5670
5731
  table.className = 'NeeloongFormGrid-table';
5671
5732
  const thead = table.appendChild(document.createElement('thead'));
5672
5733
 
5673
5734
 
5674
- const tfoot = table.appendChild(document.createElement('tfoot'));
5675
5735
 
5676
5736
  const addable = new exports.Signal.Computed(() => store.addable);
5677
5737
  const deletable = { get: () => editable };
@@ -5700,7 +5760,6 @@
5700
5760
  dragRow = index;
5701
5761
  }
5702
5762
  }
5703
- tfoot.addEventListener('dragenter', () => {dragenter();});
5704
5763
  /**
5705
5764
  *
5706
5765
  * @param {Store} child
@@ -5713,8 +5772,31 @@
5713
5772
  dragRow = -1;
5714
5773
 
5715
5774
  }
5716
- renderHead(thead, columns, add, addable, editable);
5717
- 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
+ }
5718
5800
  const start = thead;
5719
5801
  /** @type {Map<Store, [HTMLTableSectionElement, () => void]>} */
5720
5802
  let seMap = new Map();
@@ -5726,7 +5808,7 @@
5726
5808
  }
5727
5809
 
5728
5810
  }
5729
- const columnNames = columns.map(([v]) => v);
5811
+ const columnNames = columns.map((v) => Array.isArray(v) ? v : v.field);
5730
5812
  const childrenResult = watch(() => store.children, function render(children) {
5731
5813
  let nextNode = thead.nextSibling;
5732
5814
  const oldSeMap = seMap;
@@ -5734,12 +5816,12 @@
5734
5816
  for (let child of children) {
5735
5817
  const old = oldSeMap.get(child);
5736
5818
  if (!old) {
5737
- const [el, destroy] = Line(child, fieldRenderer, editable, template, {
5819
+ const [el, destroy] = Line(child, fieldRenderer, editable, layout, {
5738
5820
  columns: columnNames,
5739
5821
  remove: remove.bind(null, child),
5740
5822
  dragenter: dragenter.bind(null, child),
5741
5823
  dragstart: dragstart.bind(null, child),
5742
- dragend,
5824
+ dragend,
5743
5825
  deletable,
5744
5826
  }, options);
5745
5827
  table.insertBefore(el, nextNode);
@@ -5752,7 +5834,6 @@
5752
5834
  nextNode = nextNode.nextSibling;
5753
5835
  continue;
5754
5836
  }
5755
- console.log(table, old[0], nextNode);
5756
5837
  table.insertBefore(old[0], nextNode);
5757
5838
  }
5758
5839
  destroyMap(oldSeMap);
@@ -5763,6 +5844,9 @@
5763
5844
  thead.remove();
5764
5845
  destroyMap(seMap);
5765
5846
  childrenResult();
5847
+ for (const destroy of destroyList) {
5848
+ destroy();
5849
+ }
5766
5850
  }];
5767
5851
  }
5768
5852
 
@@ -5771,14 +5855,13 @@
5771
5855
  * @param {Store<any, any>} store
5772
5856
  * @param {FieldRenderer} fieldRenderer
5773
5857
  * @param {boolean} editable
5774
- * @param {GridFormItemTemplate?} template
5858
+ * @param {GridFieldLayout?} layout
5775
5859
  * @param {object} options
5776
5860
  * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate]
5777
5861
  * @param {boolean} [inline]
5778
5862
  * @returns {[HTMLElement, () => void]}
5779
5863
  */
5780
- function FormItem(store, fieldRenderer, editable, template, options, inline = false) {
5781
- const subFields = template?.subFields;
5864
+ function FormItem(store, fieldRenderer, editable, layout, options, inline = false) {
5782
5865
  const { type, component } = store;
5783
5866
  if (inline) {
5784
5867
  if (component) {
@@ -5794,16 +5877,16 @@
5794
5877
  const summary = root.appendChild(document.createElement('summary'));
5795
5878
  destroyList.push(effect(() => summary.innerText = store.label || ''));
5796
5879
  destroyList.push(effect(() => root.hidden = store.hidden));
5797
- if (!Array.isArray(subFields) && typeof component === 'function') {
5880
+ if (typeof component === 'function') {
5798
5881
  const [el, destroy] = fieldRenderer(store, component, options);
5799
5882
  root.appendChild(el);
5800
5883
  destroyList.push(destroy);
5801
5884
  } else if (store instanceof ArrayStore) {
5802
- const [table, destroy] = Table(store, fieldRenderer, editable, template, options);
5885
+ const [table, destroy] = Table(store, fieldRenderer, editable, layout, options);
5803
5886
  root.appendChild(table);
5804
5887
  destroyList.push(destroy);
5805
5888
  } else {
5806
- const [form, destroy] = Form(store, fieldRenderer, editable, Array.isArray(subFields) ? subFields : null, options);
5889
+ const [form, destroy] = Form(store, fieldRenderer, editable, layout, options);
5807
5890
  root.appendChild(form);
5808
5891
  destroyList.push(destroy);
5809
5892
  }
@@ -5817,18 +5900,20 @@
5817
5900
  const root = document.createElement('div');
5818
5901
  root.className = "NeeloongFormGrid-item";
5819
5902
  destroyList.push(effect(() => root.hidden = store.hidden));
5820
- const { colStart, colSpan, colEnd, rowStart, rowSpan, rowEnd } = template || {};
5821
- if (colStart) { root.style.gridColumnStart = `${colStart}`; }
5822
- if (colEnd) {
5823
- root.style.gridColumnEnd = `${colEnd}`;
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}`;
5824
5908
  } else if (colSpan) {
5825
- root.style.gridColumnEnd = `span ${colSpan}`;
5909
+ root.style.gridColumn = `span ${colSpan}`;
5826
5910
  }
5827
- if (rowStart) { root.style.gridRowStart = `${rowStart}`; }
5828
- if (rowEnd) {
5829
- root.style.gridRowEnd = `${rowEnd}`;
5911
+ if (rowStart && rowEnd) {
5912
+ root.style.gridRow = `${rowStart} / ${rowEnd}`;
5913
+ } else if (rowStart && rowSpan) {
5914
+ root.style.gridRow = `${rowStart} / span ${rowSpan}`;
5830
5915
  } else if (rowSpan) {
5831
- root.style.gridRowEnd = `span ${rowSpan}`;
5916
+ root.style.gridRow = `span ${rowSpan}`;
5832
5917
  }
5833
5918
  const label = root.appendChild(document.createElement('div'));
5834
5919
  label.className = 'NeeloongFormGrid-item-label';
@@ -5855,27 +5940,27 @@
5855
5940
 
5856
5941
  /** @import { Store } from '../Store/index.mjs' */
5857
5942
  /** @import { Relatedness } from '../types.mjs' */
5858
- /** @import { FieldRenderer, GridFormTemplate } from './types.mjs' */
5943
+ /** @import { FieldRenderer, GridLayout } from './types.mjs' */
5859
5944
 
5860
5945
  /**
5861
5946
  *
5862
5947
  * @param {Store<any, any>} store
5863
5948
  * @param {FieldRenderer} fieldRenderer
5864
5949
  * @param {boolean} editable
5865
- * @param {GridFormTemplate?} template
5950
+ * @param {GridLayout?} layout
5866
5951
  * @param {object} options
5867
5952
  * @param {(store: Store, el: Element | Relatedness) => () => void} [options.relate]
5868
5953
  * @param {HTMLElement} [options.parent]
5869
5954
  * @returns {[HTMLElement, () => void]}
5870
5955
  */
5871
- function Form(store, fieldRenderer, editable, template, {parent, relate}) {
5956
+ function Form(store, fieldRenderer, editable, layout, {parent, relate}) {
5872
5957
  const root = parent instanceof HTMLElement ? parent : document.createElement('div');
5873
5958
  root.className = 'NeeloongFormGrid';
5874
5959
  /** @type {(() => void)[]} */
5875
5960
  const destroyList = [];
5876
-
5877
- if (template) {
5878
- for (const fieldTemplate of template) {
5961
+ const fieldLayouts = layout?.fields;
5962
+ if (fieldLayouts) {
5963
+ for (const fieldTemplate of fieldLayouts) {
5879
5964
  const fieldStore = store.child(fieldTemplate.field);
5880
5965
  if (!fieldStore) { continue; }
5881
5966
  const [el, destroy] = FormItem(fieldStore, fieldRenderer, editable, fieldTemplate, {relate});
@@ -5901,7 +5986,7 @@
5901
5986
  }
5902
5987
 
5903
5988
  /** @import { Store } from '../Store/index.mjs' */
5904
- /** @import { FieldRenderer, GridFormTemplate } from './types.mjs' */
5989
+ /** @import { FieldRenderer, GridLayout } from './types.mjs' */
5905
5990
 
5906
5991
  /**
5907
5992
  *
@@ -5909,10 +5994,10 @@
5909
5994
  * @param {HTMLElement} root
5910
5995
  * @param {FieldRenderer} fieldRenderer
5911
5996
  * @param {boolean} editable
5912
- * @param {GridFormTemplate?} [template]
5997
+ * @param {GridLayout?} [layout]
5913
5998
  */
5914
- function index (store, root, fieldRenderer, editable, template) {
5915
- 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});
5916
6001
  return s[1];
5917
6002
  }
5918
6003