persian-data-grid 0.6.0 → 0.7.1

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.
Files changed (137) hide show
  1. package/README.md +1 -1
  2. package/dist/{ERPTableGroupRow-PSAPAHGL.js → ERPTableGroupRow-E5Q6FYDS.js} +4 -4
  3. package/dist/{ERPTableGroupRow-PSAPAHGL.js.map → ERPTableGroupRow-E5Q6FYDS.js.map} +1 -1
  4. package/dist/{ERPTableGroupRow-J5Z2LRC5.cjs → ERPTableGroupRow-F2IPDYL5.cjs} +9 -9
  5. package/dist/{ERPTableGroupRow-J5Z2LRC5.cjs.map → ERPTableGroupRow-F2IPDYL5.cjs.map} +1 -1
  6. package/dist/{GridPlugin-CJCaYnlw.d.ts → GridPlugin-Bewn0X51.d.ts} +1 -1
  7. package/dist/{GridPlugin-oxhq8AsM.d.cts → GridPlugin-DewWufGZ.d.cts} +1 -1
  8. package/dist/{GridSession-Bc7UIawO.d.ts → GridSession-CeIfVFck.d.ts} +142 -9
  9. package/dist/{GridSession-B-9lLcoB.d.cts → GridSession-CmQn2RnL.d.cts} +142 -9
  10. package/dist/GridSyncService-3JQLEY65.cjs +12 -0
  11. package/dist/{GridSyncService-TAPAHABV.cjs.map → GridSyncService-3JQLEY65.cjs.map} +1 -1
  12. package/dist/GridSyncService-GECXGEKL.js +3 -0
  13. package/dist/{GridSyncService-KTLB4KP3.js.map → GridSyncService-GECXGEKL.js.map} +1 -1
  14. package/dist/{PdfRenderer-CBg7hWNS.d.cts → PdfRenderer-B9FJaCo_.d.cts} +1 -1
  15. package/dist/{PdfRenderer-1HFh6UeR.d.ts → PdfRenderer-bN7ca_Aj.d.ts} +1 -1
  16. package/dist/{UITableController-DU8RopUM.d.ts → UITableController-C71LXBiX.d.ts} +48 -2
  17. package/dist/{UITableController-Cuwq481K.d.cts → UITableController-CIwnHIw8.d.cts} +48 -2
  18. package/dist/{VazirmatnPdfRenderer-BCMpC1zy.d.cts → VazirmatnPdfRenderer-CpCqA-Wh.d.cts} +2 -2
  19. package/dist/{VazirmatnPdfRenderer-Dz_G-m3c.d.ts → VazirmatnPdfRenderer-D46smncn.d.ts} +2 -2
  20. package/dist/adapters/arian-erp.cjs +16 -4
  21. package/dist/adapters/arian-erp.cjs.map +1 -1
  22. package/dist/adapters/arian-erp.d.cts +3 -1
  23. package/dist/adapters/arian-erp.d.ts +3 -1
  24. package/dist/adapters/arian-erp.js +11 -3
  25. package/dist/adapters/arian-erp.js.map +1 -1
  26. package/dist/{applyImportedRows-BO6yxoAN.d.cts → applyImportedRows-C2fWYoPy.d.cts} +3 -3
  27. package/dist/{applyImportedRows-D4zVY8uD.d.ts → applyImportedRows-Cqh_N_0k.d.ts} +3 -3
  28. package/dist/{applyRowVisibilityHandler-LXCOGESZ.cjs → applyRowVisibilityHandler-Q64ZFC7A.cjs} +7 -6
  29. package/dist/applyRowVisibilityHandler-Q64ZFC7A.cjs.map +1 -0
  30. package/dist/{applyRowVisibilityHandler-KS6TMCBN.js → applyRowVisibilityHandler-ZLZXQEGY.js} +6 -5
  31. package/dist/applyRowVisibilityHandler-ZLZXQEGY.js.map +1 -0
  32. package/dist/callbackRemoteApi-BYLjmSI6.d.ts +51 -0
  33. package/dist/callbackRemoteApi-Cv26MmSD.d.cts +51 -0
  34. package/dist/{cellRenderContext-DB7V_mlu.d.ts → cellRenderContext-BaNh4qn_.d.ts} +2 -2
  35. package/dist/{cellRenderContext-DG5BOlxK.d.cts → cellRenderContext-DvngkptV.d.cts} +2 -2
  36. package/dist/{chunk-HTNJT2QU.js → chunk-27ALJSZP.js} +617 -104
  37. package/dist/chunk-27ALJSZP.js.map +1 -0
  38. package/dist/{chunk-KUBVDIF7.cjs → chunk-3HP3HQCW.cjs} +52 -17
  39. package/dist/chunk-3HP3HQCW.cjs.map +1 -0
  40. package/dist/{chunk-DXRCYW42.cjs → chunk-52LFOPXD.cjs} +43 -23
  41. package/dist/chunk-52LFOPXD.cjs.map +1 -0
  42. package/dist/{chunk-VVGYRZ7S.cjs → chunk-72IVAOTK.cjs} +4 -4
  43. package/dist/{chunk-VVGYRZ7S.cjs.map → chunk-72IVAOTK.cjs.map} +1 -1
  44. package/dist/{chunk-LJ7LMEXO.js → chunk-7GWV5HGO.js} +3 -3
  45. package/dist/{chunk-LJ7LMEXO.js.map → chunk-7GWV5HGO.js.map} +1 -1
  46. package/dist/{chunk-RM6ZWNT3.js → chunk-7T6AWRPU.js} +43 -23
  47. package/dist/chunk-7T6AWRPU.js.map +1 -0
  48. package/dist/{chunk-PXCHPZTP.cjs → chunk-CMQ4VLCW.cjs} +8 -7
  49. package/dist/{chunk-EHHHC5JV.js.map → chunk-CMQ4VLCW.cjs.map} +1 -1
  50. package/dist/chunk-E2J533UH.cjs +53 -0
  51. package/dist/chunk-E2J533UH.cjs.map +1 -0
  52. package/dist/chunk-F6EMC75A.cjs +34 -0
  53. package/dist/chunk-F6EMC75A.cjs.map +1 -0
  54. package/dist/chunk-GKE33FOJ.js +32 -0
  55. package/dist/chunk-GKE33FOJ.js.map +1 -0
  56. package/dist/{chunk-QQMU44KJ.js → chunk-HLOHWFOA.js} +52 -19
  57. package/dist/chunk-HLOHWFOA.js.map +1 -0
  58. package/dist/{chunk-633KP74Z.js → chunk-KCNTKYUY.js} +19 -2
  59. package/dist/chunk-KCNTKYUY.js.map +1 -0
  60. package/dist/{chunk-BLYVOV3G.cjs → chunk-LY27YAXE.cjs} +19 -2
  61. package/dist/chunk-LY27YAXE.cjs.map +1 -0
  62. package/dist/{chunk-EHHHC5JV.js → chunk-NB5GCGHL.js} +5 -4
  63. package/dist/chunk-NB5GCGHL.js.map +1 -0
  64. package/dist/{chunk-6XBXZPBW.js → chunk-NQUVAJQA.js} +3 -3
  65. package/dist/{chunk-6XBXZPBW.js.map → chunk-NQUVAJQA.js.map} +1 -1
  66. package/dist/chunk-SVO62ZV2.js +50 -0
  67. package/dist/chunk-SVO62ZV2.js.map +1 -0
  68. package/dist/{chunk-MU3RY46O.js → chunk-VBAZ77Q4.js} +3 -3
  69. package/dist/{chunk-MU3RY46O.js.map → chunk-VBAZ77Q4.js.map} +1 -1
  70. package/dist/{chunk-H2LESGMX.cjs → chunk-VSAFQTW5.cjs} +4 -4
  71. package/dist/{chunk-H2LESGMX.cjs.map → chunk-VSAFQTW5.cjs.map} +1 -1
  72. package/dist/{chunk-2BJCRYOS.cjs → chunk-VYJQ2HTA.cjs} +5 -5
  73. package/dist/{chunk-2BJCRYOS.cjs.map → chunk-VYJQ2HTA.cjs.map} +1 -1
  74. package/dist/{chunk-IIISFUW4.cjs → chunk-WTHTVKVI.cjs} +621 -108
  75. package/dist/chunk-WTHTVKVI.cjs.map +1 -0
  76. package/dist/{createEngineGridQuery-Mt1hc33m.d.ts → createEngineGridQuery-BBkud5oE.d.ts} +61 -6
  77. package/dist/{createEngineGridQuery-Dmq3i5v3.d.cts → createEngineGridQuery-D-fgO_jg.d.cts} +61 -6
  78. package/dist/date-picker-jalali.cjs +17 -17
  79. package/dist/date-picker-jalali.d.cts +3 -3
  80. package/dist/date-picker-jalali.d.ts +3 -3
  81. package/dist/date-picker-jalali.js +1 -1
  82. package/dist/headless.cjs +102 -102
  83. package/dist/headless.d.cts +13 -9
  84. package/dist/headless.d.ts +13 -9
  85. package/dist/headless.js +4 -4
  86. package/dist/index.cjs +440 -382
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.d.cts +34 -14
  89. package/dist/index.d.ts +34 -14
  90. package/dist/index.js +200 -143
  91. package/dist/index.js.map +1 -1
  92. package/dist/plugins/csv.cjs +3 -3
  93. package/dist/plugins/csv.d.cts +2 -2
  94. package/dist/plugins/csv.d.ts +2 -2
  95. package/dist/plugins/csv.js +2 -2
  96. package/dist/plugins/excel.cjs +11 -11
  97. package/dist/plugins/excel.d.cts +4 -4
  98. package/dist/plugins/excel.d.ts +4 -4
  99. package/dist/plugins/excel.js +3 -3
  100. package/dist/plugins/pdf/fonts/vazirmatn.cjs +6 -6
  101. package/dist/plugins/pdf/fonts/vazirmatn.d.cts +4 -4
  102. package/dist/plugins/pdf/fonts/vazirmatn.d.ts +4 -4
  103. package/dist/plugins/pdf/fonts/vazirmatn.js +3 -3
  104. package/dist/plugins/pdf.cjs +5 -5
  105. package/dist/plugins/pdf.d.cts +5 -5
  106. package/dist/plugins/pdf.d.ts +5 -5
  107. package/dist/plugins/pdf.js +2 -2
  108. package/dist/presence.d.cts +3 -3
  109. package/dist/presence.d.ts +3 -3
  110. package/dist/sync.cjs +2 -2
  111. package/dist/sync.d.cts +3 -3
  112. package/dist/sync.d.ts +3 -3
  113. package/dist/sync.js +1 -1
  114. package/package.json +3 -3
  115. package/dist/GridSyncService-KTLB4KP3.js +0 -3
  116. package/dist/GridSyncService-TAPAHABV.cjs +0 -12
  117. package/dist/applyRowVisibilityHandler-KS6TMCBN.js.map +0 -1
  118. package/dist/applyRowVisibilityHandler-LXCOGESZ.cjs.map +0 -1
  119. package/dist/callbackRemoteApi-BlXO5NJz.d.ts +0 -30
  120. package/dist/callbackRemoteApi-DgnXH-i0.d.cts +0 -30
  121. package/dist/chunk-633KP74Z.js.map +0 -1
  122. package/dist/chunk-ASPTRYRD.cjs +0 -21
  123. package/dist/chunk-ASPTRYRD.cjs.map +0 -1
  124. package/dist/chunk-BHDRUZH5.cjs +0 -33
  125. package/dist/chunk-BHDRUZH5.cjs.map +0 -1
  126. package/dist/chunk-BLYVOV3G.cjs.map +0 -1
  127. package/dist/chunk-DXRCYW42.cjs.map +0 -1
  128. package/dist/chunk-HTNJT2QU.js.map +0 -1
  129. package/dist/chunk-IIISFUW4.cjs.map +0 -1
  130. package/dist/chunk-KUBVDIF7.cjs.map +0 -1
  131. package/dist/chunk-KYAOSBQN.js +0 -31
  132. package/dist/chunk-KYAOSBQN.js.map +0 -1
  133. package/dist/chunk-PXCHPZTP.cjs.map +0 -1
  134. package/dist/chunk-QQMU44KJ.js.map +0 -1
  135. package/dist/chunk-RM6ZWNT3.js.map +0 -1
  136. package/dist/chunk-Y6W5V2CI.js +0 -19
  137. package/dist/chunk-Y6W5V2CI.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { Cell, Row, Table, Collection, RowRemoved, MinimumRowConstraintViolated } from './chunk-5HKVMPBD.js';
2
2
  import { isIndexColumn, clampColumnWidth, sizeColumnsToFitWidths, resolveColumnWidth, resolveColumnPinned, ViewportCalculator } from './chunk-IPPIXOVQ.js';
3
- import { ChangeCellValueHandler, ApplyDynamicProjectionHandler, ChangeCellDisplayValueHandler, SaveDescriptionHandler } from './chunk-RM6ZWNT3.js';
3
+ import { ChangeCellValueHandler, ApplyDynamicProjectionHandler, ChangeCellDisplayValueHandler, SaveDescriptionHandler } from './chunk-7T6AWRPU.js';
4
4
  import { DomainError } from './chunk-KHMG6EI2.js';
5
5
  import { formatPrice } from './chunk-RKVSJU46.js';
6
6
  import { normalizeDigits, isBlankSortValue, findSortedInsertIndex, applySort } from './chunk-AU3RDJYQ.js';
@@ -1333,6 +1333,7 @@ var SparseRowIndex = class {
1333
1333
  this.orderedIds = [];
1334
1334
  /** id → position in `orderedIds` (PR-99). */
1335
1335
  this.posById = /* @__PURE__ */ new Map();
1336
+ this.revisionCount = 0;
1336
1337
  for (const [id, value] of entries) {
1337
1338
  this.byId.set(id, value);
1338
1339
  this.posById.set(id, this.orderedIds.length);
@@ -1342,6 +1343,13 @@ var SparseRowIndex = class {
1342
1343
  get size() {
1343
1344
  return this.orderedIds.length;
1344
1345
  }
1346
+ /**
1347
+ * Bumped by every change of ids, order or values. O(1) change detection for
1348
+ * subscribers (a sort keeps `size` but changes the order).
1349
+ */
1350
+ get revision() {
1351
+ return this.revisionCount;
1352
+ }
1345
1353
  getOrderedIds() {
1346
1354
  return this.orderedIds;
1347
1355
  }
@@ -1363,6 +1371,7 @@ var SparseRowIndex = class {
1363
1371
  }
1364
1372
  set(id, value) {
1365
1373
  const existed = this.byId.has(id);
1374
+ this.revisionCount += 1;
1366
1375
  this.byId.set(id, value);
1367
1376
  if (!existed) {
1368
1377
  this.posById.set(id, this.orderedIds.length);
@@ -1371,6 +1380,7 @@ var SparseRowIndex = class {
1371
1380
  }
1372
1381
  /** Insert `id` at `pos` (0…size). Shifts later positions — O(n-pos). */
1373
1382
  insertAt(pos, id, value) {
1383
+ this.revisionCount += 1;
1374
1384
  if (this.byId.has(id)) {
1375
1385
  this.byId.set(id, value);
1376
1386
  return;
@@ -1384,6 +1394,7 @@ var SparseRowIndex = class {
1384
1394
  }
1385
1395
  remove(id) {
1386
1396
  if (!this.byId.has(id)) return false;
1397
+ this.revisionCount += 1;
1387
1398
  this.byId.delete(id);
1388
1399
  const pos = this.posById.get(id);
1389
1400
  this.posById.delete(id);
@@ -1396,6 +1407,7 @@ var SparseRowIndex = class {
1396
1407
  }
1397
1408
  /** Replace order without rebuilding payloads (e.g. after sort). */
1398
1409
  setOrder(orderedIds) {
1410
+ this.revisionCount += 1;
1399
1411
  this.orderedIds = orderedIds.slice();
1400
1412
  this.posById.clear();
1401
1413
  for (let i = 0; i < this.orderedIds.length; i += 1) {
@@ -1404,6 +1416,7 @@ var SparseRowIndex = class {
1404
1416
  }
1405
1417
  /** Full replace of ids + values in one pass (PR-99 rebuild). */
1406
1418
  replaceAll(orderedIds, valueFor) {
1419
+ this.revisionCount += 1;
1407
1420
  this.byId.clear();
1408
1421
  this.orderedIds = orderedIds.slice();
1409
1422
  this.posById.clear();
@@ -1414,6 +1427,7 @@ var SparseRowIndex = class {
1414
1427
  }
1415
1428
  }
1416
1429
  clear() {
1430
+ this.revisionCount += 1;
1417
1431
  this.byId.clear();
1418
1432
  this.orderedIds = [];
1419
1433
  this.posById.clear();
@@ -3142,21 +3156,35 @@ var TableStore = class {
3142
3156
  var DEFAULT_CELL_EDIT_OPTIONS = {
3143
3157
  singleClickEdit: true,
3144
3158
  stopEditingWhenCellsLoseFocus: true,
3145
- editType: "cell"
3159
+ editType: "cell",
3160
+ commit: "deferred",
3161
+ enterNavigation: "nextCell"
3146
3162
  };
3163
+ var sessionSeq = 0;
3147
3164
  function createCellEditSession(input) {
3148
3165
  const drafts = {};
3149
3166
  for (const [colId, cell] of Object.entries(input.baseline)) {
3150
3167
  drafts[colId] = { value: cell.value, display: cell.display };
3151
3168
  }
3169
+ sessionSeq += 1;
3152
3170
  return {
3153
3171
  rowId: input.rowId,
3154
3172
  columnId: input.columnId,
3155
3173
  editType: input.editType,
3156
3174
  baseline: input.baseline,
3157
- drafts
3175
+ drafts,
3176
+ correlationId: `cell-edit-${Date.now()}-${sessionSeq}`
3158
3177
  };
3159
3178
  }
3179
+ function rebaseDraft(session, columnIds) {
3180
+ if (columnIds.length === 0) return session;
3181
+ const baseline = { ...session.baseline };
3182
+ for (const colId of columnIds) {
3183
+ const draft = session.drafts[colId];
3184
+ if (draft) baseline[colId] = { value: draft.value, display: draft.display };
3185
+ }
3186
+ return { ...session, baseline };
3187
+ }
3160
3188
  function readDraft(session, rowId, columnId) {
3161
3189
  if (!session || session.rowId !== rowId) return null;
3162
3190
  return session.drafts[columnId] ?? null;
@@ -3181,8 +3209,8 @@ function patchDraft(session, columnId, patch) {
3181
3209
  drafts: {
3182
3210
  ...session.drafts,
3183
3211
  [columnId]: {
3184
- value: patch.value !== void 0 ? patch.value : prev.value,
3185
- display: patch.display !== void 0 ? patch.display : prev.display
3212
+ value: "value" in patch ? patch.value : prev.value,
3213
+ display: "display" in patch ? patch.display : prev.display
3186
3214
  }
3187
3215
  }
3188
3216
  };
@@ -4167,6 +4195,7 @@ var ViewStateBuilder = class {
4167
4195
  };
4168
4196
 
4169
4197
  // engine/tableEngine.ts
4198
+ var cellWriteKey = (kind, rowId, columnId) => `${kind}\0${rowId}\0${columnId}`;
4170
4199
  var TableEngine = class {
4171
4200
  constructor(app, repo, actor, audit, columnMeta) {
4172
4201
  this.app = app;
@@ -4174,6 +4203,15 @@ var TableEngine = class {
4174
4203
  this.actor = actor;
4175
4204
  this.columnMeta = columnMeta;
4176
4205
  this.cellEditOptions = { ...DEFAULT_CELL_EDIT_OPTIONS };
4206
+ /**
4207
+ * Cell writes coming from editors / host input, executed one at a time in
4208
+ * arrival order. Consecutive values for the same cell coalesce (latest
4209
+ * wins). Until a write landed, the queued value is what the cell shows.
4210
+ */
4211
+ this.cellWrites = /* @__PURE__ */ new Map();
4212
+ this.cellWriteChain = Promise.resolve();
4213
+ /** > 0 while a queued write executes (re-entrant settle must not wait on itself). */
4214
+ this.cellWriteDepth = 0;
4177
4215
  this.errors = /* @__PURE__ */ new Map();
4178
4216
  this.searchResult = /* @__PURE__ */ new Map();
4179
4217
  this.asyncCellCache = /* @__PURE__ */ new Map();
@@ -4748,54 +4786,54 @@ var TableEngine = class {
4748
4786
  const next = command.sortModel.filter(
4749
4787
  (item) => item && typeof item.colId === "string" && item.colId.length > 0 && (item.sort === "asc" || item.sort === "desc")
4750
4788
  ).map((item) => ({ colId: item.colId, sort: item.sort }));
4789
+ this.viewBuilder.invalidateUiRowIndex();
4751
4790
  store.setState((prev) => ({
4752
4791
  ...prev,
4753
4792
  sortModel: next
4754
4793
  }));
4755
- this.viewBuilder.invalidateUiRowIndex();
4756
4794
  this.notifyViewQueryChanged();
4757
4795
  }
4758
4796
  setQuickFilterText(command) {
4759
4797
  const store = this.getStore(command.tableKey);
4760
4798
  if (!store) return;
4761
4799
  const next = typeof command.quickFilterText === "string" ? command.quickFilterText : "";
4800
+ this.viewBuilder.invalidateUiRowIndex();
4762
4801
  store.setState((prev) => ({
4763
4802
  ...prev,
4764
4803
  quickFilterText: next
4765
4804
  }));
4766
- this.viewBuilder.invalidateUiRowIndex();
4767
4805
  }
4768
4806
  setFilterModel(command) {
4769
4807
  const store = this.getStore(command.tableKey);
4770
4808
  if (!store) return;
4771
4809
  const next = command.filterModel && typeof command.filterModel === "object" ? { ...command.filterModel } : {};
4810
+ this.viewBuilder.invalidateUiRowIndex();
4772
4811
  store.setState((prev) => ({
4773
4812
  ...prev,
4774
4813
  filterModel: next
4775
4814
  }));
4776
- this.viewBuilder.invalidateUiRowIndex();
4777
4815
  this.notifyViewQueryChanged();
4778
4816
  }
4779
4817
  setRowGroupColumns(command) {
4780
4818
  const store = this.getStore(command.tableKey);
4781
4819
  if (!store) return;
4782
4820
  const next = command.columns.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
4821
+ this.viewBuilder.invalidateUiRowIndex();
4783
4822
  store.setState((prev) => ({
4784
4823
  ...prev,
4785
4824
  rowGroupColumns: next,
4786
4825
  rowGroupExpanded: {}
4787
4826
  }));
4788
- this.viewBuilder.invalidateUiRowIndex();
4789
4827
  }
4790
4828
  setGroupDefaultExpanded(command) {
4791
4829
  const store = this.getStore(command.tableKey);
4792
4830
  if (!store) return;
4831
+ this.viewBuilder.invalidateUiRowIndex();
4793
4832
  store.setState((prev) => ({
4794
4833
  ...prev,
4795
4834
  groupDefaultExpanded: Boolean(command.expanded),
4796
4835
  rowGroupExpanded: {}
4797
4836
  }));
4798
- this.viewBuilder.invalidateUiRowIndex();
4799
4837
  }
4800
4838
  toggleRowGroup(command) {
4801
4839
  const store = this.getStore(command.tableKey);
@@ -4806,6 +4844,7 @@ var TableEngine = class {
4806
4844
  state.rowGroupExpanded,
4807
4845
  key
4808
4846
  ) ? state.rowGroupExpanded[key] : state.groupDefaultExpanded;
4847
+ this.viewBuilder.invalidateUiRowIndex();
4809
4848
  store.setState((prev) => ({
4810
4849
  ...prev,
4811
4850
  rowGroupExpanded: {
@@ -4813,7 +4852,6 @@ var TableEngine = class {
4813
4852
  [key]: !current
4814
4853
  }
4815
4854
  }));
4816
- this.viewBuilder.invalidateUiRowIndex();
4817
4855
  }
4818
4856
  /**
4819
4857
  * Server/infinite row model hooks (PR-96).
@@ -5082,6 +5120,7 @@ var TableEngine = class {
5082
5120
  async getFullSnapShot(store, collectionScope = "active") {
5083
5121
  if (!this.tableKey) return null;
5084
5122
  try {
5123
+ await this.flushCellEdit();
5085
5124
  const rows = await this.loadAllRows(store, collectionScope);
5086
5125
  const { rowsToSnapshot } = await import('./memoryRecords-OJTGXLRH.js');
5087
5126
  return rowsToSnapshot(rows);
@@ -5098,6 +5137,7 @@ var TableEngine = class {
5098
5137
  async iterateRowsFromDb(store, collectionScope, onBatch, batchSize) {
5099
5138
  if (!this.tableKey) return;
5100
5139
  try {
5140
+ await this.flushCellEdit();
5101
5141
  if (isWindowedRepository(this.repo)) {
5102
5142
  await this.repo.iterateRowRecords(
5103
5143
  store,
@@ -5154,8 +5194,196 @@ var TableEngine = class {
5154
5194
  getCellEdit() {
5155
5195
  return this.tableStore?.getState().cellEdit ?? null;
5156
5196
  }
5197
+ /**
5198
+ * What the cell editor shows: the session draft, else a value that was
5199
+ * typed but whose write has not reached the table yet (no flicker back to
5200
+ * the old value while it is in flight). `null` = show the table value.
5201
+ */
5157
5202
  getEditDraft(rowId, columnId) {
5158
- return readDraft(this.getCellEdit(), rowId, columnId);
5203
+ const draft = readDraft(this.getCellEdit(), rowId, columnId);
5204
+ if (draft) return draft;
5205
+ const value = this.cellWrites.get(cellWriteKey("value", rowId, columnId));
5206
+ const display = this.cellWrites.get(cellWriteKey("display", rowId, columnId));
5207
+ if (!value && !display) return null;
5208
+ const cell = this.viewBuilder.getCell(rowId, columnId);
5209
+ return {
5210
+ value: value ? value.write.value : cell?.getValue(),
5211
+ display: display ? display.write.value : cell?.getDisplay()
5212
+ };
5213
+ }
5214
+ /** Editor / host value for a cell (`UI_CELL_CHANGE`). Resolves once it is durable. */
5215
+ inputCellValue(rowId, columnId, value) {
5216
+ return this.inputCell(rowId, columnId, "value", value);
5217
+ }
5218
+ /** Editor / host display label for a cell (`UI_CELL_CHANGE_DISPLAY`). */
5219
+ inputCellDisplay(rowId, columnId, display) {
5220
+ return this.inputCell(rowId, columnId, "display", display);
5221
+ }
5222
+ async inputCell(rowId, columnId, kind, input) {
5223
+ const store = this.tableStore;
5224
+ if (!store) return;
5225
+ const session = store.getState().cellEdit;
5226
+ if (session && isEditingCell(session, rowId, columnId)) {
5227
+ const immediate = this.cellEditOptions.commit === "immediate";
5228
+ const patch = kind === "value" ? { value: input } : { display: input };
5229
+ store.setState((prev) => {
5230
+ if (!prev.cellEdit || prev.cellEdit.rowId !== rowId) return prev;
5231
+ const patched = patchDraft(prev.cellEdit, columnId, patch);
5232
+ return {
5233
+ ...prev,
5234
+ cellEdit: immediate ? rebaseDraft(patched, [columnId]) : patched
5235
+ };
5236
+ });
5237
+ if (!immediate) return;
5238
+ this.enqueueCellWrite({
5239
+ rowId,
5240
+ columnId,
5241
+ kind,
5242
+ value: input,
5243
+ correlationId: session.correlationId
5244
+ });
5245
+ } else {
5246
+ this.enqueueCellWrite({ rowId, columnId, kind, value: input });
5247
+ }
5248
+ await this.settleCellWrites();
5249
+ }
5250
+ enqueueCellWrite(write) {
5251
+ const key = cellWriteKey(write.kind, write.rowId, write.columnId);
5252
+ const pending = this.cellWrites.get(key);
5253
+ if (pending?.queued) {
5254
+ pending.write = write;
5255
+ return;
5256
+ }
5257
+ const slot = { write, queued: true };
5258
+ this.cellWrites.set(key, slot);
5259
+ this.cellWriteChain = this.cellWriteChain.then(() => this.runCellWrite(key, slot));
5260
+ }
5261
+ async runCellWrite(key, slot) {
5262
+ slot.queued = false;
5263
+ const { write } = slot;
5264
+ const commandStore = this.commandStore;
5265
+ const tableKey = this.tableKey;
5266
+ this.cellWriteDepth += 1;
5267
+ try {
5268
+ if (commandStore && tableKey && this.tableStore) {
5269
+ await this.dispatch(
5270
+ write.kind === "value" ? {
5271
+ type: "CHANGE_CELL_VALUE",
5272
+ store: commandStore,
5273
+ tableKey,
5274
+ rowId: write.rowId,
5275
+ columnId: write.columnId,
5276
+ newValue: write.value,
5277
+ correlationId: write.correlationId
5278
+ } : {
5279
+ type: "CHANGE_CELL_DISPLAY_VALUE",
5280
+ store: commandStore,
5281
+ tableKey,
5282
+ rowId: write.rowId,
5283
+ columnId: write.columnId,
5284
+ newValue: write.value == null ? "" : String(write.value),
5285
+ correlationId: write.correlationId
5286
+ }
5287
+ );
5288
+ }
5289
+ } catch (e) {
5290
+ this.errHandler.handle(e, "TableEngine.cellWrite");
5291
+ } finally {
5292
+ this.cellWriteDepth -= 1;
5293
+ if (this.cellWrites.get(key) === slot && !slot.queued) {
5294
+ this.cellWrites.delete(key);
5295
+ const cell = this.viewBuilder.getCell(write.rowId, write.columnId);
5296
+ const landed = write.kind === "value" ? cell?.getValue() : cell?.getDisplay();
5297
+ if (landed !== write.value) this.tableStore?.setState((prev) => ({ ...prev }));
5298
+ }
5299
+ }
5300
+ }
5301
+ /**
5302
+ * Resolves when every queued cell write reached the table. Called from inside
5303
+ * a write (re-entrant) it returns at once instead of waiting on itself.
5304
+ */
5305
+ async settleCellWrites() {
5306
+ if (this.cellWriteDepth > 0) return;
5307
+ let chain;
5308
+ do {
5309
+ chain = this.cellWriteChain;
5310
+ await chain;
5311
+ } while (chain !== this.cellWriteChain);
5312
+ }
5313
+ /** Queues CHANGE_CELL_* for `columnIds` of `session`; returns the value writes. */
5314
+ queueDraftWrites(session, columnIds) {
5315
+ let writes = 0;
5316
+ for (const colId of columnIds) {
5317
+ const draft = session.drafts[colId];
5318
+ if (!draft) continue;
5319
+ const base = session.baseline[colId];
5320
+ if (!base || draft.value !== base.value) {
5321
+ this.enqueueCellWrite({
5322
+ rowId: session.rowId,
5323
+ columnId: colId,
5324
+ kind: "value",
5325
+ value: draft.value,
5326
+ correlationId: session.correlationId
5327
+ });
5328
+ writes += 1;
5329
+ }
5330
+ if (!base || draft.display !== base.display) {
5331
+ this.enqueueCellWrite({
5332
+ rowId: session.rowId,
5333
+ columnId: colId,
5334
+ kind: "display",
5335
+ value: draft.display,
5336
+ correlationId: session.correlationId
5337
+ });
5338
+ }
5339
+ }
5340
+ return writes;
5341
+ }
5342
+ /**
5343
+ * Make what the user typed durable WITHOUT ending the edit: dirty drafts are
5344
+ * written and become the session baseline, then all queued writes settle.
5345
+ * Every durable read (snapshot, row stream, export) calls this first, so a
5346
+ * save never misses the value still in the editor.
5347
+ */
5348
+ async flushCellEdit() {
5349
+ const store = this.tableStore;
5350
+ const session = store?.getState().cellEdit;
5351
+ if (store && session) {
5352
+ const dirty = dirtyDraftColumns(session);
5353
+ if (dirty.length > 0) {
5354
+ store.setState(
5355
+ (prev) => prev.cellEdit && prev.cellEdit.rowId === session.rowId ? { ...prev, cellEdit: rebaseDraft(prev.cellEdit, dirty) } : prev
5356
+ );
5357
+ this.queueDraftWrites(session, dirty);
5358
+ }
5359
+ }
5360
+ await this.settleCellWrites();
5361
+ }
5362
+ /**
5363
+ * Focus / pointer moved outside the grid. Deferred commit ends the edit per
5364
+ * `stopEditingWhenCellsLoseFocus`; immediate commit has nothing pending and
5365
+ * keeps the editor (legacy table behaviour).
5366
+ */
5367
+ async leaveGridFocus() {
5368
+ if (!this.getCellEdit()) return;
5369
+ if (this.cellEditOptions.commit !== "deferred") return;
5370
+ if (!this.cellEditOptions.stopEditingWhenCellsLoseFocus) return;
5371
+ await this.commitCellEdit();
5372
+ }
5373
+ /**
5374
+ * Before dispose / unmount: commit (or drop, when
5375
+ * `stopEditingWhenCellsLoseFocus` is false) the open edit and wait until
5376
+ * every queued write is durable.
5377
+ */
5378
+ async settleEditsForTeardown() {
5379
+ if (this.getCellEdit()) {
5380
+ if (this.cellEditOptions.stopEditingWhenCellsLoseFocus) {
5381
+ await this.commitCellEdit();
5382
+ } else {
5383
+ this.cancelCellEdit();
5384
+ }
5385
+ }
5386
+ await this.settleCellWrites();
5159
5387
  }
5160
5388
  isEditingCell(rowId, columnId) {
5161
5389
  return isEditingCell(this.getCellEdit(), rowId, columnId);
@@ -5246,39 +5474,12 @@ var TableEngine = class {
5246
5474
  */
5247
5475
  async commitCellEdit() {
5248
5476
  const store = this.tableStore;
5249
- const commandStore = this.commandStore;
5250
- const tableKey = this.tableKey;
5251
- if (!store || !commandStore || !tableKey) return 0;
5477
+ if (!store || !this.commandStore || !this.tableKey) return 0;
5252
5478
  const session = store.getState().cellEdit;
5253
5479
  if (!session) return 0;
5254
- const dirty = dirtyDraftColumns(session);
5255
5480
  store.setState((prev) => ({ ...prev, cellEdit: null }));
5256
- let writes = 0;
5257
- for (const colId of dirty) {
5258
- const draft = session.drafts[colId];
5259
- const base = session.baseline[colId];
5260
- if (!base || draft.value !== base.value) {
5261
- await this.dispatch({
5262
- type: "CHANGE_CELL_VALUE",
5263
- store: commandStore,
5264
- tableKey,
5265
- rowId: session.rowId,
5266
- columnId: colId,
5267
- newValue: draft.value
5268
- });
5269
- writes += 1;
5270
- }
5271
- if (!base || draft.display !== base.display) {
5272
- await this.dispatch({
5273
- type: "CHANGE_CELL_DISPLAY_VALUE",
5274
- store: commandStore,
5275
- tableKey,
5276
- rowId: session.rowId,
5277
- columnId: colId,
5278
- newValue: draft.display == null ? "" : String(draft.display)
5279
- });
5280
- }
5281
- }
5481
+ const writes = this.queueDraftWrites(session, dirtyDraftColumns(session));
5482
+ await this.settleCellWrites();
5282
5483
  return writes;
5283
5484
  }
5284
5485
  async transitionActiveCell(rowId, columnId) {
@@ -5467,16 +5668,15 @@ var TableEngine = class {
5467
5668
  if (syncError) return syncError;
5468
5669
  return this.applyAsyncValidators(value, schema, caption, rowId);
5469
5670
  }
5470
- finishValidation(cb, tableStore) {
5671
+ finishValidation(cb, tableStore, errorsChanged) {
5471
5672
  cb({
5472
5673
  valid: this.errors.size === 0,
5473
5674
  errors: this.errors,
5474
5675
  data: this.getAllRows()
5475
5676
  });
5476
- tableStore.setState((prev) => ({
5477
- ...prev,
5478
- expandedRows: {}
5479
- }));
5677
+ tableStore.setState(
5678
+ (prev) => !errorsChanged && Object.keys(prev.expandedRows ?? {}).length === 0 ? prev : { ...prev, expandedRows: {} }
5679
+ );
5480
5680
  }
5481
5681
  collectValidationJobs(table) {
5482
5682
  const jobs = [];
@@ -5495,11 +5695,23 @@ var TableEngine = class {
5495
5695
  });
5496
5696
  return jobs;
5497
5697
  }
5698
+ /** Replaces the error map; true when the set of errors / messages changed. */
5498
5699
  applyValidationErrors(nextErrors) {
5700
+ let changed = nextErrors.size !== this.errors.size;
5701
+ if (!changed) {
5702
+ for (const [cellId, error] of nextErrors) {
5703
+ const prev = this.errors.get(cellId);
5704
+ if (!prev || prev.message !== error.message || prev.rowId !== error.rowId) {
5705
+ changed = true;
5706
+ break;
5707
+ }
5708
+ }
5709
+ }
5499
5710
  this.errors.clear();
5500
5711
  nextErrors.forEach((error, cellId) => {
5501
5712
  this.errors.set(cellId, error);
5502
5713
  });
5714
+ return changed;
5503
5715
  }
5504
5716
  runValidationSync(table, cb, tableStore) {
5505
5717
  const nextErrors = /* @__PURE__ */ new Map();
@@ -5512,8 +5724,8 @@ var TableEngine = class {
5512
5724
  );
5513
5725
  if (error) nextErrors.set(job.cellId, error);
5514
5726
  }
5515
- this.applyValidationErrors(nextErrors);
5516
- this.finishValidation(cb, tableStore);
5727
+ const changed = this.applyValidationErrors(nextErrors);
5728
+ this.finishValidation(cb, tableStore, changed);
5517
5729
  }
5518
5730
  async runValidationAsync(table, cb, tableStore) {
5519
5731
  const nextErrors = /* @__PURE__ */ new Map();
@@ -5526,8 +5738,8 @@ var TableEngine = class {
5526
5738
  );
5527
5739
  if (error) nextErrors.set(job.cellId, error);
5528
5740
  }
5529
- this.applyValidationErrors(nextErrors);
5530
- this.finishValidation(cb, tableStore);
5741
+ const changed = this.applyValidationErrors(nextErrors);
5742
+ this.finishValidation(cb, tableStore, changed);
5531
5743
  }
5532
5744
  scheduleAsyncCellCheck(cellId, valueKey, value, schema, caption, rowId) {
5533
5745
  const seq = (this.asyncCellSeq.get(cellId) ?? 0) + 1;
@@ -5872,6 +6084,9 @@ async function openIndexedDbDatabase(dbName, storeNames, version = 1) {
5872
6084
  request.onsuccess = () => resolve(request.result);
5873
6085
  });
5874
6086
  return {
6087
+ close() {
6088
+ db.close();
6089
+ },
5875
6090
  objectStore(name) {
5876
6091
  return {
5877
6092
  async get(key) {
@@ -5923,6 +6138,8 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5923
6138
  this.db = db;
5924
6139
  this.listeners = /* @__PURE__ */ new Map();
5925
6140
  this.globalListeners = /* @__PURE__ */ new Set();
6141
+ this.closed = false;
6142
+ this.inflight = /* @__PURE__ */ new Set();
5926
6143
  this.ready = Promise.resolve();
5927
6144
  }
5928
6145
  static async open(dbName = DEFAULT_EVENT_STORE_PREFIX) {
@@ -5933,7 +6150,36 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5933
6150
  static inMemory() {
5934
6151
  return new _IndexedDbEventStore(new MemoryKvDatabase());
5935
6152
  }
5936
- async append(streamId, events, expectedVersion) {
6153
+ /**
6154
+ * Release the IndexedDB connection (stored events are untouched). Waits for
6155
+ * appends already in flight; appends started afterwards are dropped (they
6156
+ * resolve `ok` without writing) because the owning grid is gone. Idempotent.
6157
+ */
6158
+ close() {
6159
+ if (!this.closePromise) {
6160
+ this.closed = true;
6161
+ this.closePromise = (async () => {
6162
+ await Promise.allSettled([...this.inflight]);
6163
+ this.listeners.clear();
6164
+ this.globalListeners.clear();
6165
+ this.db.close?.();
6166
+ })();
6167
+ }
6168
+ return this.closePromise;
6169
+ }
6170
+ append(streamId, events, expectedVersion) {
6171
+ if (this.closed) {
6172
+ return Promise.resolve({ ok: true, version: 0 });
6173
+ }
6174
+ const job = this.appendInternal(streamId, events, expectedVersion);
6175
+ this.inflight.add(job);
6176
+ const forget = () => {
6177
+ this.inflight.delete(job);
6178
+ };
6179
+ job.then(forget, forget);
6180
+ return job;
6181
+ }
6182
+ async appendInternal(streamId, events, expectedVersion) {
5937
6183
  await this.ready;
5938
6184
  const streamStore = this.db.objectStore(STREAMS);
5939
6185
  const globalStore = this.db.objectStore(GLOBAL);
@@ -5973,11 +6219,13 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5973
6219
  return { ok: true, version: record.events.length };
5974
6220
  }
5975
6221
  async read(streamId, fromVersion = 0) {
6222
+ if (this.closed) return [];
5976
6223
  await this.ready;
5977
6224
  const record = await this.db.objectStore(STREAMS).get(streamId);
5978
6225
  return (record?.events ?? []).slice(fromVersion);
5979
6226
  }
5980
6227
  async readAll(fromPosition = 0) {
6228
+ if (this.closed) return [];
5981
6229
  await this.ready;
5982
6230
  const globalStore = this.db.objectStore(GLOBAL);
5983
6231
  const keys = (await globalStore.getAllKeys()).map(Number).filter((n) => Number.isFinite(n)).sort((a, b) => a - b).filter((n) => n > fromPosition);
@@ -5989,6 +6237,7 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5989
6237
  return events;
5990
6238
  }
5991
6239
  subscribe(streamId, onEvent) {
6240
+ if (this.closed) return () => void 0;
5992
6241
  if (!this.listeners.has(streamId)) {
5993
6242
  this.listeners.set(streamId, /* @__PURE__ */ new Set());
5994
6243
  }
@@ -5999,6 +6248,7 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5999
6248
  };
6000
6249
  }
6001
6250
  subscribeAll(onEvent) {
6251
+ if (this.closed) return () => void 0;
6002
6252
  this.globalListeners.add(onEvent);
6003
6253
  return () => {
6004
6254
  this.globalListeners.delete(onEvent);
@@ -6047,6 +6297,8 @@ var OfflineStatus = class {
6047
6297
  this.syncedIds = /* @__PURE__ */ new Set();
6048
6298
  this.connectivityUnsub = null;
6049
6299
  this.storeUnsub = null;
6300
+ /** Incremented by every stop(); invalidates an in-flight start(). */
6301
+ this.lifecycle = 0;
6050
6302
  this.connectivityListeners = /* @__PURE__ */ new Set();
6051
6303
  this.pendingListeners = /* @__PURE__ */ new Set();
6052
6304
  this.online = options.connectivity.getOnline();
@@ -6054,11 +6306,14 @@ var OfflineStatus = class {
6054
6306
  }
6055
6307
  async start(eventStore) {
6056
6308
  this.stop();
6309
+ const run2 = this.lifecycle;
6057
6310
  this.connectivityUnsub = this.options.connectivity.subscribe((online) => {
6311
+ if (run2 !== this.lifecycle) return;
6058
6312
  this.online = online;
6059
6313
  this.emitConnectivity();
6060
6314
  });
6061
6315
  const past = await eventStore.readAll(0);
6316
+ if (run2 !== this.lifecycle) return;
6062
6317
  for (const event of past) {
6063
6318
  this.consider(event);
6064
6319
  }
@@ -6067,7 +6322,14 @@ var OfflineStatus = class {
6067
6322
  });
6068
6323
  this.emitPending();
6069
6324
  }
6325
+ /** Stop listening and drop all listeners. The instance is inert afterwards. */
6326
+ dispose() {
6327
+ this.stop();
6328
+ this.connectivityListeners.clear();
6329
+ this.pendingListeners.clear();
6330
+ }
6070
6331
  stop() {
6332
+ this.lifecycle += 1;
6071
6333
  this.connectivityUnsub?.();
6072
6334
  this.connectivityUnsub = null;
6073
6335
  this.storeUnsub?.();
@@ -6574,18 +6836,40 @@ var UITableController = class {
6574
6836
  return this.syncPort;
6575
6837
  }
6576
6838
  setSyncPort(port) {
6839
+ if (this.releasePromise) {
6840
+ port?.dispose?.();
6841
+ return;
6842
+ }
6577
6843
  this.syncPort = port;
6578
6844
  }
6579
6845
  getPresencePort() {
6580
6846
  return this.presencePort;
6581
6847
  }
6582
6848
  setPresencePort(port) {
6849
+ if (this.releasePromise) {
6850
+ port?.dispose();
6851
+ return;
6852
+ }
6583
6853
  this.presencePort = port;
6584
6854
  }
6855
+ /**
6856
+ * Composition root hook: the callback that releases every resource the grid
6857
+ * created (subscriptions, projections, plugins, owned event store).
6858
+ * Run once by {@link releaseResources}.
6859
+ */
6860
+ setResourceTeardown(teardown) {
6861
+ this.resourceTeardown = teardown;
6862
+ }
6863
+ /** True once resources were released (via releaseResources or dispose). */
6864
+ isReleased() {
6865
+ return this.releasePromise !== void 0;
6866
+ }
6585
6867
  getPresencePeers() {
6586
6868
  return this.presencePort?.getPeers() ?? [];
6587
6869
  }
6588
6870
  subscribePresencePeers(cb) {
6871
+ if (this.isReleased()) return () => {
6872
+ };
6589
6873
  if (!this.presencePort) {
6590
6874
  cb([]);
6591
6875
  return () => {
@@ -6610,6 +6894,8 @@ var UITableController = class {
6610
6894
  this.offlineStatus?.markSynced(eventIds);
6611
6895
  }
6612
6896
  subscribeConnectivity(cb) {
6897
+ if (this.isReleased()) return () => {
6898
+ };
6613
6899
  if (!this.offlineStatus) {
6614
6900
  cb("online");
6615
6901
  return () => {
@@ -6618,6 +6904,8 @@ var UITableController = class {
6618
6904
  return this.offlineStatus.subscribeConnectivity(cb);
6619
6905
  }
6620
6906
  subscribePendingChanges(cb) {
6907
+ if (this.isReleased()) return () => {
6908
+ };
6621
6909
  if (!this.offlineStatus) {
6622
6910
  cb([]);
6623
6911
  return () => {
@@ -6635,6 +6923,8 @@ var UITableController = class {
6635
6923
  return this.syncPort?.getOpenConflicts() ?? [];
6636
6924
  }
6637
6925
  subscribeSyncConflicts(cb) {
6926
+ if (this.isReleased()) return () => {
6927
+ };
6638
6928
  if (!this.syncPort) {
6639
6929
  cb([]);
6640
6930
  return () => {
@@ -6643,10 +6933,11 @@ var UITableController = class {
6643
6933
  return this.syncPort.subscribeConflicts(cb);
6644
6934
  }
6645
6935
  async syncNow() {
6646
- if (!this.syncPort) return null;
6936
+ if (this.isReleased() || !this.syncPort) return null;
6647
6937
  return this.syncPort.sync();
6648
6938
  }
6649
6939
  async resolveSyncConflict(conflictId, choice) {
6940
+ if (this.isReleased()) return;
6650
6941
  await this.syncPort?.resolveConflict(conflictId, choice);
6651
6942
  }
6652
6943
  /** Apply a remote cell value (used by SyncPort; dual-writes via engine). */
@@ -6695,24 +6986,17 @@ var UITableController = class {
6695
6986
  changePage(page) {
6696
6987
  this.engine.dispatch({ type: "CHANGE_PAGE", store: this.store, tableKey: this.tableKey, page });
6697
6988
  }
6989
+ /** Editor of this cell → session draft (written per `cellEditCommit`); otherwise write-through. */
6698
6990
  onCellChange(rowId, columnId, value) {
6699
- if (this.engine.isEditingCell(rowId, columnId)) {
6700
- this.engine.patchCellEdit(rowId, columnId, { value });
6701
- return;
6702
- }
6703
- this.engine.dispatch({ type: "CHANGE_CELL_VALUE", store: this.store, tableKey: this.tableKey, rowId, columnId, newValue: value });
6991
+ return this.engine.inputCellValue(rowId, columnId, value);
6704
6992
  }
6705
6993
  onDescriptionSave(rowId, flag) {
6706
6994
  this.engine.dispatch({ type: "SAVE_DESCRIPTION", store: this.store, tableKey: this.tableKey, rowId, flag });
6707
6995
  }
6708
6996
  onCellDisplayChange(rowId, columnId, value) {
6709
- if (this.engine.isEditingCell(rowId, columnId)) {
6710
- this.engine.patchCellEdit(rowId, columnId, { display: value });
6711
- return;
6712
- }
6713
- this.engine.dispatch({ type: "CHANGE_CELL_DISPLAY_VALUE", store: this.store, tableKey: this.tableKey, rowId, columnId, newValue: value });
6997
+ return this.engine.inputCellDisplay(rowId, columnId, value);
6714
6998
  }
6715
- setActiveCell(rowId, columnId) {
6999
+ async setActiveCell(rowId, columnId) {
6716
7000
  const prev = this.engine.getActiveCell();
6717
7001
  if (prev?.rowId === rowId && prev?.columnId === columnId) {
6718
7002
  if (this.engine.getCellEditOptions().singleClickEdit && !this.engine.getCellEdit()) {
@@ -6729,14 +7013,14 @@ var UITableController = class {
6729
7013
  rowId
6730
7014
  });
6731
7015
  }
6732
- this.engine.dispatch({ type: "ACTIVE_CELL", tableKey: this.tableKey, rowId, columnId });
6733
7016
  this.publishPresenceCell(rowId, columnId);
7017
+ await this.engine.dispatch({ type: "ACTIVE_CELL", tableKey: this.tableKey, rowId, columnId });
6734
7018
  }
6735
7019
  setActiveRow(rowId) {
6736
7020
  this.engine.dispatch({ type: "ACTIVE_ROW", store: this.store, tableKey: this.tableKey, rowId });
6737
7021
  }
6738
7022
  moveActiveCellVertical(rowId, columnId, direction) {
6739
- this.engine.dispatch({ type: "MOVE_ACTIVE_CELL_VERTICAL", store: this.store, tableKey: this.tableKey, rowId, columnId, direction });
7023
+ return this.engine.dispatch({ type: "MOVE_ACTIVE_CELL_VERTICAL", store: this.store, tableKey: this.tableKey, rowId, columnId, direction });
6740
7024
  }
6741
7025
  reorderRow(draggedRowId, droppedRowId, position) {
6742
7026
  this.engine.dispatch({ type: "ROW_REORDERED", store: this.store, tableKey: this.tableKey, draggedRowId, droppedRowId, position });
@@ -6812,7 +7096,7 @@ var UITableController = class {
6812
7096
  this.engine.dispatch({ type: "ACTIVATE_FIRST_CELL", tableKey: this.tableKey, rowId });
6813
7097
  }
6814
7098
  moveToNextCell(rowId, columnId, direction = "next") {
6815
- this.engine.dispatch({ type: "MOVE_ACTIVE_CELL", store: this.store, tableKey: this.tableKey, rowId, columnId, direction });
7099
+ return this.engine.dispatch({ type: "MOVE_ACTIVE_CELL", store: this.store, tableKey: this.tableKey, rowId, columnId, direction });
6816
7100
  }
6817
7101
  async setTableAction(action) {
6818
7102
  await this.engine.dispatch({ type: "SET_TABLE_ACTION", store: this.store, tableKey: this.tableKey, action });
@@ -7080,7 +7364,7 @@ var UITableController = class {
7080
7364
  ) ?? 0;
7081
7365
  }
7082
7366
  subscribeChangelog(cb, filter = {}) {
7083
- if (!this.changelogProjection) return () => {
7367
+ if (!this.changelogProjection || this.isReleased()) return () => {
7084
7368
  };
7085
7369
  return this.safe(
7086
7370
  () => this.changelogProjection.subscribe(cb, { tableId: this.tableKey, ...filter }),
@@ -7103,7 +7387,7 @@ var UITableController = class {
7103
7387
  ) ?? 0;
7104
7388
  }
7105
7389
  subscribeHistoryItems(cb, filter = {}) {
7106
- if (!this.historyPanelViewModel) return () => {
7390
+ if (!this.historyPanelViewModel || this.isReleased()) return () => {
7107
7391
  };
7108
7392
  return this.safe(
7109
7393
  () => this.historyPanelViewModel.subscribe(cb, { tableId: this.tableKey, ...filter }),
@@ -7176,6 +7460,40 @@ var UITableController = class {
7176
7460
  commitCellEdit() {
7177
7461
  return this.safe(() => this.engine.commitCellEdit(), "commitCellEdit") ?? Promise.resolve(0);
7178
7462
  }
7463
+ /**
7464
+ * Write what is still in the cell editor (the edit stays open) and wait
7465
+ * until every pending cell write is durable. `getFullSnapShot` /
7466
+ * `iterateRowsFromDb` / exports already do this; call it before reading
7467
+ * synchronous APIs (`getRowView`, `getSnapShot`) for a save.
7468
+ */
7469
+ flushCellEdit() {
7470
+ return this.engine.flushCellEdit().catch((error) => {
7471
+ this.audit.log({
7472
+ type: "ERROR",
7473
+ message: "UITableController.flushCellEdit failed",
7474
+ payload: { error, tableKey: this.tableKey }
7475
+ });
7476
+ });
7477
+ }
7478
+ /** DataGrid: focus / pointer left the grid (see `stopEditingWhenCellsLoseFocus`). */
7479
+ leaveGridFocus() {
7480
+ return this.engine.leaveGridFocus().catch((error) => {
7481
+ this.audit.log({
7482
+ type: "ERROR",
7483
+ message: "UITableController.leaveGridFocus failed",
7484
+ payload: { error, tableKey: this.tableKey }
7485
+ });
7486
+ });
7487
+ }
7488
+ settleEditsForTeardown() {
7489
+ return this.engine.settleEditsForTeardown().catch((error) => {
7490
+ this.audit.log({
7491
+ type: "ERROR",
7492
+ message: "UITableController: committing the open edit before teardown failed",
7493
+ payload: { error, tableKey: this.tableKey }
7494
+ });
7495
+ });
7496
+ }
7179
7497
  getEditDraft(rowId, columnId) {
7180
7498
  return this.safe(
7181
7499
  () => this.engine.getEditDraft(rowId, columnId),
@@ -7296,12 +7614,68 @@ var UITableController = class {
7296
7614
  this.engine.addCellRange(range);
7297
7615
  }
7298
7616
  // ─── lifecycle ───────────────────────────────────────────────────────
7617
+ /**
7618
+ * Full dispose: `TABLE_DISPOSED` (windowed repo: drop the RAM page window,
7619
+ * other repos: `repo.delete` — unchanged data semantics) followed by
7620
+ * {@link releaseResources}. Idempotent; the promise settles once teardown
7621
+ * finished and never rejects.
7622
+ */
7299
7623
  dispose() {
7300
- if (this.disposed) return;
7624
+ if (this.disposed) return this.releasePromise ?? this.releaseResources();
7301
7625
  this.disposed = true;
7302
7626
  this.presencePort?.dispose();
7303
7627
  this.presencePort = void 0;
7304
- void this.engine.dispatch({ type: "TABLE_DISPOSED", store: this.store, tableKey: this.tableKey });
7628
+ const dispatched = this.settleEditsForTeardown().then(
7629
+ () => this.engine.dispatch({ type: "TABLE_DISPOSED", store: this.store, tableKey: this.tableKey })
7630
+ ).catch((error) => {
7631
+ this.audit.log({
7632
+ type: "ERROR",
7633
+ message: "UITableController.dispose: TABLE_DISPOSED failed",
7634
+ payload: { error, tableKey: this.tableKey }
7635
+ });
7636
+ });
7637
+ return dispatched.then(() => this.releaseResources());
7638
+ }
7639
+ /**
7640
+ * Resource-only teardown: stops subscriptions, projections, sync, presence,
7641
+ * plugins, server row model and closes an event store the grid opened.
7642
+ * Does NOT dispatch `TABLE_DISPOSED` — persisted table data is untouched.
7643
+ * Idempotent (same promise on every call); never rejects.
7644
+ */
7645
+ releaseResources() {
7646
+ if (!this.releasePromise) {
7647
+ this.releasePromise = this.runResourceTeardown();
7648
+ }
7649
+ return this.releasePromise;
7650
+ }
7651
+ async runResourceTeardown() {
7652
+ const fail = (what, error) => {
7653
+ this.audit.log({
7654
+ type: "ERROR",
7655
+ message: `UITableController.releaseResources: ${what} failed`,
7656
+ payload: { error, tableKey: this.tableKey }
7657
+ });
7658
+ };
7659
+ await this.settleEditsForTeardown();
7660
+ const presence = this.presencePort;
7661
+ const sync = this.syncPort;
7662
+ this.presencePort = void 0;
7663
+ this.syncPort = void 0;
7664
+ try {
7665
+ presence?.dispose();
7666
+ } catch (error) {
7667
+ fail("presence dispose", error);
7668
+ }
7669
+ try {
7670
+ sync?.dispose?.();
7671
+ } catch (error) {
7672
+ fail("sync dispose", error);
7673
+ }
7674
+ try {
7675
+ await this.resourceTeardown?.();
7676
+ } catch (error) {
7677
+ fail("teardown", error);
7678
+ }
7305
7679
  }
7306
7680
  // ─── main dispatch ───────────────────────────────────────────────────
7307
7681
  async dispatch(command) {
@@ -7325,13 +7699,13 @@ var UITableController = class {
7325
7699
  this.changePage(command.page);
7326
7700
  return;
7327
7701
  case "UI_CELL_CHANGE":
7328
- this.onCellChange(command.rowId, command.columnId, command.value);
7702
+ await this.onCellChange(command.rowId, command.columnId, command.value);
7329
7703
  return;
7330
7704
  case "UI_DESCRIPTION_SAVE":
7331
7705
  this.onDescriptionSave(command.rowId, command.flag);
7332
7706
  return;
7333
7707
  case "UI_CELL_CHANGE_DISPLAY":
7334
- this.onCellDisplayChange(command.rowId, command.columnId, command.value);
7708
+ await this.onCellDisplayChange(command.rowId, command.columnId, command.value);
7335
7709
  return;
7336
7710
  case "ADD_EMPTY_ROW":
7337
7711
  await this.addEmptyRow();
@@ -7371,13 +7745,13 @@ var UITableController = class {
7371
7745
  shiftKey: Boolean(command.shiftKey),
7372
7746
  ctrlKey: Boolean(command.ctrlKey)
7373
7747
  });
7374
- this.setActiveCell(command.rowId, command.columnId);
7748
+ await this.setActiveCell(command.rowId, command.columnId);
7375
7749
  return;
7376
7750
  case "UI_MOVE_ACTIVE_CELL":
7377
- this.moveToNextCell(command.rowId, command.columnId, command.direction);
7751
+ await this.moveToNextCell(command.rowId, command.columnId, command.direction);
7378
7752
  return;
7379
7753
  case "UI_MOVE_ACTIVE_CELL_VERTICAL":
7380
- this.moveActiveCellVertical(command.rowId, command.columnId, command.direction);
7754
+ await this.moveActiveCellVertical(command.rowId, command.columnId, command.direction);
7381
7755
  return;
7382
7756
  case "UI_ACTIVE_ROW":
7383
7757
  this.setActiveRow(command.rowId);
@@ -7592,6 +7966,7 @@ var PluginHost = class {
7592
7966
  return this.plugins.some((p) => p.name === pluginName);
7593
7967
  }
7594
7968
  async requestExport(options) {
7969
+ await this.deps.beforeRead?.();
7595
7970
  const bus = this.createBus();
7596
7971
  const correlationId = options.correlationId ?? createEventId();
7597
7972
  const pluginName = options.format === "pdf" ? "pdf-export" : options.format === "csv" ? "csv-export" : "excel-export";
@@ -8077,6 +8452,40 @@ var ConsoleAuditLogger = class {
8077
8452
  }
8078
8453
  };
8079
8454
 
8455
+ // infrastructure/eventStore/trackedEventStore.ts
8456
+ function trackEventStoreSubscriptions(inner) {
8457
+ const open = /* @__PURE__ */ new Set();
8458
+ let released = false;
8459
+ const track = (subscribe) => {
8460
+ if (released) return () => void 0;
8461
+ const raw = subscribe();
8462
+ let done = false;
8463
+ const unsub = () => {
8464
+ if (done) return;
8465
+ done = true;
8466
+ open.delete(unsub);
8467
+ raw();
8468
+ };
8469
+ open.add(unsub);
8470
+ return unsub;
8471
+ };
8472
+ const store = {
8473
+ append: (streamId, events, expectedVersion) => inner.append(streamId, events, expectedVersion),
8474
+ read: (streamId, fromVersion) => inner.read(streamId, fromVersion),
8475
+ readAll: (fromPosition) => inner.readAll(fromPosition),
8476
+ subscribe: (streamId, onEvent) => track(() => inner.subscribe(streamId, onEvent)),
8477
+ subscribeAll: (onEvent) => track(() => inner.subscribeAll(onEvent))
8478
+ };
8479
+ return {
8480
+ store,
8481
+ release() {
8482
+ released = true;
8483
+ for (const unsub of [...open]) unsub();
8484
+ open.clear();
8485
+ }
8486
+ };
8487
+ }
8488
+
8080
8489
  // infrastructure/history/HistoryPanelViewModel.ts
8081
8490
  var HISTORY_CLASSIFICATIONS = {
8082
8491
  CELL_CHANGED: { category: "data", severity: "info" },
@@ -8288,6 +8697,8 @@ var ChangelogProjection = class {
8288
8697
  this.subscribers = /* @__PURE__ */ new Set();
8289
8698
  this.seenEventIds = /* @__PURE__ */ new Set();
8290
8699
  this.started = false;
8700
+ /** Incremented by start() and stop(); invalidates an in-flight catch-up. */
8701
+ this.run = 0;
8291
8702
  }
8292
8703
  /**
8293
8704
  * Catch up via readAll, then subscribe for live events.
@@ -8301,17 +8712,25 @@ var ChangelogProjection = class {
8301
8712
  this.seenEventIds.add(event.eventId);
8302
8713
  this.project(event);
8303
8714
  };
8715
+ const run2 = ++this.run;
8304
8716
  this.unsubscribe = this.eventStore.subscribeAll(projectOnce);
8305
8717
  const past = await this.eventStore.readAll(0);
8718
+ if (run2 !== this.run) return;
8306
8719
  for (const event of past) {
8307
8720
  projectOnce(event);
8308
8721
  }
8309
8722
  }
8310
8723
  stop() {
8724
+ this.run += 1;
8311
8725
  this.unsubscribe?.();
8312
8726
  this.unsubscribe = null;
8313
8727
  this.started = false;
8314
8728
  }
8729
+ /** Stop and drop all subscribers (grid teardown). Entries stay readable. */
8730
+ dispose() {
8731
+ this.stop();
8732
+ this.subscribers.clear();
8733
+ }
8315
8734
  query(filter = {}) {
8316
8735
  let result = this.entries;
8317
8736
  if (filter.tableId) {
@@ -8893,6 +9312,33 @@ function isCellEditorTarget(target) {
8893
9312
  if (!editor) return false;
8894
9313
  return Boolean(editor.closest('[role="gridcell"], [data-erp-cell-textarea]'));
8895
9314
  }
9315
+ var CELL_SELECTOR = '[role="gridcell"], [data-erp-cell-textarea]';
9316
+ function isGridCellTarget(target) {
9317
+ if (!target || !(target instanceof Element)) return false;
9318
+ if (target.closest('[role="menu"], [role="dialog"], .erp-table-modal')) {
9319
+ return false;
9320
+ }
9321
+ return Boolean(target.closest(CELL_SELECTOR));
9322
+ }
9323
+ var TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
9324
+ "",
9325
+ "text",
9326
+ "search",
9327
+ "number",
9328
+ "tel",
9329
+ "url",
9330
+ "email",
9331
+ "password"
9332
+ ]);
9333
+ function isTextEditorTarget(target) {
9334
+ if (!isCellEditorTarget(target)) return false;
9335
+ const el = target;
9336
+ if (el.isContentEditable || el.getAttribute("contenteditable") === "true") return true;
9337
+ const tag = el.tagName;
9338
+ if (tag === "TEXTAREA") return true;
9339
+ if (tag !== "INPUT") return false;
9340
+ return TEXT_INPUT_TYPES.has((el.getAttribute("type") ?? "").toLowerCase());
9341
+ }
8896
9342
  function closestGridRoot(target) {
8897
9343
  if (!target || !(target instanceof Element)) return null;
8898
9344
  return target.closest('[role="grid"], [role="treegrid"]');
@@ -8947,6 +9393,13 @@ var UITableKeyboardHandler = class {
8947
9393
  setPasteCells(handler) {
8948
9394
  this.pasteCells = handler;
8949
9395
  }
9396
+ /** More than one cell selected (Shift+Arrow / drag): clipboard keys act on the grid. */
9397
+ hasMultiCellRange() {
9398
+ const ranges = this.engine.getCellRanges();
9399
+ if (ranges.length > 1) return true;
9400
+ const range = ranges[0];
9401
+ return Boolean(range && (range.startRow !== range.endRow || range.colIds.length > 1));
9402
+ }
8950
9403
  async handle(e, ctx2) {
8951
9404
  try {
8952
9405
  await this.handleInternal(e, ctx2);
@@ -8966,6 +9419,8 @@ var UITableKeyboardHandler = class {
8966
9419
  const { activeColumnId } = ctx2;
8967
9420
  const selectedRows = this.engine.getSelectedRows();
8968
9421
  const activeRowId = ctx2.activeRowId ?? this.engine.getActiveRow() ?? (selectedRows ? Number(Object.keys(selectedRows).find((id) => selectedRows[Number(id)])) || null : null);
9422
+ const inTextEditor = isTextEditorTarget(e.target);
9423
+ const clipboardToEditor = inTextEditor && !this.hasMultiCellRange();
8969
9424
  switch (code) {
8970
9425
  case "KeyN": {
8971
9426
  if (altKey) {
@@ -8997,7 +9452,7 @@ var UITableKeyboardHandler = class {
8997
9452
  });
8998
9453
  return;
8999
9454
  }
9000
- if (!ctrlKey && !shiftKey && !altKey && activeRowId && activeColumnId) {
9455
+ if (!ctrlKey && !shiftKey && !altKey && !inTextEditor && activeRowId && activeColumnId) {
9001
9456
  e.preventDefault();
9002
9457
  await this.dispatchIntent({
9003
9458
  type: "JUMP_ACTIVE_CELL_ROW_EDGE",
@@ -9031,7 +9486,7 @@ var UITableKeyboardHandler = class {
9031
9486
  });
9032
9487
  return;
9033
9488
  }
9034
- if (!ctrlKey && !shiftKey && !altKey && activeRowId && activeColumnId) {
9489
+ if (!ctrlKey && !shiftKey && !altKey && !inTextEditor && activeRowId && activeColumnId) {
9035
9490
  e.preventDefault();
9036
9491
  await this.dispatchIntent({
9037
9492
  type: "JUMP_ACTIVE_CELL_ROW_EDGE",
@@ -9059,28 +9514,28 @@ var UITableKeyboardHandler = class {
9059
9514
  type: "ACTIVATE_FIRST_CELL",
9060
9515
  rowId: activeRowId
9061
9516
  });
9062
- } else if (this.engine.getCellEdit()) {
9517
+ } else if (!this.engine.getCellEdit() && !this.engine.getCellEditOptions().singleClickEdit) {
9518
+ this.engine.startCellEdit(activeRowId, activeColumnId);
9519
+ } else if (this.engine.getCellEditOptions().enterNavigation === "down") {
9063
9520
  await this.dispatchIntent({
9064
9521
  type: "MOVE_ACTIVE_CELL_VERTICAL",
9065
9522
  rowId: activeRowId,
9066
9523
  columnId: activeColumnId,
9067
9524
  direction: shiftKey ? "up" : "down"
9068
9525
  });
9069
- } else if (!this.engine.getCellEditOptions().singleClickEdit) {
9070
- this.engine.startCellEdit(activeRowId, activeColumnId);
9071
9526
  } else {
9072
9527
  await this.dispatchIntent({
9073
- type: "MOVE_ACTIVE_CELL_VERTICAL",
9528
+ type: "MOVE_ACTIVE_CELL",
9074
9529
  rowId: activeRowId,
9075
9530
  columnId: activeColumnId,
9076
- direction: shiftKey ? "up" : "down"
9531
+ direction: shiftKey ? "prev" : "next"
9077
9532
  });
9078
9533
  }
9079
9534
  }
9080
9535
  return;
9081
9536
  }
9082
9537
  case "Tab": {
9083
- if (!isCellEditorTarget(e.target)) {
9538
+ if (!isGridCellTarget(e.target)) {
9084
9539
  return;
9085
9540
  }
9086
9541
  if (activeRowId && activeColumnId) {
@@ -9270,7 +9725,7 @@ var UITableKeyboardHandler = class {
9270
9725
  return;
9271
9726
  }
9272
9727
  case "KeyA": {
9273
- if (ctrlKey) {
9728
+ if (ctrlKey && !inTextEditor) {
9274
9729
  e.preventDefault();
9275
9730
  await this.dispatchIntent({ type: "SELECT_ALL_CELLS" });
9276
9731
  }
@@ -9278,6 +9733,7 @@ var UITableKeyboardHandler = class {
9278
9733
  }
9279
9734
  case "KeyC": {
9280
9735
  if (ctrlKey && !altKey) {
9736
+ if (clipboardToEditor) return;
9281
9737
  e.preventDefault();
9282
9738
  if (this.copyCells) {
9283
9739
  await this.copyCells();
@@ -9292,6 +9748,7 @@ var UITableKeyboardHandler = class {
9292
9748
  }
9293
9749
  case "KeyX": {
9294
9750
  if (ctrlKey && !altKey) {
9751
+ if (clipboardToEditor) return;
9295
9752
  e.preventDefault();
9296
9753
  if (this.cutCells) {
9297
9754
  await this.cutCells();
@@ -9306,6 +9763,7 @@ var UITableKeyboardHandler = class {
9306
9763
  }
9307
9764
  case "KeyV": {
9308
9765
  if (ctrlKey && !altKey) {
9766
+ if (clipboardToEditor) return;
9309
9767
  e.preventDefault();
9310
9768
  if (this.pasteCells) {
9311
9769
  await this.pasteCells();
@@ -9356,6 +9814,7 @@ var UITableKeyboardHandler = class {
9356
9814
  return;
9357
9815
  }
9358
9816
  case "KeyZ": {
9817
+ if (inTextEditor) return;
9359
9818
  if (ctrlKey && !shiftKey) {
9360
9819
  e.preventDefault();
9361
9820
  await this.dispatchIntent({ type: "UNDO" });
@@ -9368,7 +9827,7 @@ var UITableKeyboardHandler = class {
9368
9827
  return;
9369
9828
  }
9370
9829
  case "KeyY": {
9371
- if (ctrlKey) {
9830
+ if (ctrlKey && !inTextEditor) {
9372
9831
  e.preventDefault();
9373
9832
  await this.dispatchIntent({ type: "REDO" });
9374
9833
  }
@@ -11049,7 +11508,7 @@ function createTableAppService(repo, audit, permissionPolicy, { columnMeta, remo
11049
11508
  const changeCellHandler = new ChangeCellValueHandler(repo, audit, permissionPolicy, { columnMeta, remoteApi });
11050
11509
  const applyDynamicProjection = new ApplyDynamicProjectionHandler(repo, audit, permissionPolicy, { columnMeta, remoteApi });
11051
11510
  let rowVisibilityHandler;
11052
- const applyRowVisibility = () => rowVisibilityHandler ?? (rowVisibilityHandler = import('./applyRowVisibilityHandler-KS6TMCBN.js').then(
11511
+ const applyRowVisibility = () => rowVisibilityHandler ?? (rowVisibilityHandler = import('./applyRowVisibilityHandler-ZLZXQEGY.js').then(
11053
11512
  (m) => new m.ApplyRowVisibilityHandler(repo, audit, permissionPolicy, { columnMeta, remoteApi })
11054
11513
  ));
11055
11514
  const changeCellDisplayHandler = new ChangeCellDisplayValueHandler(repo, audit, permissionPolicy);
@@ -11106,14 +11565,39 @@ function createTableAppService(repo, audit, permissionPolicy, { columnMeta, remo
11106
11565
 
11107
11566
  // bootstrap/index.ts
11108
11567
  async function resolveEventStore(config, persist) {
11109
- if (config.eventStore) return config.eventStore;
11110
- if (config.persistence === "paged-idb") return new InMemoryEventStore();
11568
+ if (config.eventStore) return { eventStore: config.eventStore };
11569
+ if (config.persistence === "paged-idb") {
11570
+ return { eventStore: new InMemoryEventStore() };
11571
+ }
11111
11572
  if (persist && typeof indexedDB !== "undefined") {
11112
- return IndexedDbEventStore.open(
11573
+ const opened = await IndexedDbEventStore.open(
11113
11574
  eventStoreDbName(config.store, config.eventStoreName)
11114
11575
  );
11576
+ return { eventStore: opened, close: () => opened.close() };
11115
11577
  }
11116
- return new InMemoryEventStore();
11578
+ return { eventStore: new InMemoryEventStore() };
11579
+ }
11580
+ function createDisposerRegistry() {
11581
+ const disposers = [];
11582
+ let running;
11583
+ return {
11584
+ add(disposer) {
11585
+ disposers.push(disposer);
11586
+ },
11587
+ run() {
11588
+ if (!running) {
11589
+ running = (async () => {
11590
+ for (const disposer of disposers.splice(0).reverse()) {
11591
+ try {
11592
+ await disposer();
11593
+ } catch {
11594
+ }
11595
+ }
11596
+ })();
11597
+ }
11598
+ return running;
11599
+ }
11600
+ };
11117
11601
  }
11118
11602
  function createInitialRows(collectionColumns) {
11119
11603
  const rows = [];
@@ -11145,6 +11629,15 @@ function resolveHostRows(config, columns) {
11145
11629
  );
11146
11630
  }
11147
11631
  async function assembleGrid(config) {
11632
+ const registry = createDisposerRegistry();
11633
+ try {
11634
+ return await assembleGridInner(config, registry);
11635
+ } catch (error) {
11636
+ await registry.run();
11637
+ throw error;
11638
+ }
11639
+ }
11640
+ async function assembleGridInner(config, registry) {
11148
11641
  const { store, tableKey, actor, persist } = resolveCreateGridDefaults(config);
11149
11642
  const resolved = {
11150
11643
  ...config,
@@ -11160,8 +11653,14 @@ async function assembleGrid(config) {
11160
11653
  const windowed = isWindowedRepository(repo);
11161
11654
  const audit = config.audit ?? new ConsoleAuditLogger();
11162
11655
  const remoteApi = config.remoteApi ?? new NoopRemoteApi();
11163
- const eventStore = await resolveEventStore(resolved, persist);
11164
- const changelogProjection = new ChangelogProjection(eventStore);
11656
+ const resolvedStore = await resolveEventStore(resolved, persist);
11657
+ const { eventStore } = resolvedStore;
11658
+ if (resolvedStore.close) registry.add(resolvedStore.close);
11659
+ const tracked = trackEventStoreSubscriptions(eventStore);
11660
+ registry.add(() => tracked.release());
11661
+ const internalStore = tracked.store;
11662
+ const changelogProjection = new ChangelogProjection(internalStore);
11663
+ registry.add(() => changelogProjection.dispose());
11165
11664
  const historyPanelViewModel = new HistoryPanelViewModel(changelogProjection);
11166
11665
  const cellUndoStack = new CellUndoStack();
11167
11666
  await changelogProjection.start();
@@ -11170,11 +11669,12 @@ async function assembleGrid(config) {
11170
11669
  connectivity: config.connectivity ?? createBrowserConnectivity(),
11171
11670
  watermark: createLocalStorageWatermark(`pdg-sync-watermark:${store}:${tableKey}`)
11172
11671
  });
11173
- await offlineStatus.start(eventStore);
11672
+ registry.add(() => offlineStatus.dispose());
11673
+ await offlineStatus.start(internalStore);
11174
11674
  const engineRef = {};
11175
11675
  const snapshotStrategy = config.snapshotStore ? new SnapshotStrategy(
11176
11676
  config.snapshotStore,
11177
- eventStore,
11677
+ internalStore,
11178
11678
  () => engineRef.current?.getSnapShot("active") ?? {
11179
11679
  rows: []
11180
11680
  },
@@ -11289,17 +11789,20 @@ async function assembleGrid(config) {
11289
11789
  remoteApi,
11290
11790
  offlineStatus
11291
11791
  );
11792
+ controller.setResourceTeardown(() => registry.run());
11292
11793
  let syncPort;
11293
11794
  if (config.syncTransport) {
11294
- const { GridSyncService } = await import('./GridSyncService-KTLB4KP3.js');
11795
+ const { GridSyncService } = await import('./GridSyncService-GECXGEKL.js');
11295
11796
  syncPort = new GridSyncService({
11296
11797
  tableId: tableKey,
11297
- eventStore,
11798
+ eventStore: internalStore,
11298
11799
  offlineStatus,
11299
11800
  transport: config.syncTransport,
11300
11801
  policy: config.syncConflictPolicy,
11301
11802
  applyCellValue: (rowId, columnId, value) => controller.applySyncedCellValue(rowId, columnId, value)
11302
11803
  });
11804
+ const syncService = syncPort;
11805
+ registry.add(() => syncService.dispose?.());
11303
11806
  controller.setSyncPort(syncPort);
11304
11807
  }
11305
11808
  let presencePort;
@@ -11314,6 +11817,8 @@ async function assembleGrid(config) {
11314
11817
  label: config.presenceLabel ?? actor.userId,
11315
11818
  transport: presenceTransport
11316
11819
  });
11820
+ const presenceService = presencePort;
11821
+ registry.add(() => presenceService.dispose());
11317
11822
  controller.setPresencePort(presencePort);
11318
11823
  }
11319
11824
  const { definitions: columnDefinitions, bindings: columnBindings } = mapColumnSchemas(hostColumns);
@@ -11331,7 +11836,11 @@ async function assembleGrid(config) {
11331
11836
  engine.setCellEditOptions({
11332
11837
  ...typeof config.singleClickEdit === "boolean" ? { singleClickEdit: config.singleClickEdit } : {},
11333
11838
  ...typeof config.stopEditingWhenCellsLoseFocus === "boolean" ? { stopEditingWhenCellsLoseFocus: config.stopEditingWhenCellsLoseFocus } : {},
11334
- ...config.editType === "cell" || config.editType === "fullRow" ? { editType: config.editType } : {}
11839
+ ...config.editType === "cell" || config.editType === "fullRow" ? { editType: config.editType } : {},
11840
+ // Page-window (document) grids keep the arianERP write-per-change
11841
+ // contract; the event-sourced grid avoids one EventStore write per key.
11842
+ commit: config.cellEditCommit === "immediate" || config.cellEditCommit === "deferred" ? config.cellEditCommit : pagedIdb ? "immediate" : "deferred",
11843
+ ...config.enterNavigation === "nextCell" || config.enterNavigation === "down" ? { enterNavigation: config.enterNavigation } : {}
11335
11844
  });
11336
11845
  controller.configureClipboard({
11337
11846
  copyFormattedValue: config.copyFormattedValue,
@@ -11373,6 +11882,7 @@ async function assembleGrid(config) {
11373
11882
  maxBlocksInCache: config.maxBlocksInCache
11374
11883
  });
11375
11884
  refreshServerSide = () => handle.refreshServerSide();
11885
+ registry.add(() => handle.dispose());
11376
11886
  }
11377
11887
  if (snapshotStrategy) {
11378
11888
  await snapshotStrategy.loadWithCatchUp(`table:${tableKey}`);
@@ -11380,10 +11890,12 @@ async function assembleGrid(config) {
11380
11890
  const pluginHost = new PluginHost({
11381
11891
  tableId: tableKey,
11382
11892
  actorId: actor.userId,
11383
- eventStore,
11893
+ eventStore: internalStore,
11384
11894
  session,
11385
- query: createEngineGridQuery(engine)
11895
+ query: createEngineGridQuery(engine),
11896
+ beforeRead: () => engine.flushCellEdit()
11386
11897
  });
11898
+ registry.add(() => pluginHost.dispose());
11387
11899
  if (config.plugins?.length) {
11388
11900
  pluginHost.register(config.plugins);
11389
11901
  }
@@ -11394,14 +11906,15 @@ async function assembleGrid(config) {
11394
11906
  getRowId: config.getRowId,
11395
11907
  refreshServerSide
11396
11908
  });
11397
- await attachColumnStatePersistence({
11909
+ const detachColumnState = await attachColumnStatePersistence({
11398
11910
  api,
11399
11911
  controller,
11400
11912
  tableId: tableKey,
11401
11913
  actorId: actor.userId,
11402
11914
  stateKey: config.stateKey,
11403
- eventStore
11915
+ eventStore: internalStore
11404
11916
  });
11917
+ registry.add(detachColumnState);
11405
11918
  return {
11406
11919
  engine,
11407
11920
  controller,
@@ -11670,5 +12183,5 @@ function listUndoableEventTypes() {
11670
12183
  }
11671
12184
 
11672
12185
  export { ColumnAggIndex, DEFAULT_EVENT_STORE_PREFIX, DEFAULT_GRID_ACTOR, DEFAULT_MEMORY_STORE, EVENT_CATALOG, InMemoryEventStore, InMemorySnapshotStore, IndexedDbEventStore, LEGACY_EVENT_STORE_PREFIX, MemoryKvDatabase, NoopRemoteApi, OfflineStatus, PluginHost, QuickFilterIndex, SnapshotStrategy, SparseRowIndex, TableEngine, UITableController, accumulateAdd, accumulateRemove, addJalaliMonths, alwaysOnlineConnectivity, applyAgg, applyFillHandleDrag, applyFilter, applyHostRowDataToTable, applyQuickFilter, buildClipboardPayload, buildColumnGroupHeaderModel, buildFillUpdates, buildPastePlan, buildQuickFilterBlob, canSetColumnVisible, cellRangeClassName, cellRangeEdgeAttr, clampColIndex, clampRowIndex, clickCell, coercePastedCellValue, colIndex, collectUniqueFilterValues, columnDefFieldName, copyGridSelectionToClipboard, createAccumulator, createBrowserConnectivity, createERPTable, createEmptyCellRangeSelection, createEngineGridQuery, createGrid, createGridApi, createGridTableKey, createLocalStorageWatermark, ctrlClickCell, cutGridSelectionToClipboard, defaultGetRowId, detectFillSeries, ensureIndexColumnDef, eventStoreDbName, extendSeriesValue, flattenColDefOrGroup, getEventCatalogEntry, isAggFuncName, isCellInRange, isColumnFilterActive, isColumnGroupDef, isCustomAggFunc, isPersistentStore, isServerRowModel, isWindowedRepository, listUndoableEventTypes, mapColDefOrGroups, mapColumnDefs, mapColumnSchemas, mapColumnSchemasToDefs, mapRowDataToRowInputs, maxColumnGroupDepth, mergeCellRanges, moveCellAddress, normalizeCellRange, normalizeFilterText, normalizePersianNumberText, normalizeQuickFilterText, openIndexedDbDatabase, pageWindow, parseClipboardDate, parseClipboardNumber, parseDelimitedClipboardText, pasteGridFromClipboard, rangeFromAnchorFocus, rangesOverlap, readAccumulator, readSystemClipboardText, resolveBuiltinFilterKind, resolveCellAddressFromPoint, resolveCellRangePresentation, resolveColumnAggSpecs, resolveCopyRanges, resolveCreateGridDefaults, resolveFillCorner, resolveFilterUi, resolveGroupBlockReorder, selectAllCellRanges, shiftArrowCell, shiftClickCell, toColumnDef, toColumnDefinition, toColumnSchema, toColumnViewBinding, toMinorUnits, trapTabKey, writeSystemClipboard };
11673
- //# sourceMappingURL=chunk-HTNJT2QU.js.map
11674
- //# sourceMappingURL=chunk-HTNJT2QU.js.map
12186
+ //# sourceMappingURL=chunk-27ALJSZP.js.map
12187
+ //# sourceMappingURL=chunk-27ALJSZP.js.map