@neeloong/form 0.22.0 → 0.23.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.22.0
2
+ * @neeloong/form v0.23.0
3
3
  * (c) 2024-2026 Fierflame
4
4
  * @license Apache-2.0
5
5
  */
@@ -2892,13 +2892,36 @@
2892
2892
 
2893
2893
  /**
2894
2894
  * 相应式执行
2895
+ * @overload
2895
2896
  * @param {() => void} fn 执行函数
2897
+ * @param {null} [signal]
2896
2898
  * @returns {() => void} 取消函数
2897
2899
  */
2898
- function effect(fn) {
2900
+ /**
2901
+ * 相应式执行
2902
+ * @overload
2903
+ * @param {() => void} fn 执行函数
2904
+ * @param {AbortSignal} signal
2905
+ * @returns {void} 取消函数
2906
+ */
2907
+ /**
2908
+ * 相应式执行
2909
+ * @overload
2910
+ * @param {() => void} fn 执行函数
2911
+ * @param {AbortSignal?} [signal]
2912
+ * @returns {(() => void) | void} 取消函数
2913
+ */
2914
+ /**
2915
+ * 相应式执行
2916
+ * @param {() => void} fn 执行函数
2917
+ * @param {AbortSignal?} [signal]
2918
+ * @returns {(() => void) | void} 取消函数
2919
+ */
2920
+ function effect(fn, signal) {
2921
+ if (signal?.aborted) { return; }
2899
2922
  let needsEnqueue = true;
2900
2923
  const w = new exports.Signal.subtle.Watcher(() => {
2901
- if (!needsEnqueue) { return }
2924
+ if (!needsEnqueue) { return; }
2902
2925
  needsEnqueue = false;
2903
2926
  queueMicrotask(() => {
2904
2927
  needsEnqueue = true;
@@ -2912,19 +2935,54 @@
2912
2935
 
2913
2936
  w.watch(computed);
2914
2937
  computed.get();
2938
+ if (!signal) { return () => { w.unwatch(computed); }; }
2939
+ signal.addEventListener('abort', () => {
2940
+ w.unwatch(computed);
2941
+ }, { once: true });
2915
2942
 
2916
- return () => { w.unwatch(computed); };
2917
2943
  }
2918
2944
 
2919
2945
  /**
2920
2946
  * 创建可赋值计算值
2921
2947
  * @template T
2948
+ * @overload
2922
2949
  * @param {() => T} getter 取值方法
2923
2950
  * @param {(value: T) => void} callback 取值方法
2924
2951
  * @param {boolean} immediate 是否立即执行一次
2952
+ * @param {null} [signal]
2925
2953
  * @returns {() => void}
2926
2954
  */
2927
- function watch(getter, callback, immediate) {
2955
+
2956
+ /**
2957
+ * 创建可赋值计算值
2958
+ * @template T
2959
+ * @overload
2960
+ * @param {() => T} getter 取值方法
2961
+ * @param {(value: T) => void} callback 取值方法
2962
+ * @param {boolean} immediate 是否立即执行一次
2963
+ * @param {AbortSignal?} [signal]
2964
+ * @returns {(() => void) | void}
2965
+ */
2966
+ /**
2967
+ * 创建可赋值计算值
2968
+ * @template T
2969
+ * @overload
2970
+ * @param {() => T} getter 取值方法
2971
+ * @param {(value: T) => void} callback 取值方法
2972
+ * @param {boolean} immediate 是否立即执行一次
2973
+ * @param {AbortSignal} signal
2974
+ * @returns {void}
2975
+ */
2976
+ /**
2977
+ * 创建可赋值计算值
2978
+ * @template T
2979
+ * @param {() => T} getter 取值方法
2980
+ * @param {(value: T) => void} callback 取值方法
2981
+ * @param {boolean} immediate 是否立即执行一次
2982
+ * @param {AbortSignal?} [signal]
2983
+ * @returns {(() => void) | void}
2984
+ */
2985
+ function watch(getter, callback, immediate, signal) {
2928
2986
  let run = false;
2929
2987
  /** @type {any} */
2930
2988
  let value;
@@ -2939,7 +2997,7 @@
2939
2997
  if (Object.is(val, value)) { return; }
2940
2998
  value = val;
2941
2999
  callback(val);
2942
- });
3000
+ }, signal);
2943
3001
  }
2944
3002
 
2945
3003
  const bindable = {
@@ -5251,67 +5309,65 @@
5251
5309
  /** @import { Store } from '../Store/index.mjs' */
5252
5310
  /** @import { StoreLayout } from '../types.mjs' */
5253
5311
 
5254
-
5255
5312
  /**
5256
5313
  *
5314
+ * @template T
5257
5315
  * @param {string} field
5316
+ * @returns {(v: StoreLayout.Item<T>) => v is StoreLayout.Field<T>}
5258
5317
  */
5259
5318
  function createFieldFilter(field) {
5260
-
5261
5319
  /**
5262
5320
  *
5263
- * @param {StoreLayout.Item} v
5264
- * @returns {v is StoreLayout.Field}
5321
+ * @param {StoreLayout.Item<T>} v
5322
+ * @returns {v is StoreLayout.Field<T>}
5265
5323
  */
5266
-
5267
- return function (v) {
5324
+ return v => {
5268
5325
  if (v.type && v.type !== 'field') { return false; }
5269
5326
  return v.field === field;
5270
5327
 
5271
5328
  };
5272
5329
  }
5273
5330
  /**
5274
- *
5275
- * @param {StoreLayout.Renderer} fieldRenderer
5331
+ * @template T
5332
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
5276
5333
  * @param {Store} store
5277
5334
  * @param {Node} node
5278
5335
  * @param {StoreLayout.Options?} options
5279
- * @param {StoreLayout?} [layout]
5336
+ * @param {StoreLayout<T>?} [layout]
5280
5337
  * @param {Node} [anchor]
5281
5338
  * @param {(child?: Store<any, any> | undefined) => void} [dragenter]
5339
+ * @returns {void}
5282
5340
  */
5283
5341
  function renderHtml(store, fieldRenderer, node, options, layout, anchor, dragenter) {
5284
- /** @type {(() => void)[]} */
5285
- const destroyList = [];
5342
+ if (options?.signal?.aborted) { return; }
5286
5343
  if (node instanceof Element) {
5287
5344
  const tagName = node.tagName.toLowerCase();
5288
- if (!node.parentNode) { return () => { }; }
5345
+ if (!node.parentNode) { return; }
5289
5346
  if (tagName === 'nl-form-field') {
5290
5347
  const field = node.getAttribute('name') || '';
5291
5348
  const mode = node.getAttribute('mode') || '';
5292
5349
  const fieldStore = field ? store.child(field) : store;
5293
5350
  const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) || null : null;
5294
- if (!fieldStore) { return () => { }; }
5351
+ if (!fieldStore) { return; }
5295
5352
  switch (mode) {
5296
5353
  case 'grid': {
5297
- const [el, destroy] = Form(store, fieldRenderer, fieldLayout, options);
5298
- node.replaceWith(el);
5299
- return destroy;
5354
+ const el = Form(store, fieldRenderer, fieldLayout, options);
5355
+ if (el) { node.replaceWith(el); }
5356
+ return;
5300
5357
  }
5301
5358
  }
5302
- const res = fieldRenderer(fieldStore, options);
5359
+ const res = fieldRenderer(fieldStore, layout?.renderer, options);
5303
5360
  if (res) {
5304
- const [el, destroy] = res;
5305
- node.replaceWith(el);
5306
- return destroy;
5361
+ node.replaceWith(res);
5362
+ return;
5307
5363
  }
5308
5364
  const value = node.getAttribute('placeholder') || '';
5309
5365
  node.replaceWith(document.createTextNode(value));
5310
- return () => { };
5366
+ return;
5311
5367
  }
5312
5368
  if (tagName === 'nl-form-button') {
5313
5369
  const button = document.createElement('button');
5314
- button.className = 'NeeloongForm-item-button';
5370
+ button.classList.add('NeeloongForm-item-button');
5315
5371
  const click = node.getAttribute('click') || '';
5316
5372
  const call = options?.call;
5317
5373
  if (click && typeof call === 'function') {
@@ -5321,7 +5377,7 @@
5321
5377
  button.appendChild(n);
5322
5378
  }
5323
5379
  node.replaceWith(button);
5324
- return () => { };
5380
+ return;
5325
5381
  }
5326
5382
  const name = node.getAttribute('nl-form-field');
5327
5383
  if (name) {
@@ -5330,32 +5386,35 @@
5330
5386
 
5331
5387
  const fieldStore = field ? store.child(field) : store;
5332
5388
  if (!fieldStore) {
5333
- return () => { };
5389
+ return;
5334
5390
  }
5335
5391
  node.removeAttribute('nl-form-field');
5336
5392
  const fieldLayout = field ? layout?.fields?.find(createFieldFilter(field)) : layout;
5337
- if (!array) { return renderHtml(fieldStore, fieldRenderer, node, options, fieldLayout, anchor); }
5393
+ if (!array) {
5394
+ renderHtml(fieldStore, fieldRenderer, node, options, fieldLayout, anchor);
5395
+ return;
5396
+ }
5338
5397
  if (!(fieldStore instanceof ArrayStore)) {
5339
5398
  node.remove();
5340
- return () => { };
5399
+ return;
5341
5400
  }
5342
5401
  const parentElement = node.parentElement;
5343
5402
  if (!parentElement) {
5344
5403
  node.remove();
5345
- return () => { };
5404
+ return;
5346
5405
  }
5347
5406
  const comment = parentElement.insertBefore(document.createComment(''), node) || null;
5348
5407
  node.remove();
5349
5408
 
5350
- /** @type {Map<Store, [Node, () => void]>} */
5409
+ /** @type {Map<Store, [Node, AbortController]>} */
5351
5410
  let seMap = new Map();
5352
- /** @param {Map<Store, [tbody: Node, destroy: () => void]>} map */
5411
+ /** @param {Map<Store, [Node, AbortController]>} map */
5353
5412
  function destroyMap(map) {
5354
- for (const [el, destroy] of map.values()) {
5355
- destroy();
5413
+ for (const [el, ac] of map.values()) {
5356
5414
  if (el instanceof Element) {
5357
5415
  el.remove();
5358
5416
  }
5417
+ ac.abort();
5359
5418
  }
5360
5419
  }
5361
5420
  let dragRow = -1;
@@ -5372,22 +5431,26 @@
5372
5431
  }
5373
5432
  };
5374
5433
 
5375
- const childrenResult = watch(() => fieldStore.children, children => {
5434
+ watch(() => fieldStore.children, children => {
5376
5435
  let nextNode = comment.nextSibling;
5377
5436
  const oldSeMap = seMap;
5378
5437
  seMap = new Map();
5379
5438
  for (const child of children) {
5380
5439
  const old = oldSeMap.get(child);
5381
5440
  if (!old) {
5441
+ const ac = new AbortController();
5382
5442
  const el = parentElement.insertBefore(node.cloneNode(true), nextNode);
5383
- const d = renderHtml(child, fieldRenderer, el, options, fieldLayout, el);
5443
+ renderHtml(child, fieldRenderer, el, {
5444
+ ...options,
5445
+ signal: options?.signal ? AbortSignal.any([options?.signal, ac.signal]) : ac.signal,
5446
+ }, fieldLayout, el);
5384
5447
  el.addEventListener('dragenter', () => { newDragenter(child); });
5385
5448
  el.addEventListener('dragstart', (event) => {
5386
5449
  if (event.target !== event.currentTarget) { return; }
5387
5450
  dragRow = Number(child.index);
5388
5451
  });
5389
5452
  el.addEventListener('dragend', () => { dragRow = -1; });
5390
- seMap.set(child, [el, d]);
5453
+ seMap.set(child, [el, ac]);
5391
5454
  continue;
5392
5455
  }
5393
5456
  oldSeMap.delete(child);
@@ -5399,13 +5462,13 @@
5399
5462
  parentElement.insertBefore(old[0], nextNode);
5400
5463
  }
5401
5464
  destroyMap(oldSeMap);
5402
- }, true);
5465
+ }, true, options?.signal);
5403
5466
 
5404
- return () => {
5467
+ options?.signal?.addEventListener('abort', () => {
5405
5468
  comment.remove();
5406
5469
  destroyMap(seMap);
5407
- childrenResult();
5408
- };
5470
+ }, { once: true });
5471
+ return;
5409
5472
 
5410
5473
  }
5411
5474
  if (node.getAttribute('nl-form-remove') !== null) {
@@ -5448,11 +5511,11 @@
5448
5511
  const field = node.getAttribute(attr);
5449
5512
  const fieldStore = field ? store.child(field) : store;
5450
5513
  if (!fieldStore) { continue; }
5451
- destroyList.push(effect(() => {
5514
+ effect(() => {
5452
5515
  try {
5453
5516
  node.setAttribute(name, fieldStore.value);
5454
5517
  } catch { }
5455
- }));
5518
+ }, options?.signal);
5456
5519
  } else if (prefix === 'nl-form-event' && typeof call === 'function') {
5457
5520
  const event = node.getAttribute(attr);
5458
5521
  if (!event) { continue; }
@@ -5464,15 +5527,9 @@
5464
5527
  }
5465
5528
  if (node instanceof Element || node instanceof DocumentFragment) {
5466
5529
  for (const n of [...node.children]) {
5467
- destroyList.push(renderHtml(store, fieldRenderer, n, options, layout, anchor));
5530
+ renderHtml(store, fieldRenderer, n, options, layout, anchor);
5468
5531
  }
5469
5532
  }
5470
- return () => {
5471
- for (const destroy of destroyList) {
5472
- destroy();
5473
- }
5474
- };
5475
-
5476
5533
  }
5477
5534
 
5478
5535
  /**
@@ -5494,14 +5551,16 @@
5494
5551
 
5495
5552
  /**
5496
5553
  *
5554
+ * @template T
5497
5555
  * @param {Store<any, any>} store
5498
- * @param {StoreLayout.Renderer} fieldRenderer
5499
- * @param {StoreLayout.Field?} layout
5556
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
5557
+ * @param {StoreLayout.Field<T>?} layout
5500
5558
  * @param {StoreLayout.Options?} options
5501
- * @returns {[ParentNode, () => void]}
5559
+ * @returns {ParentNode?}
5502
5560
  */
5503
5561
  function FormFieldInline(store, fieldRenderer, layout, options) {
5504
- return fieldRenderer(store, options) || [document.createElement('div'), () => { }];
5562
+ if (options?.signal?.aborted) { return null; }
5563
+ return fieldRenderer(store, layout?.renderer, options);
5505
5564
  }
5506
5565
 
5507
5566
  /** @import { Store } from '../Store/index.mjs' */
@@ -5509,9 +5568,10 @@
5509
5568
 
5510
5569
  /**
5511
5570
  *
5571
+ * @template T
5512
5572
  * @param {Store<any, any>} store
5513
- * @param {StoreLayout.Renderer} fieldRenderer
5514
- * @param {StoreLayout.Field?} layout
5573
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
5574
+ * @param {StoreLayout.Field<T>?} layout
5515
5575
  * @param {object} option
5516
5576
  * @param {StoreLayout.Column[]} option.columns
5517
5577
  * @param {() => void} option.remove
@@ -5520,8 +5580,7 @@
5520
5580
  * @param {() => void} option.dragend
5521
5581
  * @param {{get(): boolean}} option.deletable
5522
5582
  * @param {StoreLayout.Options?} options
5523
-
5524
- * @returns {[HTMLTableSectionElement, () => void]}
5583
+ * @returns {HTMLTableSectionElement}
5525
5584
  */
5526
5585
  function Line(store, fieldRenderer, layout, {
5527
5586
  columns,
@@ -5538,38 +5597,34 @@
5538
5597
  root.addEventListener('dragend', dragend);
5539
5598
  const head = root.appendChild(document.createElement('tr'));
5540
5599
 
5541
- /** @type {(() => void)[]} */
5542
- const destroyList = [];
5543
5600
 
5544
5601
 
5545
5602
  let trigger = () => { };
5546
5603
  /** @type {HTMLButtonElement[]} */
5547
5604
  const triggerList = [];
5548
5605
  if (columns.find(v => v.actions?.includes('trigger'))) {
5549
- const body = root.appendChild(document.createElement('tr'));
5550
- const main = body.appendChild(document.createElement('td'));
5551
- main.colSpan = columns.length;
5552
-
5553
- const [form, destroy] = Form(store, fieldRenderer, layout, options);
5554
- main.appendChild(form);
5555
- destroyList.push(destroy);
5556
- body.hidden = true;
5557
- trigger = function click() {
5558
- if (body.hidden) {
5559
- body.hidden = false;
5560
- for (const ext of triggerList) {
5561
- ext.classList.remove('NeeloongForm-table-line-open');
5562
- ext.classList.add('NeeloongForm-table-line-close');
5563
- }
5564
- } else {
5565
- body.hidden = true;
5566
- for (const ext of triggerList) {
5567
- ext.classList.remove('NeeloongForm-table-line-close');
5568
- ext.classList.add('NeeloongForm-table-line-open');
5606
+ const form = Form(store, fieldRenderer, layout, options);
5607
+ if (form) {
5608
+ const body = root.appendChild(document.createElement('tr'));
5609
+ const main = body.appendChild(document.createElement('td'));
5610
+ main.colSpan = columns.length;
5611
+ body.hidden = true;
5612
+ trigger = () => {
5613
+ if (body.hidden) {
5614
+ body.hidden = false;
5615
+ for (const ext of triggerList) {
5616
+ ext.classList.remove('NeeloongForm-table-line-open');
5617
+ ext.classList.add('NeeloongForm-table-line-close');
5618
+ }
5619
+ } else {
5620
+ body.hidden = true;
5621
+ for (const ext of triggerList) {
5622
+ ext.classList.remove('NeeloongForm-table-line-close');
5623
+ ext.classList.add('NeeloongForm-table-line-open');
5624
+ }
5569
5625
  }
5570
- }
5571
- };
5572
-
5626
+ };
5627
+ }
5573
5628
  }
5574
5629
 
5575
5630
  /**
@@ -5597,9 +5652,8 @@
5597
5652
  const td = head.appendChild(document.createElement('td'));
5598
5653
  const child = field && store.child(field);
5599
5654
  if (child) {
5600
- const [el, destroy] = FormFieldInline(child, fieldRenderer, null, options);
5601
- destroyList.push(destroy);
5602
- td.appendChild(el);
5655
+ const el = FormFieldInline(child, fieldRenderer, null, options);
5656
+ if (el) { td.appendChild(el); }
5603
5657
  }
5604
5658
  continue;
5605
5659
  }
@@ -5619,9 +5673,9 @@
5619
5673
  const move = handle.appendChild(document.createElement('button'));
5620
5674
  move.classList.add('NeeloongForm-table-move');
5621
5675
  move.addEventListener('pointerdown', pointerdown);
5622
- destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
5676
+ watch(() => store.readonly || store.disabled, disabled => {
5623
5677
  move.disabled = disabled;
5624
- }, true));
5678
+ }, true, options.signal);
5625
5679
  continue;
5626
5680
  }
5627
5681
  case 'remove': {
@@ -5629,9 +5683,9 @@
5629
5683
  const del = handle.appendChild(document.createElement('button'));
5630
5684
  del.classList.add('NeeloongForm-table-remove');
5631
5685
  del.addEventListener('click', remove);
5632
- destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5686
+ watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
5633
5687
  del.disabled = disabled;
5634
- }, true));
5688
+ }, true, options.signal);
5635
5689
  continue;
5636
5690
  }
5637
5691
  case 'serial': {
@@ -5643,11 +5697,7 @@
5643
5697
  }
5644
5698
  }
5645
5699
 
5646
- return [root, () => {
5647
- for (const destroy of destroyList) {
5648
- destroy();
5649
- }
5650
- }];
5700
+ return root;
5651
5701
  }
5652
5702
 
5653
5703
  /** @import { Store, ArrayStore } from '../Store/index.mjs' */
@@ -5655,16 +5705,16 @@
5655
5705
 
5656
5706
  /**
5657
5707
  *
5708
+ * @param {AbortSignal | null | undefined} signal
5658
5709
  * @param {HTMLElement} parent
5659
5710
  * @param {StoreLayout.Column[]} columns
5660
5711
  * @param {() => any} add
5661
5712
  * @param {{get(): boolean}} addable
5662
5713
  * @param {boolean?} [editable]
5714
+ * @returns {void}
5663
5715
  */
5664
- function renderHead(parent, columns, add, addable, editable) {
5716
+ function renderHead(signal, parent, columns, add, addable, editable) {
5665
5717
  const tr = parent.appendChild(document.createElement('tr'));
5666
- /** @type {(() => void)[]} */
5667
- const destroyList = [];
5668
5718
  for (const { action, actions, width, label } of columns) {
5669
5719
  const th = tr.appendChild(document.createElement('th'));
5670
5720
  if (width) { th.setAttribute('width', `${width}`); }
@@ -5676,23 +5726,20 @@
5676
5726
  const button = th.appendChild(document.createElement('button'));
5677
5727
  button.addEventListener('click', add);
5678
5728
  button.classList.add('NeeloongForm-table-add');
5679
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5729
+ watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true, signal);
5680
5730
  }
5681
- return () => {
5682
- for (const destroy of destroyList) {
5683
- destroy();
5684
- }
5685
- };
5686
5731
  }
5687
5732
  /**
5688
5733
  *
5734
+ * @template T
5689
5735
  * @param {ArrayStore} store
5690
- * @param {StoreLayout.Renderer} fieldRenderer
5691
- * @param {StoreLayout.Field?} layout
5736
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
5737
+ * @param {StoreLayout.Field<T>?} layout
5692
5738
  * @param {StoreLayout.Options?} options
5693
- * @returns {[HTMLTableElement, () => void]}
5739
+ * @returns {HTMLTableElement?}
5694
5740
  */
5695
5741
  function Table(store, fieldRenderer, layout, options) {
5742
+ if (options?.signal?.aborted) { return null; }
5696
5743
  const headerColumns = layout?.columns;
5697
5744
  const fieldList = Object.entries(store.type || {})
5698
5745
  .filter(([k, v]) => typeof v?.type !== 'object')
@@ -5743,7 +5790,7 @@
5743
5790
  }
5744
5791
 
5745
5792
  const table = document.createElement('table');
5746
- table.className = 'NeeloongForm-table';
5793
+ table.classList.add('NeeloongForm-table');
5747
5794
  const thead = table.appendChild(document.createElement('thead'));
5748
5795
 
5749
5796
  const addable = new exports.Signal.Computed(() => store.addable);
@@ -5784,15 +5831,13 @@
5784
5831
  dragRow = -1;
5785
5832
 
5786
5833
  }
5787
- /** @type {(() => void)[]} */
5788
- const destroyList = [];
5789
- destroyList.push(renderHead(thead, columns, add, addable, Boolean(options?.editable)));
5834
+ renderHead(options?.signal, thead, columns, add, addable, Boolean(options?.editable));
5790
5835
  switch (layout?.tableFoot) {
5791
5836
  default:
5792
5837
  case 'header': {
5793
5838
  const tfoot = table.appendChild(document.createElement('tfoot'));
5794
5839
  tfoot.addEventListener('dragenter', () => { dragenter(); });
5795
- destroyList.push(renderHead(tfoot, columns, add, addable, Boolean(options?.editable)));
5840
+ renderHead(options?.signal, tfoot, columns, add, addable, Boolean(options?.editable));
5796
5841
  break;
5797
5842
  }
5798
5843
  case 'add': {
@@ -5804,39 +5849,34 @@
5804
5849
  const button = th.appendChild(document.createElement('button'));
5805
5850
  button.addEventListener('click', add);
5806
5851
  button.classList.add('NeeloongForm-table-foot-add');
5807
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
5852
+ watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true, options?.signal);
5808
5853
  break;
5809
5854
  }
5810
5855
  case 'none':
5811
5856
  }
5812
- const start = thead;
5813
- /** @type {Map<Store, [HTMLTableSectionElement, () => void]>} */
5857
+ /** @type {Map<Store, [HTMLTableSectionElement, AbortController]>} */
5814
5858
  let seMap = new Map();
5815
- /** @param {Map<Store, [tbody: HTMLTableSectionElement, destroy: () => void]>} map */
5816
- function destroyMap(map) {
5817
- for (const [el, destroy] of map.values()) {
5818
- destroy();
5819
- el.remove();
5820
- }
5821
-
5822
- }
5823
- const childrenResult = watch(() => store.children, function render(children) {
5859
+ watch(() => store.children, children => {
5824
5860
  let nextNode = thead.nextSibling;
5825
5861
  const oldSeMap = seMap;
5826
5862
  seMap = new Map();
5827
5863
  for (let child of children) {
5828
5864
  const old = oldSeMap.get(child);
5829
5865
  if (!old) {
5830
- const [el, destroy] = Line(child, fieldRenderer, layout, {
5866
+ const ac = new AbortController();
5867
+ const el = Line(child, fieldRenderer, layout, {
5831
5868
  columns,
5832
5869
  remove: remove.bind(null, child),
5833
5870
  dragenter: dragenter.bind(null, child),
5834
5871
  dragstart: dragstart.bind(null, child),
5835
5872
  dragend,
5836
5873
  deletable,
5837
- }, options);
5874
+ }, {
5875
+ ...options,
5876
+ signal: options?.signal ? AbortSignal.any([options?.signal, ac.signal]) : ac.signal,
5877
+ });
5838
5878
  table.insertBefore(el, nextNode);
5839
- seMap.set(child, [el, destroy]);
5879
+ seMap.set(child, [el, ac]);
5840
5880
  continue;
5841
5881
  }
5842
5882
  oldSeMap.delete(child);
@@ -5847,18 +5887,13 @@
5847
5887
  }
5848
5888
  table.insertBefore(old[0], nextNode);
5849
5889
  }
5850
- destroyMap(oldSeMap);
5851
- }, true);
5852
-
5853
- return [table, () => {
5854
- start.remove();
5855
- thead.remove();
5856
- destroyMap(seMap);
5857
- childrenResult();
5858
- for (const destroy of destroyList) {
5859
- destroy();
5890
+ for (const [el, ac] of oldSeMap.values()) {
5891
+ el.remove();
5892
+ ac.abort();
5860
5893
  }
5861
- }];
5894
+ }, true, options?.signal);
5895
+
5896
+ return table;
5862
5897
  }
5863
5898
 
5864
5899
  /** @import { StoreLayout } from '../types.mjs' */
@@ -5867,6 +5902,7 @@
5867
5902
  *
5868
5903
  * @param {HTMLElement} root
5869
5904
  * @param {StoreLayout.Grid?} [layout]
5905
+ * @returns {void}
5870
5906
  */
5871
5907
  function bindGrid(root, layout) {
5872
5908
  const { colStart, colSpan, colEnd, rowStart, rowSpan, rowEnd } = layout || {};
@@ -5885,211 +5921,109 @@
5885
5921
  } else if (rowSpan) {
5886
5922
  root.style.gridRow = `span ${rowSpan}`;
5887
5923
  }
5888
-
5889
-
5890
-
5891
-
5892
5924
  }
5893
5925
 
5894
- /** @import { CellValues } from './createCell.mjs' */
5895
-
5896
- /**
5897
- *
5898
- * @param {HTMLElement} root
5899
- * @param {CellValues} [values]
5900
- * @returns {() => void}
5901
- */
5902
- function bindErrored(root, values) {
5903
- return effect(() => {
5904
- if (values?.error) {
5905
- root.classList.add('NeeloongForm-item-errored');
5906
- } else {
5907
- root.classList.remove('NeeloongForm-item-errored');
5908
- }
5909
- });
5910
- }
5926
+ /** @import { StoreLayout } from '../types.mjs' */
5911
5927
 
5912
5928
  /**
5913
- *
5914
- * @param {HTMLElement} root
5915
- * @param {{ required?: boolean | null }} [values]
5916
- * @returns {() => void}
5929
+ * @typedef {object} CellValues
5930
+ * @property {string?} [label]
5931
+ * @property {string?} [description]
5932
+ * @property {string?} [error]
5933
+ * @property {boolean?} [required]
5917
5934
  */
5918
- function bindRequired(root, values) {
5919
- return effect(() => {
5920
- if (values?.required) {
5921
- root.classList.add('NeeloongForm-item-required');
5922
- } else {
5923
- root.classList.remove('NeeloongForm-item-required');
5924
- }
5925
- });
5926
- }
5927
-
5928
- /** @import { CellValues } from './createCell.mjs' */
5929
-
5930
5935
  /**
5931
5936
  *
5937
+ * @param {AbortSignal | null | undefined} signal
5932
5938
  * @param {CellValues} [values]
5933
- * @returns {[HTMLDivElement, () => void, HTMLDivElement, (() => void)[]]}
5939
+ * @returns {[HTMLDivElement, HTMLDivElement]}
5934
5940
  */
5935
- function createStdCell(values) {
5936
- /** @type {(() => void)[]} */
5937
- const destroyList = [];
5941
+ function createStdCell(signal, values) {
5938
5942
  const root = document.createElement('div');
5939
- root.className = 'NeeloongForm-item';
5940
- destroyList.push(bindRequired(root, values));
5941
-
5942
5943
  const label = root.appendChild(document.createElement('div'));
5943
- label.className = 'NeeloongForm-item-label';
5944
- destroyList.push(effect(() => label.innerText = values?.label || ''));
5944
+ label.classList.add('NeeloongForm-item-label');
5945
+ effect(() => label.innerText = values?.label || '', signal);
5945
5946
 
5946
5947
  const content = root.appendChild(document.createElement('div'));
5947
- content.className = 'NeeloongForm-item-content';
5948
+ content.classList.add('NeeloongForm-item-content');
5948
5949
 
5949
5950
  const description = root.appendChild(document.createElement('div'));
5950
- description.className = 'NeeloongForm-item-description';
5951
- destroyList.push(effect(() => description.innerText = values?.description || ''));
5951
+ description.classList.add('NeeloongForm-item-description');
5952
+ effect(() => description.innerText = values?.description || '', signal);
5952
5953
  const error = root.appendChild(document.createElement('div'));
5953
- error.className = 'NeeloongForm-item-error';
5954
- destroyList.push(effect(() => error.innerText = values?.error || ''));
5955
- destroyList.push(bindErrored(root, values));
5956
-
5957
- return [root, () => {
5958
- for (const destroy of destroyList) {
5959
- destroy();
5960
- }
5961
- }, content, destroyList];
5954
+ error.classList.add('NeeloongForm-item-error');
5955
+ effect(() => error.innerText = values?.error || '', signal);
5956
+ return [root, content];
5962
5957
 
5963
5958
 
5964
5959
  }
5965
5960
 
5966
- /** @import { CellValues } from './createCell.mjs' */
5967
-
5968
- /**
5969
- *
5970
- * @param {CellValues} [values]
5971
- * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]}
5972
- */
5973
- function createCollapseCell(values) {
5974
- /** @type {(() => void)[]} */
5975
- const destroyList = [];
5976
- const root = document.createElement('details');
5977
- root.className = 'NeeloongForm-item';
5978
- destroyList.push(bindRequired(root, values));
5979
-
5980
- root.open = true;
5981
- const summary = root.appendChild(document.createElement('summary'));
5982
- destroyList.push(effect(() => summary.innerText = values?.label || ''));
5983
- destroyList.push(bindErrored(root, values));
5984
-
5985
- return [root, () => {
5986
- for (const destroy of destroyList) {
5987
- destroy();
5988
- }
5989
- }, root, destroyList];
5990
-
5991
-
5992
- }
5993
-
5994
- /** @import { CellValues } from './createCell.mjs' */
5995
-
5996
- /**
5997
- *
5998
- * @param {CellValues} [values]
5999
- * @returns {[HTMLDivElement, () => void, HTMLDivElement, (() => void)[]]}
6000
- */
6001
- function createNullCell(values) {
6002
- /** @type {(() => void)[]} */
6003
- const destroyList = [];
6004
- const root = document.createElement('div');
6005
- root.className = 'NeeloongForm-item';
6006
- destroyList.push(bindRequired(root, values));
6007
- destroyList.push(bindErrored(root, values));
6008
-
6009
-
6010
- return [root, () => {
6011
- for (const destroy of destroyList) {
6012
- destroy();
6013
- }
6014
- }, root, destroyList];
6015
-
6016
-
6017
- }
6018
-
6019
- /** @import { CellValues } from './createCell.mjs' */
6020
-
6021
- /**
6022
- *
6023
- * @param {CellValues} [values]
6024
- * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]}
6025
- */
6026
- function createFieldsetCell(values) {
6027
- /** @type {(() => void)[]} */
6028
- const destroyList = [];
6029
- const root = document.createElement('fieldset');
6030
- root.className = 'NeeloongForm-item';
6031
- destroyList.push(bindRequired(root, values));
6032
-
6033
- const legend = root.appendChild(document.createElement('legend'));
6034
- destroyList.push(effect(() => legend.innerText = values?.label || ''));
6035
- destroyList.push(bindErrored(root, values));
6036
-
6037
- return [root, () => {
6038
- for (const destroy of destroyList) {
6039
- destroy();
6040
- }
6041
- }, root, destroyList];
6042
-
6043
-
6044
- }
6045
-
6046
- /** @import { StoreLayout } from '../types.mjs' */
6047
-
6048
- /**
6049
- * @typedef {object} CellValues
6050
- * @property {string?} [label]
6051
- * @property {string?} [description]
6052
- * @property {string?} [error]
6053
- * @property {boolean?} [required]
6054
- */
6055
5961
  /**
6056
5962
  *
5963
+ * @param {AbortSignal | null | undefined} signal
6057
5964
  * @param {StoreLayout.Grid?} [layout]
6058
5965
  * @param {CellValues} [values]
6059
5966
  * @param {StoreLayout.Grid['cell']?} [defCell]
6060
5967
  * @param {boolean?} [blockOnly]
6061
- * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]}
5968
+ * @returns {[HTMLElement, HTMLElement]}
6062
5969
  */
6063
- function createCell(layout, values, defCell, blockOnly) {
5970
+ function createCell(signal, layout, values, defCell, blockOnly) {
6064
5971
  /**
6065
5972
  *
6066
5973
  * @param {string?} [cellType]
6067
- * @returns {[HTMLElement, () => void, HTMLElement, (() => void)[]]?}
5974
+ * @returns {[HTMLElement, HTMLElement]?}
6068
5975
  */
6069
5976
  function create(cellType) {
6070
5977
  if (!cellType) { return null; }
6071
5978
  if (!blockOnly) {
6072
5979
  switch (cellType) {
6073
5980
  case 'inline': {
6074
- const result = createStdCell(values);
6075
- bindGrid(result[0], layout);
6076
- return result;
5981
+ const [root, content] = createStdCell(signal, values);
5982
+ bindGrid(root, layout);
5983
+ return [root, content];
6077
5984
  }
6078
5985
  case 'base': {
6079
- const result = createNullCell(values);
6080
- bindGrid(result[0], layout);
6081
- return result;
5986
+ const root = document.createElement('div');
5987
+ bindGrid(root, layout);
5988
+ return [root, root];
6082
5989
  }
6083
5990
  }
6084
5991
  }
6085
5992
  switch (cellType) {
6086
- case 'block': return createStdCell(values);
6087
- case 'fieldset': return createFieldsetCell(values);
6088
- case 'collapse': return createCollapseCell(values);
5993
+ case 'block': return createStdCell(signal, values);
5994
+ case 'fieldset': {
5995
+ const root = document.createElement('fieldset');
5996
+ const legend = root.appendChild(document.createElement('legend'));
5997
+ effect(() => legend.innerText = values?.label || '', signal);
5998
+ return [root, root];
5999
+ }
6000
+ case 'collapse': {
6001
+ const root = document.createElement('details');
6002
+ root.open = true;
6003
+ const summary = root.appendChild(document.createElement('summary'));
6004
+ effect(() => summary.innerText = values?.label || '', signal);
6005
+ return [root, root];
6006
+ }
6089
6007
  }
6090
6008
  return null;
6091
6009
  }
6092
- return create(layout?.cell) || create(defCell) || createStdCell(values);
6010
+ const [root, content] = create(layout?.cell) || create(defCell) || createStdCell(signal, values);
6011
+ root.classList.add('NeeloongForm-item');
6012
+ effect(() => {
6013
+ if (values?.error) {
6014
+ root.classList.add('NeeloongForm-item-errored');
6015
+ } else {
6016
+ root.classList.remove('NeeloongForm-item-errored');
6017
+ }
6018
+ }, signal);
6019
+ effect(() => {
6020
+ if (values?.required) {
6021
+ root.classList.add('NeeloongForm-item-required');
6022
+ } else {
6023
+ root.classList.remove('NeeloongForm-item-required');
6024
+ }
6025
+ }, signal);
6026
+ return [root, content];
6093
6027
  }
6094
6028
 
6095
6029
  /** @import { Store } from '../Store/index.mjs' */
@@ -6098,11 +6032,12 @@
6098
6032
 
6099
6033
  /**
6100
6034
  *
6035
+ * @template T
6101
6036
  * @param {Store<any, any>} store
6102
6037
  * @param {Signal.State<Store<any, any>?>} currentStore
6103
- * @param {StoreLayout.Renderer} fieldRenderer
6104
- * @param {StoreLayout.Field?} layout
6105
- * @param {State} initState
6038
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
6039
+ * @param {StoreLayout.Field<T>?} layout
6040
+ * @param {Signal.State<State>} state
6106
6041
  * @param {object} option
6107
6042
  * @param {StoreLayout.Column[]} option.columns
6108
6043
  * @param {() => void} option.remove
@@ -6114,15 +6049,13 @@
6114
6049
  * @param {() => void} option.addNode
6115
6050
  * @param {(store: Store<any, any>) => () => void} option.createDetails
6116
6051
  * @param {StoreLayout.Options?} options
6117
-
6118
- * @returns {[HTMLElement, () => void, (s: State) => void]}
6052
+ * @returns {HTMLElement}
6119
6053
  */
6120
6054
  function TreeLine(
6121
- store, currentStore, fieldRenderer, layout, initState, {
6055
+ store, currentStore, fieldRenderer, layout, state, {
6122
6056
  columns,
6123
6057
  remove, dragenter, dragstart, dragend, deletable, addNode, drop, createDetails,
6124
6058
  }, options) {
6125
- const state = new exports.Signal.State(initState);
6126
6059
  const root = document.createElement('div');
6127
6060
  root.addEventListener('dragstart', (event) => {
6128
6061
  if (event.target !== event.currentTarget) { return; }
@@ -6130,23 +6063,21 @@
6130
6063
  });
6131
6064
  root.addEventListener('dragend', dragend);
6132
6065
 
6133
- /** @type {(() => void)[]} */
6134
- const destroyList = [];
6135
6066
  root.classList.add('NeeloongForm-tree-item');
6136
6067
 
6137
- destroyList.push(effect(() => {
6068
+ effect(() => {
6138
6069
  if (currentStore.get() === store) {
6139
6070
  root.classList.add('NeeloongForm-tree-current');
6140
6071
  } else {
6141
6072
  root.classList.remove('NeeloongForm-tree-current');
6142
6073
  }
6143
- }));
6144
- destroyList.push(effect(() => {
6074
+ }, options?.signal);
6075
+ effect(() => {
6145
6076
  const level = state.get().level;
6146
6077
  root.style.setProperty(`--NeeloongForm-tree-level`, `${level}`);
6147
- }));
6078
+ }, options?.signal);
6148
6079
 
6149
- destroyList.push(effect(() => { root.hidden = state.get().hidden; }));
6080
+ effect(() => { root.hidden = state.get().hidden; }, options?.signal);
6150
6081
 
6151
6082
 
6152
6083
  /** @type {HTMLButtonElement[]} */
@@ -6199,9 +6130,9 @@
6199
6130
  dropChildren.addEventListener('dragover', (e) => e.preventDefault());
6200
6131
  dropFront.addEventListener('drop', () => drop());
6201
6132
  dropChildren.addEventListener('drop', () => drop(true));
6202
- destroyList.push(effect(() => {
6133
+ effect(() => {
6203
6134
  dropFront.hidden = dropChildren.hidden = !state.get().droppable;
6204
- }));
6135
+ }, options?.signal);
6205
6136
 
6206
6137
  let dragleave = () => { };
6207
6138
  dropFront.addEventListener('dragenter', () => dragleave = dragenter(dropFront));
@@ -6218,9 +6149,8 @@
6218
6149
  if (field) {
6219
6150
  const child = store.child(field);
6220
6151
  if (!child) { continue; }
6221
- const [el, destroy] = FormFieldInline(child, fieldRenderer, null, { ...options, editable: false });
6222
- destroyList.push(destroy);
6223
- td.appendChild(el);
6152
+ const el = FormFieldInline(child, fieldRenderer, null, { ...options, editable: false });
6153
+ if (el) { td.appendChild(el); }
6224
6154
  continue;
6225
6155
  }
6226
6156
  if (typeof placeholder === 'number') {
@@ -6258,9 +6188,9 @@
6258
6188
  const move = line.appendChild(document.createElement('button'));
6259
6189
  move.classList.add('NeeloongForm-tree-move');
6260
6190
  move.addEventListener('pointerdown', pointerdown);
6261
- destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
6191
+ watch(() => store.readonly || store.disabled, disabled => {
6262
6192
  move.disabled = disabled;
6263
- }, true));
6193
+ }, true, options.signal);
6264
6194
  continue;
6265
6195
  }
6266
6196
  case 'add': {
@@ -6268,9 +6198,9 @@
6268
6198
  const move = line.appendChild(document.createElement('button'));
6269
6199
  move.classList.add('NeeloongForm-tree-add');
6270
6200
  move.addEventListener('click', addNode);
6271
- destroyList.push(watch(() => store.readonly || store.disabled, disabled => {
6201
+ watch(() => store.readonly || store.disabled, disabled => {
6272
6202
  move.disabled = disabled;
6273
- }, true));
6203
+ }, true, options.signal);
6274
6204
  continue;
6275
6205
  }
6276
6206
  case 'remove': {
@@ -6278,9 +6208,9 @@
6278
6208
  const del = line.appendChild(document.createElement('button'));
6279
6209
  del.classList.add('NeeloongForm-tree-remove');
6280
6210
  del.addEventListener('click', remove);
6281
- destroyList.push(watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
6211
+ watch(() => !deletable.get() || store.readonly || store.disabled, disabled => {
6282
6212
  del.disabled = disabled;
6283
- }, true));
6213
+ }, true, options.signal);
6284
6214
  continue;
6285
6215
  }
6286
6216
  case 'serial': {
@@ -6291,7 +6221,7 @@
6291
6221
  }
6292
6222
  }
6293
6223
  }
6294
- destroyList.push(effect(() => {
6224
+ effect(() => {
6295
6225
  const s = state.get();
6296
6226
  if (!s.hasChildren) {
6297
6227
  for (const btn of collapseList) {
@@ -6312,13 +6242,9 @@
6312
6242
  btn.disabled = false;
6313
6243
  }
6314
6244
  }
6315
- }));
6245
+ }, options?.signal);
6316
6246
 
6317
- return [root, () => {
6318
- for (const destroy of destroyList) {
6319
- destroy();
6320
- }
6321
- }, s => state.set(s)];
6247
+ return root;
6322
6248
  }
6323
6249
 
6324
6250
  /** @import { Store, ArrayStore } from '../Store/index.mjs' */
@@ -6428,13 +6354,15 @@
6428
6354
  }
6429
6355
  /**
6430
6356
  *
6357
+ * @template T
6431
6358
  * @param {ArrayStore} store
6432
- * @param {StoreLayout.Renderer} fieldRenderer
6433
- * @param {StoreLayout.Field?} layout
6359
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
6360
+ * @param {StoreLayout.Field<T>?} layout
6434
6361
  * @param {StoreLayout.Options?} options
6435
- * @returns {[HTMLElement, () => void]}
6362
+ * @returns {HTMLElement?}
6436
6363
  */
6437
6364
  function Tree(store, fieldRenderer, layout, options) {
6365
+ if (options?.signal?.aborted) { return null; }
6438
6366
  const headerColumns = layout?.columns;
6439
6367
  const fieldList = Object.entries(store.type || {})
6440
6368
  .filter(([k, v]) => typeof v?.type !== 'object')
@@ -6488,13 +6416,13 @@
6488
6416
 
6489
6417
 
6490
6418
  const root = document.createElement('div');
6491
- root.className = 'NeeloongForm-tree';
6419
+ root.classList.add('NeeloongForm-tree');
6492
6420
  const main = root.appendChild(document.createElement('div'));
6493
- main.className = 'NeeloongForm-tree-main';
6421
+ main.classList.add('NeeloongForm-tree-main');
6494
6422
  const splitter = root.appendChild(document.createElement('div'));
6495
- splitter.className = 'NeeloongForm-tree-splitter';
6423
+ splitter.classList.add('NeeloongForm-tree-splitter');
6496
6424
  const details = root.appendChild(document.createElement('div'));
6497
- details.className = 'NeeloongForm-tree-details';
6425
+ details.classList.add('NeeloongForm-tree-details');
6498
6426
  splitter.hidden = true;
6499
6427
  details.hidden = true;
6500
6428
  /** @type {number?} */
@@ -6558,9 +6486,8 @@
6558
6486
  });
6559
6487
 
6560
6488
 
6561
- /** @type {(() => void)[]} */
6562
- const destroyList = [];
6563
- let destroyDetails = () => { };
6489
+ /** @type {AbortController?} */
6490
+ let detailAbortController = null;
6564
6491
  const detailsStore = new exports.Signal.State(/** @type{Store<any, any>?}*/(null));
6565
6492
  /**
6566
6493
  *
@@ -6568,25 +6495,36 @@
6568
6495
  * @returns
6569
6496
  */
6570
6497
  function createDetails(store) {
6571
- if (detailsStore.get() === store) { return destroyDetails; }
6572
- destroyDetails();
6498
+ if (options?.signal?.aborted) { return () => { }; }
6499
+ if (detailsStore.get() === store && detailAbortController) {
6500
+ const ac = detailAbortController;
6501
+ return () => { ac.abort(); };
6502
+ }
6503
+ detailAbortController?.abort();
6573
6504
  detailsStore.set(store);
6574
- const [form, destroy] = Form(store, fieldRenderer, layout, options);
6575
- details.appendChild(form);
6576
- details.hidden = false;
6577
- splitter.hidden = false;
6578
- let done = false;
6579
- destroyDetails = () => {
6580
- if (done) { return; }
6581
- done = true;
6505
+ const ac = new AbortController();
6506
+ detailAbortController = ac;
6507
+ const signal = options?.signal ? AbortSignal.any([options?.signal, ac.signal]) : ac.signal;
6508
+ const form = Form(store, fieldRenderer, layout, {
6509
+ ...options,
6510
+ signal: options?.signal ? AbortSignal.any([options?.signal, signal]) : signal,
6511
+ });
6512
+ signal.addEventListener('abort', () => {
6582
6513
  detailsStore.set(null);
6583
- form.remove();
6584
- destroy();
6585
6514
  stopMove();
6586
- splitter.hidden = true;
6587
- details.hidden = true;
6588
- };
6589
- return destroyDetails;
6515
+ }, { once: true });
6516
+ if (form) {
6517
+ details.appendChild(form);
6518
+ details.hidden = false;
6519
+ splitter.hidden = false;
6520
+ signal.addEventListener('abort', () => {
6521
+ form.remove();
6522
+ splitter.hidden = true;
6523
+ details.hidden = true;
6524
+ }, { once: true });
6525
+
6526
+ }
6527
+ return () => { ac.abort(); };
6590
6528
 
6591
6529
  }
6592
6530
 
@@ -6722,7 +6660,7 @@
6722
6660
  const button = main.appendChild(document.createElement('button'));
6723
6661
  button.addEventListener('click', () => addNode(-1));
6724
6662
  button.classList.add('NeeloongForm-tree-head-add');
6725
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
6663
+ watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true, options.signal);
6726
6664
  }
6727
6665
  const start = main.appendChild(document.createComment(''));
6728
6666
  if (options?.editable) {
@@ -6742,21 +6680,13 @@
6742
6680
  dropFront.addEventListener('drop', () => drop());
6743
6681
 
6744
6682
 
6745
- destroyList.push(watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true));
6683
+ watch(() => !addable.get(), disabled => { button.disabled = disabled; }, true, options.signal);
6746
6684
  }
6747
- /** @type {Map<Store, [HTMLElement, () => void, (s: State) => void]>} */
6685
+ /** @type {Map<Store, [tbody: HTMLElement, AbortController, (s: State) => void]>} */
6748
6686
  let seMap = new Map();
6749
- /** @param {Map<Store, [tbody: HTMLElement, destroy: () => void, (s: State) => void]>} map */
6750
- function destroyMap(map) {
6751
- for (const [el, destroy] of map.values()) {
6752
- destroy();
6753
- el.remove();
6754
- }
6755
-
6756
- }
6757
6687
  /** @type {State[]} */
6758
6688
  const states = [];
6759
- const childrenResult = watch(() => store.children, function render(children) {
6689
+ watch(() => store.children, children => {
6760
6690
  let nextNode = start.nextSibling;
6761
6691
  const oldSeMap = seMap;
6762
6692
  seMap = new Map();
@@ -6771,7 +6701,9 @@
6771
6701
  const state = states[i];
6772
6702
  const old = oldSeMap.get(child);
6773
6703
  if (!old) {
6774
- const [el, destroy, setState] = TreeLine(child, detailsStore, fieldRenderer, layout, state, {
6704
+ const elState = new exports.Signal.State(state);
6705
+ const ac = new AbortController();
6706
+ const el = TreeLine(child, detailsStore, fieldRenderer, layout, elState, {
6775
6707
  columns,
6776
6708
  remove: remove.bind(null, child),
6777
6709
  dragenter,
@@ -6781,9 +6713,12 @@
6781
6713
  addNode: () => addNode(Number(child.index)),
6782
6714
  createDetails,
6783
6715
  drop: drop.bind(null, child),
6784
- }, options);
6716
+ }, {
6717
+ ...options,
6718
+ signal: options?.signal ? AbortSignal.any([options?.signal, ac.signal]) : ac.signal,
6719
+ });
6785
6720
  main.insertBefore(el, nextNode);
6786
- seMap.set(child, [el, destroy, setState]);
6721
+ seMap.set(child, [el, ac, s => elState.set(s)]);
6787
6722
  continue;
6788
6723
  }
6789
6724
  oldSeMap.delete(child);
@@ -6796,77 +6731,78 @@
6796
6731
  main.insertBefore(old[0], nextNode);
6797
6732
  }
6798
6733
  states.splice(childrenLength);
6799
- destroyMap(oldSeMap);
6800
- }, true);
6734
+ for (const [el, ac] of oldSeMap.values()) {
6735
+ el.remove();
6736
+ ac.abort();
6737
+ }
6738
+ }, true, options?.signal);
6801
6739
 
6802
- return [root, () => {
6803
- start.remove();
6804
- destroyMap(seMap);
6805
- childrenResult();
6806
- destroyDetails?.();
6807
- for (const destroy of destroyList) {
6808
- destroy();
6809
- }
6810
- }];
6740
+ return root;
6811
6741
  }
6812
6742
 
6813
6743
  /**
6814
6744
  *
6745
+ * @template T
6815
6746
  * @param {string | ParentNode} html
6816
6747
  * @param {Store<any, any>} store
6817
- * @param {StoreLayout.Renderer} fieldRenderer
6748
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
6818
6749
  * @param {StoreLayout.Options?} options
6819
- * @param {StoreLayout.Field?} layout
6820
- * @returns
6750
+ * @param {StoreLayout.Field<T>?} layout
6751
+ * @returns {ParentNode}
6821
6752
  */
6822
6753
  function Html(html, store, fieldRenderer, options, layout) {
6823
6754
  const htmlContent = getHtmlContent(html);
6824
- const destroy = renderHtml(store, fieldRenderer, htmlContent, options, layout);
6825
- return [htmlContent, destroy];
6755
+ renderHtml(store, fieldRenderer, htmlContent, options, layout);
6756
+ return htmlContent;
6826
6757
  }
6827
-
6828
6758
  /**
6829
6759
  *
6830
- * @param {StoreLayout.Field['arrayStyle']?} arrayStyle
6760
+ * @template T
6761
+ * @param {StoreLayout.Field<T>['arrayStyle']?} arrayStyle
6762
+ * @param {ArrayStore} store
6763
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
6764
+ * @param {StoreLayout.Field<T>?} layout
6765
+ * @param {StoreLayout.Options?} options
6766
+ * @returns {HTMLElement?}
6831
6767
  */
6832
- function getArrayCell(arrayStyle) {
6833
- switch(arrayStyle) {
6834
- case 'tree': return Tree;
6835
- default: return Table;
6768
+ function renderArrayCell(arrayStyle, store, fieldRenderer, layout, options) {
6769
+ switch (arrayStyle) {
6770
+ case 'tree': return Tree(store, fieldRenderer, layout, options);
6771
+ default: return Table(store, fieldRenderer, layout, options);
6836
6772
  }
6837
6773
 
6838
6774
  }
6839
6775
 
6840
6776
  /**
6841
6777
  *
6778
+ * @template T
6842
6779
  * @param {Store<any, any>} store
6843
- * @param {StoreLayout.Renderer} fieldRenderer
6844
- * @param {StoreLayout.Field?} layout
6780
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
6781
+ * @param {StoreLayout.Field<T>?} layout
6845
6782
  * @param {StoreLayout.Options?} options
6846
- * @returns {[ParentNode, () => void]}
6783
+ * @returns {ParentNode}
6847
6784
  */
6848
6785
  function FormField(store, fieldRenderer, layout, options) {
6849
6786
  const { type } = store;
6850
- const isObject = type && typeof type === 'object';
6787
+ const isObject = Boolean(type && typeof type === 'object');
6851
6788
  const html = layout?.html;
6852
6789
  /** @type {StoreLayout.Grid['cell']} */
6853
6790
  const cellType = isObject
6854
6791
  ? store instanceof ArrayStore ? 'collapse' : 'fieldset'
6855
6792
  : html ? 'base' : 'inline';
6856
- const [root, destroy, content, destroyList] = createCell(layout, store, cellType, isObject);
6857
- destroyList.push(effect(() => root.hidden = store.hidden));
6793
+ const [root, content] = createCell(options?.signal, layout, store, cellType, isObject);
6794
+ effect(() => root.hidden = store.hidden, options?.signal);
6858
6795
 
6796
+ /** @type {false | ParentNode | null} */
6859
6797
  const r =
6860
6798
  html && Html(html, store, fieldRenderer, options, layout)
6861
- || fieldRenderer(store, options)
6862
- || store instanceof ArrayStore && getArrayCell(layout?.arrayStyle)(store, fieldRenderer, layout, options)
6799
+ || fieldRenderer(store, layout?.renderer, options)
6800
+ || store instanceof ArrayStore && renderArrayCell(layout?.arrayStyle, store, fieldRenderer, layout, options)
6863
6801
  || isObject && Form(store, fieldRenderer, layout, options);
6864
6802
  if (r) {
6865
- const [el, destroy] = r;
6866
- content.appendChild(el);
6867
- destroyList.push(destroy);
6803
+ content.appendChild(r);
6868
6804
  }
6869
- return [root, destroy];
6805
+ return root;
6870
6806
  }
6871
6807
 
6872
6808
  /** @import { Store } from '../Store/index.mjs' */
@@ -6877,22 +6813,22 @@
6877
6813
  * @param {Store<any, any>} store
6878
6814
  * @param {StoreLayout.Button} layout
6879
6815
  * @param {StoreLayout.Options?} options
6880
- * @returns {[ParentNode, () => void]}
6816
+ * @returns {ParentNode}
6881
6817
  */
6882
6818
  function FormButton(store, layout, options) {
6883
- const [root, destroy, content, destroyList] = createCell(layout, store);
6819
+ const [root, content] = createCell(options?.signal, layout, store);
6884
6820
  const button = document.createElement('button');
6885
- destroyList.push(() => {
6821
+ effect(() => {
6886
6822
  const t = layout.text;
6887
6823
  const text = typeof t === 'function' ? t(store, options) : t;
6888
6824
  button.innerText = text ?? '';
6889
- });
6890
- destroyList.push(() => {
6825
+ }, options?.signal);
6826
+ effect(() => {
6891
6827
  const d = layout.disabled;
6892
6828
  const disabled = typeof d === 'function' ? d(store, options) : d;
6893
6829
  button.disabled = Boolean(disabled);
6894
- });
6895
- button.className = 'NeeloongForm-item-button';
6830
+ }, options?.signal);
6831
+ button.classList.add('NeeloongForm-item-button');
6896
6832
  content.appendChild(button);
6897
6833
  const click = layout.click;
6898
6834
  if (typeof click === 'function') {
@@ -6903,7 +6839,7 @@
6903
6839
  button.addEventListener('click', e => call(click, e, store, options));
6904
6840
  }
6905
6841
  }
6906
- return [root, destroy];
6842
+ return root;
6907
6843
  }
6908
6844
 
6909
6845
  /** @import { Store } from '../Store/index.mjs' */
@@ -6911,20 +6847,21 @@
6911
6847
 
6912
6848
  /**
6913
6849
  *
6850
+ * @template T
6914
6851
  * @param {Store<any, any>} store
6915
- * @param {StoreLayout.Renderer} fieldRenderer
6852
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
6916
6853
  * @param {StoreLayout.Html} layout
6917
6854
  * @param {StoreLayout.Options?} options
6918
- * @returns {[ParentNode, () => void]}
6855
+ * @returns {ParentNode?}
6919
6856
  */
6920
6857
  function FormHtml(store, fieldRenderer, layout, options) {
6921
- const [root, destroy, content, destroyList] = createCell(layout, store);
6922
6858
  const html = layout.html;
6923
- if (!html) { return [root, destroy]; }
6859
+ if (!html) { return null; }
6860
+ const [root, content] = createCell(options?.signal, layout, store);
6924
6861
  const htmlContent = getHtmlContent(html);
6925
- destroyList.push(renderHtml(store, fieldRenderer, htmlContent, options, layout));
6862
+ renderHtml(store, fieldRenderer, htmlContent, options, layout);
6926
6863
  content.appendChild(htmlContent);
6927
- return [root, destroy];
6864
+ return root;
6928
6865
  }
6929
6866
 
6930
6867
  /** @import { Store } from '../Store/index.mjs' */
@@ -6932,11 +6869,12 @@
6932
6869
 
6933
6870
  /**
6934
6871
  *
6872
+ * @template T
6935
6873
  * @param {Store<any, any>} store
6936
- * @param {StoreLayout.Renderer} fieldRenderer
6937
- * @param {StoreLayout.Item} item
6874
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
6875
+ * @param {StoreLayout.Item<T>} item
6938
6876
  * @param {StoreLayout.Options?} options
6939
- * @returns {[ParentNode, () => void]?}
6877
+ * @returns {ParentNode?}
6940
6878
  */
6941
6879
  function FormItem(store, fieldRenderer, item, options) {
6942
6880
  if (item.type === 'button') {
@@ -6954,40 +6892,32 @@
6954
6892
 
6955
6893
  /**
6956
6894
  *
6895
+ * @template T
6957
6896
  * @param {Store<any, any>} store
6958
- * @param {StoreLayout.Renderer} fieldRenderer
6959
- * @param {StoreLayout?} layout
6897
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
6898
+ * @param {StoreLayout<T>?} layout
6960
6899
  * @param {StoreLayout.Options?} options
6961
6900
  * @param {HTMLElement} [parent]
6962
- * @returns {[HTMLElement, () => void]}
6901
+ * @returns {HTMLElement?}
6963
6902
  */
6964
6903
  function Form(store, fieldRenderer, layout, options, parent) {
6904
+ if (options?.signal?.aborted) { return null; }
6965
6905
  const root = parent instanceof HTMLElement ? parent : document.createElement('div');
6966
- root.className = 'NeeloongForm';
6967
- /** @type {(() => void)[]} */
6968
- const destroyList = [];
6906
+ root.classList.add('NeeloongForm');
6969
6907
  const fieldLayouts = layout?.fields;
6970
6908
  if (fieldLayouts) {
6971
6909
  for (const fieldTemplate of fieldLayouts) {
6972
- const result = FormItem(store, fieldRenderer, fieldTemplate, options);
6973
- if (!result) { continue; }
6974
- const [el, destroy] = result;
6975
- root.appendChild(el);
6976
- destroyList.push(destroy);
6910
+ const el = FormItem(store, fieldRenderer, fieldTemplate, options);
6911
+ if (el) { root.appendChild(el); }
6977
6912
  }
6978
6913
  } else {
6979
6914
  const fields = [...store].map(([, v]) => v);
6980
6915
  for (const field of fields) {
6981
- const [el, destroy] = FormField(field, fieldRenderer, null, options);
6982
- root.appendChild(el);
6983
- destroyList.push(destroy);
6916
+ const el = FormField(field, fieldRenderer, null, options);
6917
+ if (el) { root.appendChild(el); }
6984
6918
  }
6985
6919
  }
6986
- return [root, () => {
6987
- for (const destroy of destroyList) {
6988
- destroy();
6989
- }
6990
- }];
6920
+ return root;
6991
6921
 
6992
6922
  }
6993
6923
 
@@ -6996,22 +6926,27 @@
6996
6926
 
6997
6927
  /**
6998
6928
  *
6929
+ * @template T
6999
6930
  * @param {Store} store
7000
- * @param {StoreLayout.Renderer} fieldRenderer
6931
+ * @param {StoreLayout.Renderer<T>} fieldRenderer
7001
6932
  * @param {HTMLElement} root
7002
- * @param {StoreLayout?} [layout]
6933
+ * @param {StoreLayout<T>?} [layout]
7003
6934
  * @param {StoreLayout.Options & {clone?: boolean} | null} [options]
6935
+ * @returns {void}
7004
6936
  */
7005
6937
  function renderStore(store, fieldRenderer, root, layout, options) {
6938
+ if (options?.signal?.aborted) { return; }
7006
6939
  const html = layout?.html;
7007
- if (html) {
7008
- const content = getHtmlContent(html);
7009
- const destroy = renderHtml(store, fieldRenderer, content, options || null, layout);
7010
- root.appendChild(content);
7011
- return destroy;
7012
- }
7013
- const s = Form(store, fieldRenderer, layout || null, options || null, root);
7014
- return s[1];
6940
+ if (!html) {
6941
+ Form(store, fieldRenderer, layout || null, options || null, root);
6942
+ return;
6943
+ }
6944
+ const content = getHtmlContent(html);
6945
+ renderHtml(store, fieldRenderer, content, options || null, layout);
6946
+ root.appendChild(content);
6947
+ options?.signal?.addEventListener('abort', () => {
6948
+ root.removeChild(content);
6949
+ }, { once: true });
7015
6950
  }
7016
6951
 
7017
6952
  exports.ArrayStore = ArrayStore;