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
@@ -2,7 +2,7 @@
2
2
 
3
3
  var chunkMT3KLB2Q_cjs = require('./chunk-MT3KLB2Q.cjs');
4
4
  var chunkUC7PHDM6_cjs = require('./chunk-UC7PHDM6.cjs');
5
- var chunkDXRCYW42_cjs = require('./chunk-DXRCYW42.cjs');
5
+ var chunk52LFOPXD_cjs = require('./chunk-52LFOPXD.cjs');
6
6
  var chunkKMRURD5J_cjs = require('./chunk-KMRURD5J.cjs');
7
7
  var chunkJOICD47Q_cjs = require('./chunk-JOICD47Q.cjs');
8
8
  var chunkDFFP7SJ6_cjs = require('./chunk-DFFP7SJ6.cjs');
@@ -1335,6 +1335,7 @@ var SparseRowIndex = class {
1335
1335
  this.orderedIds = [];
1336
1336
  /** id → position in `orderedIds` (PR-99). */
1337
1337
  this.posById = /* @__PURE__ */ new Map();
1338
+ this.revisionCount = 0;
1338
1339
  for (const [id, value] of entries) {
1339
1340
  this.byId.set(id, value);
1340
1341
  this.posById.set(id, this.orderedIds.length);
@@ -1344,6 +1345,13 @@ var SparseRowIndex = class {
1344
1345
  get size() {
1345
1346
  return this.orderedIds.length;
1346
1347
  }
1348
+ /**
1349
+ * Bumped by every change of ids, order or values. O(1) change detection for
1350
+ * subscribers (a sort keeps `size` but changes the order).
1351
+ */
1352
+ get revision() {
1353
+ return this.revisionCount;
1354
+ }
1347
1355
  getOrderedIds() {
1348
1356
  return this.orderedIds;
1349
1357
  }
@@ -1365,6 +1373,7 @@ var SparseRowIndex = class {
1365
1373
  }
1366
1374
  set(id, value) {
1367
1375
  const existed = this.byId.has(id);
1376
+ this.revisionCount += 1;
1368
1377
  this.byId.set(id, value);
1369
1378
  if (!existed) {
1370
1379
  this.posById.set(id, this.orderedIds.length);
@@ -1373,6 +1382,7 @@ var SparseRowIndex = class {
1373
1382
  }
1374
1383
  /** Insert `id` at `pos` (0…size). Shifts later positions — O(n-pos). */
1375
1384
  insertAt(pos, id, value) {
1385
+ this.revisionCount += 1;
1376
1386
  if (this.byId.has(id)) {
1377
1387
  this.byId.set(id, value);
1378
1388
  return;
@@ -1386,6 +1396,7 @@ var SparseRowIndex = class {
1386
1396
  }
1387
1397
  remove(id) {
1388
1398
  if (!this.byId.has(id)) return false;
1399
+ this.revisionCount += 1;
1389
1400
  this.byId.delete(id);
1390
1401
  const pos = this.posById.get(id);
1391
1402
  this.posById.delete(id);
@@ -1398,6 +1409,7 @@ var SparseRowIndex = class {
1398
1409
  }
1399
1410
  /** Replace order without rebuilding payloads (e.g. after sort). */
1400
1411
  setOrder(orderedIds) {
1412
+ this.revisionCount += 1;
1401
1413
  this.orderedIds = orderedIds.slice();
1402
1414
  this.posById.clear();
1403
1415
  for (let i = 0; i < this.orderedIds.length; i += 1) {
@@ -1406,6 +1418,7 @@ var SparseRowIndex = class {
1406
1418
  }
1407
1419
  /** Full replace of ids + values in one pass (PR-99 rebuild). */
1408
1420
  replaceAll(orderedIds, valueFor) {
1421
+ this.revisionCount += 1;
1409
1422
  this.byId.clear();
1410
1423
  this.orderedIds = orderedIds.slice();
1411
1424
  this.posById.clear();
@@ -1416,6 +1429,7 @@ var SparseRowIndex = class {
1416
1429
  }
1417
1430
  }
1418
1431
  clear() {
1432
+ this.revisionCount += 1;
1419
1433
  this.byId.clear();
1420
1434
  this.orderedIds = [];
1421
1435
  this.posById.clear();
@@ -3144,21 +3158,35 @@ var TableStore = class {
3144
3158
  var DEFAULT_CELL_EDIT_OPTIONS = {
3145
3159
  singleClickEdit: true,
3146
3160
  stopEditingWhenCellsLoseFocus: true,
3147
- editType: "cell"
3161
+ editType: "cell",
3162
+ commit: "deferred",
3163
+ enterNavigation: "nextCell"
3148
3164
  };
3165
+ var sessionSeq = 0;
3149
3166
  function createCellEditSession(input) {
3150
3167
  const drafts = {};
3151
3168
  for (const [colId, cell] of Object.entries(input.baseline)) {
3152
3169
  drafts[colId] = { value: cell.value, display: cell.display };
3153
3170
  }
3171
+ sessionSeq += 1;
3154
3172
  return {
3155
3173
  rowId: input.rowId,
3156
3174
  columnId: input.columnId,
3157
3175
  editType: input.editType,
3158
3176
  baseline: input.baseline,
3159
- drafts
3177
+ drafts,
3178
+ correlationId: `cell-edit-${Date.now()}-${sessionSeq}`
3160
3179
  };
3161
3180
  }
3181
+ function rebaseDraft(session, columnIds) {
3182
+ if (columnIds.length === 0) return session;
3183
+ const baseline = { ...session.baseline };
3184
+ for (const colId of columnIds) {
3185
+ const draft = session.drafts[colId];
3186
+ if (draft) baseline[colId] = { value: draft.value, display: draft.display };
3187
+ }
3188
+ return { ...session, baseline };
3189
+ }
3162
3190
  function readDraft(session, rowId, columnId) {
3163
3191
  if (!session || session.rowId !== rowId) return null;
3164
3192
  return session.drafts[columnId] ?? null;
@@ -3183,8 +3211,8 @@ function patchDraft(session, columnId, patch) {
3183
3211
  drafts: {
3184
3212
  ...session.drafts,
3185
3213
  [columnId]: {
3186
- value: patch.value !== void 0 ? patch.value : prev.value,
3187
- display: patch.display !== void 0 ? patch.display : prev.display
3214
+ value: "value" in patch ? patch.value : prev.value,
3215
+ display: "display" in patch ? patch.display : prev.display
3188
3216
  }
3189
3217
  }
3190
3218
  };
@@ -4169,6 +4197,7 @@ var ViewStateBuilder = class {
4169
4197
  };
4170
4198
 
4171
4199
  // engine/tableEngine.ts
4200
+ var cellWriteKey = (kind, rowId, columnId) => `${kind}\0${rowId}\0${columnId}`;
4172
4201
  var TableEngine = class {
4173
4202
  constructor(app, repo, actor, audit, columnMeta) {
4174
4203
  this.app = app;
@@ -4176,6 +4205,15 @@ var TableEngine = class {
4176
4205
  this.actor = actor;
4177
4206
  this.columnMeta = columnMeta;
4178
4207
  this.cellEditOptions = { ...DEFAULT_CELL_EDIT_OPTIONS };
4208
+ /**
4209
+ * Cell writes coming from editors / host input, executed one at a time in
4210
+ * arrival order. Consecutive values for the same cell coalesce (latest
4211
+ * wins). Until a write landed, the queued value is what the cell shows.
4212
+ */
4213
+ this.cellWrites = /* @__PURE__ */ new Map();
4214
+ this.cellWriteChain = Promise.resolve();
4215
+ /** > 0 while a queued write executes (re-entrant settle must not wait on itself). */
4216
+ this.cellWriteDepth = 0;
4179
4217
  this.errors = /* @__PURE__ */ new Map();
4180
4218
  this.searchResult = /* @__PURE__ */ new Map();
4181
4219
  this.asyncCellCache = /* @__PURE__ */ new Map();
@@ -4750,54 +4788,54 @@ var TableEngine = class {
4750
4788
  const next = command.sortModel.filter(
4751
4789
  (item) => item && typeof item.colId === "string" && item.colId.length > 0 && (item.sort === "asc" || item.sort === "desc")
4752
4790
  ).map((item) => ({ colId: item.colId, sort: item.sort }));
4791
+ this.viewBuilder.invalidateUiRowIndex();
4753
4792
  store.setState((prev) => ({
4754
4793
  ...prev,
4755
4794
  sortModel: next
4756
4795
  }));
4757
- this.viewBuilder.invalidateUiRowIndex();
4758
4796
  this.notifyViewQueryChanged();
4759
4797
  }
4760
4798
  setQuickFilterText(command) {
4761
4799
  const store = this.getStore(command.tableKey);
4762
4800
  if (!store) return;
4763
4801
  const next = typeof command.quickFilterText === "string" ? command.quickFilterText : "";
4802
+ this.viewBuilder.invalidateUiRowIndex();
4764
4803
  store.setState((prev) => ({
4765
4804
  ...prev,
4766
4805
  quickFilterText: next
4767
4806
  }));
4768
- this.viewBuilder.invalidateUiRowIndex();
4769
4807
  }
4770
4808
  setFilterModel(command) {
4771
4809
  const store = this.getStore(command.tableKey);
4772
4810
  if (!store) return;
4773
4811
  const next = command.filterModel && typeof command.filterModel === "object" ? { ...command.filterModel } : {};
4812
+ this.viewBuilder.invalidateUiRowIndex();
4774
4813
  store.setState((prev) => ({
4775
4814
  ...prev,
4776
4815
  filterModel: next
4777
4816
  }));
4778
- this.viewBuilder.invalidateUiRowIndex();
4779
4817
  this.notifyViewQueryChanged();
4780
4818
  }
4781
4819
  setRowGroupColumns(command) {
4782
4820
  const store = this.getStore(command.tableKey);
4783
4821
  if (!store) return;
4784
4822
  const next = command.columns.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
4823
+ this.viewBuilder.invalidateUiRowIndex();
4785
4824
  store.setState((prev) => ({
4786
4825
  ...prev,
4787
4826
  rowGroupColumns: next,
4788
4827
  rowGroupExpanded: {}
4789
4828
  }));
4790
- this.viewBuilder.invalidateUiRowIndex();
4791
4829
  }
4792
4830
  setGroupDefaultExpanded(command) {
4793
4831
  const store = this.getStore(command.tableKey);
4794
4832
  if (!store) return;
4833
+ this.viewBuilder.invalidateUiRowIndex();
4795
4834
  store.setState((prev) => ({
4796
4835
  ...prev,
4797
4836
  groupDefaultExpanded: Boolean(command.expanded),
4798
4837
  rowGroupExpanded: {}
4799
4838
  }));
4800
- this.viewBuilder.invalidateUiRowIndex();
4801
4839
  }
4802
4840
  toggleRowGroup(command) {
4803
4841
  const store = this.getStore(command.tableKey);
@@ -4808,6 +4846,7 @@ var TableEngine = class {
4808
4846
  state.rowGroupExpanded,
4809
4847
  key
4810
4848
  ) ? state.rowGroupExpanded[key] : state.groupDefaultExpanded;
4849
+ this.viewBuilder.invalidateUiRowIndex();
4811
4850
  store.setState((prev) => ({
4812
4851
  ...prev,
4813
4852
  rowGroupExpanded: {
@@ -4815,7 +4854,6 @@ var TableEngine = class {
4815
4854
  [key]: !current
4816
4855
  }
4817
4856
  }));
4818
- this.viewBuilder.invalidateUiRowIndex();
4819
4857
  }
4820
4858
  /**
4821
4859
  * Server/infinite row model hooks (PR-96).
@@ -5084,6 +5122,7 @@ var TableEngine = class {
5084
5122
  async getFullSnapShot(store, collectionScope = "active") {
5085
5123
  if (!this.tableKey) return null;
5086
5124
  try {
5125
+ await this.flushCellEdit();
5087
5126
  const rows = await this.loadAllRows(store, collectionScope);
5088
5127
  const { rowsToSnapshot } = await import('./memoryRecords-AWDKUJ2F.cjs');
5089
5128
  return rowsToSnapshot(rows);
@@ -5100,6 +5139,7 @@ var TableEngine = class {
5100
5139
  async iterateRowsFromDb(store, collectionScope, onBatch, batchSize) {
5101
5140
  if (!this.tableKey) return;
5102
5141
  try {
5142
+ await this.flushCellEdit();
5103
5143
  if (isWindowedRepository(this.repo)) {
5104
5144
  await this.repo.iterateRowRecords(
5105
5145
  store,
@@ -5156,8 +5196,196 @@ var TableEngine = class {
5156
5196
  getCellEdit() {
5157
5197
  return this.tableStore?.getState().cellEdit ?? null;
5158
5198
  }
5199
+ /**
5200
+ * What the cell editor shows: the session draft, else a value that was
5201
+ * typed but whose write has not reached the table yet (no flicker back to
5202
+ * the old value while it is in flight). `null` = show the table value.
5203
+ */
5159
5204
  getEditDraft(rowId, columnId) {
5160
- return readDraft(this.getCellEdit(), rowId, columnId);
5205
+ const draft = readDraft(this.getCellEdit(), rowId, columnId);
5206
+ if (draft) return draft;
5207
+ const value = this.cellWrites.get(cellWriteKey("value", rowId, columnId));
5208
+ const display = this.cellWrites.get(cellWriteKey("display", rowId, columnId));
5209
+ if (!value && !display) return null;
5210
+ const cell = this.viewBuilder.getCell(rowId, columnId);
5211
+ return {
5212
+ value: value ? value.write.value : cell?.getValue(),
5213
+ display: display ? display.write.value : cell?.getDisplay()
5214
+ };
5215
+ }
5216
+ /** Editor / host value for a cell (`UI_CELL_CHANGE`). Resolves once it is durable. */
5217
+ inputCellValue(rowId, columnId, value) {
5218
+ return this.inputCell(rowId, columnId, "value", value);
5219
+ }
5220
+ /** Editor / host display label for a cell (`UI_CELL_CHANGE_DISPLAY`). */
5221
+ inputCellDisplay(rowId, columnId, display) {
5222
+ return this.inputCell(rowId, columnId, "display", display);
5223
+ }
5224
+ async inputCell(rowId, columnId, kind, input) {
5225
+ const store = this.tableStore;
5226
+ if (!store) return;
5227
+ const session = store.getState().cellEdit;
5228
+ if (session && isEditingCell(session, rowId, columnId)) {
5229
+ const immediate = this.cellEditOptions.commit === "immediate";
5230
+ const patch = kind === "value" ? { value: input } : { display: input };
5231
+ store.setState((prev) => {
5232
+ if (!prev.cellEdit || prev.cellEdit.rowId !== rowId) return prev;
5233
+ const patched = patchDraft(prev.cellEdit, columnId, patch);
5234
+ return {
5235
+ ...prev,
5236
+ cellEdit: immediate ? rebaseDraft(patched, [columnId]) : patched
5237
+ };
5238
+ });
5239
+ if (!immediate) return;
5240
+ this.enqueueCellWrite({
5241
+ rowId,
5242
+ columnId,
5243
+ kind,
5244
+ value: input,
5245
+ correlationId: session.correlationId
5246
+ });
5247
+ } else {
5248
+ this.enqueueCellWrite({ rowId, columnId, kind, value: input });
5249
+ }
5250
+ await this.settleCellWrites();
5251
+ }
5252
+ enqueueCellWrite(write) {
5253
+ const key = cellWriteKey(write.kind, write.rowId, write.columnId);
5254
+ const pending = this.cellWrites.get(key);
5255
+ if (pending?.queued) {
5256
+ pending.write = write;
5257
+ return;
5258
+ }
5259
+ const slot = { write, queued: true };
5260
+ this.cellWrites.set(key, slot);
5261
+ this.cellWriteChain = this.cellWriteChain.then(() => this.runCellWrite(key, slot));
5262
+ }
5263
+ async runCellWrite(key, slot) {
5264
+ slot.queued = false;
5265
+ const { write } = slot;
5266
+ const commandStore = this.commandStore;
5267
+ const tableKey = this.tableKey;
5268
+ this.cellWriteDepth += 1;
5269
+ try {
5270
+ if (commandStore && tableKey && this.tableStore) {
5271
+ await this.dispatch(
5272
+ write.kind === "value" ? {
5273
+ type: "CHANGE_CELL_VALUE",
5274
+ store: commandStore,
5275
+ tableKey,
5276
+ rowId: write.rowId,
5277
+ columnId: write.columnId,
5278
+ newValue: write.value,
5279
+ correlationId: write.correlationId
5280
+ } : {
5281
+ type: "CHANGE_CELL_DISPLAY_VALUE",
5282
+ store: commandStore,
5283
+ tableKey,
5284
+ rowId: write.rowId,
5285
+ columnId: write.columnId,
5286
+ newValue: write.value == null ? "" : String(write.value),
5287
+ correlationId: write.correlationId
5288
+ }
5289
+ );
5290
+ }
5291
+ } catch (e) {
5292
+ this.errHandler.handle(e, "TableEngine.cellWrite");
5293
+ } finally {
5294
+ this.cellWriteDepth -= 1;
5295
+ if (this.cellWrites.get(key) === slot && !slot.queued) {
5296
+ this.cellWrites.delete(key);
5297
+ const cell = this.viewBuilder.getCell(write.rowId, write.columnId);
5298
+ const landed = write.kind === "value" ? cell?.getValue() : cell?.getDisplay();
5299
+ if (landed !== write.value) this.tableStore?.setState((prev) => ({ ...prev }));
5300
+ }
5301
+ }
5302
+ }
5303
+ /**
5304
+ * Resolves when every queued cell write reached the table. Called from inside
5305
+ * a write (re-entrant) it returns at once instead of waiting on itself.
5306
+ */
5307
+ async settleCellWrites() {
5308
+ if (this.cellWriteDepth > 0) return;
5309
+ let chain;
5310
+ do {
5311
+ chain = this.cellWriteChain;
5312
+ await chain;
5313
+ } while (chain !== this.cellWriteChain);
5314
+ }
5315
+ /** Queues CHANGE_CELL_* for `columnIds` of `session`; returns the value writes. */
5316
+ queueDraftWrites(session, columnIds) {
5317
+ let writes = 0;
5318
+ for (const colId of columnIds) {
5319
+ const draft = session.drafts[colId];
5320
+ if (!draft) continue;
5321
+ const base = session.baseline[colId];
5322
+ if (!base || draft.value !== base.value) {
5323
+ this.enqueueCellWrite({
5324
+ rowId: session.rowId,
5325
+ columnId: colId,
5326
+ kind: "value",
5327
+ value: draft.value,
5328
+ correlationId: session.correlationId
5329
+ });
5330
+ writes += 1;
5331
+ }
5332
+ if (!base || draft.display !== base.display) {
5333
+ this.enqueueCellWrite({
5334
+ rowId: session.rowId,
5335
+ columnId: colId,
5336
+ kind: "display",
5337
+ value: draft.display,
5338
+ correlationId: session.correlationId
5339
+ });
5340
+ }
5341
+ }
5342
+ return writes;
5343
+ }
5344
+ /**
5345
+ * Make what the user typed durable WITHOUT ending the edit: dirty drafts are
5346
+ * written and become the session baseline, then all queued writes settle.
5347
+ * Every durable read (snapshot, row stream, export) calls this first, so a
5348
+ * save never misses the value still in the editor.
5349
+ */
5350
+ async flushCellEdit() {
5351
+ const store = this.tableStore;
5352
+ const session = store?.getState().cellEdit;
5353
+ if (store && session) {
5354
+ const dirty = dirtyDraftColumns(session);
5355
+ if (dirty.length > 0) {
5356
+ store.setState(
5357
+ (prev) => prev.cellEdit && prev.cellEdit.rowId === session.rowId ? { ...prev, cellEdit: rebaseDraft(prev.cellEdit, dirty) } : prev
5358
+ );
5359
+ this.queueDraftWrites(session, dirty);
5360
+ }
5361
+ }
5362
+ await this.settleCellWrites();
5363
+ }
5364
+ /**
5365
+ * Focus / pointer moved outside the grid. Deferred commit ends the edit per
5366
+ * `stopEditingWhenCellsLoseFocus`; immediate commit has nothing pending and
5367
+ * keeps the editor (legacy table behaviour).
5368
+ */
5369
+ async leaveGridFocus() {
5370
+ if (!this.getCellEdit()) return;
5371
+ if (this.cellEditOptions.commit !== "deferred") return;
5372
+ if (!this.cellEditOptions.stopEditingWhenCellsLoseFocus) return;
5373
+ await this.commitCellEdit();
5374
+ }
5375
+ /**
5376
+ * Before dispose / unmount: commit (or drop, when
5377
+ * `stopEditingWhenCellsLoseFocus` is false) the open edit and wait until
5378
+ * every queued write is durable.
5379
+ */
5380
+ async settleEditsForTeardown() {
5381
+ if (this.getCellEdit()) {
5382
+ if (this.cellEditOptions.stopEditingWhenCellsLoseFocus) {
5383
+ await this.commitCellEdit();
5384
+ } else {
5385
+ this.cancelCellEdit();
5386
+ }
5387
+ }
5388
+ await this.settleCellWrites();
5161
5389
  }
5162
5390
  isEditingCell(rowId, columnId) {
5163
5391
  return isEditingCell(this.getCellEdit(), rowId, columnId);
@@ -5248,39 +5476,12 @@ var TableEngine = class {
5248
5476
  */
5249
5477
  async commitCellEdit() {
5250
5478
  const store = this.tableStore;
5251
- const commandStore = this.commandStore;
5252
- const tableKey = this.tableKey;
5253
- if (!store || !commandStore || !tableKey) return 0;
5479
+ if (!store || !this.commandStore || !this.tableKey) return 0;
5254
5480
  const session = store.getState().cellEdit;
5255
5481
  if (!session) return 0;
5256
- const dirty = dirtyDraftColumns(session);
5257
5482
  store.setState((prev) => ({ ...prev, cellEdit: null }));
5258
- let writes = 0;
5259
- for (const colId of dirty) {
5260
- const draft = session.drafts[colId];
5261
- const base = session.baseline[colId];
5262
- if (!base || draft.value !== base.value) {
5263
- await this.dispatch({
5264
- type: "CHANGE_CELL_VALUE",
5265
- store: commandStore,
5266
- tableKey,
5267
- rowId: session.rowId,
5268
- columnId: colId,
5269
- newValue: draft.value
5270
- });
5271
- writes += 1;
5272
- }
5273
- if (!base || draft.display !== base.display) {
5274
- await this.dispatch({
5275
- type: "CHANGE_CELL_DISPLAY_VALUE",
5276
- store: commandStore,
5277
- tableKey,
5278
- rowId: session.rowId,
5279
- columnId: colId,
5280
- newValue: draft.display == null ? "" : String(draft.display)
5281
- });
5282
- }
5283
- }
5483
+ const writes = this.queueDraftWrites(session, dirtyDraftColumns(session));
5484
+ await this.settleCellWrites();
5284
5485
  return writes;
5285
5486
  }
5286
5487
  async transitionActiveCell(rowId, columnId) {
@@ -5469,16 +5670,15 @@ var TableEngine = class {
5469
5670
  if (syncError) return syncError;
5470
5671
  return this.applyAsyncValidators(value, schema, caption, rowId);
5471
5672
  }
5472
- finishValidation(cb, tableStore) {
5673
+ finishValidation(cb, tableStore, errorsChanged) {
5473
5674
  cb({
5474
5675
  valid: this.errors.size === 0,
5475
5676
  errors: this.errors,
5476
5677
  data: this.getAllRows()
5477
5678
  });
5478
- tableStore.setState((prev) => ({
5479
- ...prev,
5480
- expandedRows: {}
5481
- }));
5679
+ tableStore.setState(
5680
+ (prev) => !errorsChanged && Object.keys(prev.expandedRows ?? {}).length === 0 ? prev : { ...prev, expandedRows: {} }
5681
+ );
5482
5682
  }
5483
5683
  collectValidationJobs(table) {
5484
5684
  const jobs = [];
@@ -5497,11 +5697,23 @@ var TableEngine = class {
5497
5697
  });
5498
5698
  return jobs;
5499
5699
  }
5700
+ /** Replaces the error map; true when the set of errors / messages changed. */
5500
5701
  applyValidationErrors(nextErrors) {
5702
+ let changed = nextErrors.size !== this.errors.size;
5703
+ if (!changed) {
5704
+ for (const [cellId, error] of nextErrors) {
5705
+ const prev = this.errors.get(cellId);
5706
+ if (!prev || prev.message !== error.message || prev.rowId !== error.rowId) {
5707
+ changed = true;
5708
+ break;
5709
+ }
5710
+ }
5711
+ }
5501
5712
  this.errors.clear();
5502
5713
  nextErrors.forEach((error, cellId) => {
5503
5714
  this.errors.set(cellId, error);
5504
5715
  });
5716
+ return changed;
5505
5717
  }
5506
5718
  runValidationSync(table, cb, tableStore) {
5507
5719
  const nextErrors = /* @__PURE__ */ new Map();
@@ -5514,8 +5726,8 @@ var TableEngine = class {
5514
5726
  );
5515
5727
  if (error) nextErrors.set(job.cellId, error);
5516
5728
  }
5517
- this.applyValidationErrors(nextErrors);
5518
- this.finishValidation(cb, tableStore);
5729
+ const changed = this.applyValidationErrors(nextErrors);
5730
+ this.finishValidation(cb, tableStore, changed);
5519
5731
  }
5520
5732
  async runValidationAsync(table, cb, tableStore) {
5521
5733
  const nextErrors = /* @__PURE__ */ new Map();
@@ -5528,8 +5740,8 @@ var TableEngine = class {
5528
5740
  );
5529
5741
  if (error) nextErrors.set(job.cellId, error);
5530
5742
  }
5531
- this.applyValidationErrors(nextErrors);
5532
- this.finishValidation(cb, tableStore);
5743
+ const changed = this.applyValidationErrors(nextErrors);
5744
+ this.finishValidation(cb, tableStore, changed);
5533
5745
  }
5534
5746
  scheduleAsyncCellCheck(cellId, valueKey, value, schema, caption, rowId) {
5535
5747
  const seq = (this.asyncCellSeq.get(cellId) ?? 0) + 1;
@@ -5874,6 +6086,9 @@ async function openIndexedDbDatabase(dbName, storeNames, version = 1) {
5874
6086
  request.onsuccess = () => resolve(request.result);
5875
6087
  });
5876
6088
  return {
6089
+ close() {
6090
+ db.close();
6091
+ },
5877
6092
  objectStore(name) {
5878
6093
  return {
5879
6094
  async get(key) {
@@ -5925,6 +6140,8 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5925
6140
  this.db = db;
5926
6141
  this.listeners = /* @__PURE__ */ new Map();
5927
6142
  this.globalListeners = /* @__PURE__ */ new Set();
6143
+ this.closed = false;
6144
+ this.inflight = /* @__PURE__ */ new Set();
5928
6145
  this.ready = Promise.resolve();
5929
6146
  }
5930
6147
  static async open(dbName = DEFAULT_EVENT_STORE_PREFIX) {
@@ -5935,7 +6152,36 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5935
6152
  static inMemory() {
5936
6153
  return new _IndexedDbEventStore(new MemoryKvDatabase());
5937
6154
  }
5938
- async append(streamId, events, expectedVersion) {
6155
+ /**
6156
+ * Release the IndexedDB connection (stored events are untouched). Waits for
6157
+ * appends already in flight; appends started afterwards are dropped (they
6158
+ * resolve `ok` without writing) because the owning grid is gone. Idempotent.
6159
+ */
6160
+ close() {
6161
+ if (!this.closePromise) {
6162
+ this.closed = true;
6163
+ this.closePromise = (async () => {
6164
+ await Promise.allSettled([...this.inflight]);
6165
+ this.listeners.clear();
6166
+ this.globalListeners.clear();
6167
+ this.db.close?.();
6168
+ })();
6169
+ }
6170
+ return this.closePromise;
6171
+ }
6172
+ append(streamId, events, expectedVersion) {
6173
+ if (this.closed) {
6174
+ return Promise.resolve({ ok: true, version: 0 });
6175
+ }
6176
+ const job = this.appendInternal(streamId, events, expectedVersion);
6177
+ this.inflight.add(job);
6178
+ const forget = () => {
6179
+ this.inflight.delete(job);
6180
+ };
6181
+ job.then(forget, forget);
6182
+ return job;
6183
+ }
6184
+ async appendInternal(streamId, events, expectedVersion) {
5939
6185
  await this.ready;
5940
6186
  const streamStore = this.db.objectStore(STREAMS);
5941
6187
  const globalStore = this.db.objectStore(GLOBAL);
@@ -5975,11 +6221,13 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5975
6221
  return { ok: true, version: record.events.length };
5976
6222
  }
5977
6223
  async read(streamId, fromVersion = 0) {
6224
+ if (this.closed) return [];
5978
6225
  await this.ready;
5979
6226
  const record = await this.db.objectStore(STREAMS).get(streamId);
5980
6227
  return (record?.events ?? []).slice(fromVersion);
5981
6228
  }
5982
6229
  async readAll(fromPosition = 0) {
6230
+ if (this.closed) return [];
5983
6231
  await this.ready;
5984
6232
  const globalStore = this.db.objectStore(GLOBAL);
5985
6233
  const keys = (await globalStore.getAllKeys()).map(Number).filter((n) => Number.isFinite(n)).sort((a, b) => a - b).filter((n) => n > fromPosition);
@@ -5991,6 +6239,7 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
5991
6239
  return events;
5992
6240
  }
5993
6241
  subscribe(streamId, onEvent) {
6242
+ if (this.closed) return () => void 0;
5994
6243
  if (!this.listeners.has(streamId)) {
5995
6244
  this.listeners.set(streamId, /* @__PURE__ */ new Set());
5996
6245
  }
@@ -6001,6 +6250,7 @@ var IndexedDbEventStore = class _IndexedDbEventStore {
6001
6250
  };
6002
6251
  }
6003
6252
  subscribeAll(onEvent) {
6253
+ if (this.closed) return () => void 0;
6004
6254
  this.globalListeners.add(onEvent);
6005
6255
  return () => {
6006
6256
  this.globalListeners.delete(onEvent);
@@ -6049,6 +6299,8 @@ var OfflineStatus = class {
6049
6299
  this.syncedIds = /* @__PURE__ */ new Set();
6050
6300
  this.connectivityUnsub = null;
6051
6301
  this.storeUnsub = null;
6302
+ /** Incremented by every stop(); invalidates an in-flight start(). */
6303
+ this.lifecycle = 0;
6052
6304
  this.connectivityListeners = /* @__PURE__ */ new Set();
6053
6305
  this.pendingListeners = /* @__PURE__ */ new Set();
6054
6306
  this.online = options.connectivity.getOnline();
@@ -6056,11 +6308,14 @@ var OfflineStatus = class {
6056
6308
  }
6057
6309
  async start(eventStore) {
6058
6310
  this.stop();
6311
+ const run2 = this.lifecycle;
6059
6312
  this.connectivityUnsub = this.options.connectivity.subscribe((online) => {
6313
+ if (run2 !== this.lifecycle) return;
6060
6314
  this.online = online;
6061
6315
  this.emitConnectivity();
6062
6316
  });
6063
6317
  const past = await eventStore.readAll(0);
6318
+ if (run2 !== this.lifecycle) return;
6064
6319
  for (const event of past) {
6065
6320
  this.consider(event);
6066
6321
  }
@@ -6069,7 +6324,14 @@ var OfflineStatus = class {
6069
6324
  });
6070
6325
  this.emitPending();
6071
6326
  }
6327
+ /** Stop listening and drop all listeners. The instance is inert afterwards. */
6328
+ dispose() {
6329
+ this.stop();
6330
+ this.connectivityListeners.clear();
6331
+ this.pendingListeners.clear();
6332
+ }
6072
6333
  stop() {
6334
+ this.lifecycle += 1;
6073
6335
  this.connectivityUnsub?.();
6074
6336
  this.connectivityUnsub = null;
6075
6337
  this.storeUnsub?.();
@@ -6576,18 +6838,40 @@ var UITableController = class {
6576
6838
  return this.syncPort;
6577
6839
  }
6578
6840
  setSyncPort(port) {
6841
+ if (this.releasePromise) {
6842
+ port?.dispose?.();
6843
+ return;
6844
+ }
6579
6845
  this.syncPort = port;
6580
6846
  }
6581
6847
  getPresencePort() {
6582
6848
  return this.presencePort;
6583
6849
  }
6584
6850
  setPresencePort(port) {
6851
+ if (this.releasePromise) {
6852
+ port?.dispose();
6853
+ return;
6854
+ }
6585
6855
  this.presencePort = port;
6586
6856
  }
6857
+ /**
6858
+ * Composition root hook: the callback that releases every resource the grid
6859
+ * created (subscriptions, projections, plugins, owned event store).
6860
+ * Run once by {@link releaseResources}.
6861
+ */
6862
+ setResourceTeardown(teardown) {
6863
+ this.resourceTeardown = teardown;
6864
+ }
6865
+ /** True once resources were released (via releaseResources or dispose). */
6866
+ isReleased() {
6867
+ return this.releasePromise !== void 0;
6868
+ }
6587
6869
  getPresencePeers() {
6588
6870
  return this.presencePort?.getPeers() ?? [];
6589
6871
  }
6590
6872
  subscribePresencePeers(cb) {
6873
+ if (this.isReleased()) return () => {
6874
+ };
6591
6875
  if (!this.presencePort) {
6592
6876
  cb([]);
6593
6877
  return () => {
@@ -6612,6 +6896,8 @@ var UITableController = class {
6612
6896
  this.offlineStatus?.markSynced(eventIds);
6613
6897
  }
6614
6898
  subscribeConnectivity(cb) {
6899
+ if (this.isReleased()) return () => {
6900
+ };
6615
6901
  if (!this.offlineStatus) {
6616
6902
  cb("online");
6617
6903
  return () => {
@@ -6620,6 +6906,8 @@ var UITableController = class {
6620
6906
  return this.offlineStatus.subscribeConnectivity(cb);
6621
6907
  }
6622
6908
  subscribePendingChanges(cb) {
6909
+ if (this.isReleased()) return () => {
6910
+ };
6623
6911
  if (!this.offlineStatus) {
6624
6912
  cb([]);
6625
6913
  return () => {
@@ -6637,6 +6925,8 @@ var UITableController = class {
6637
6925
  return this.syncPort?.getOpenConflicts() ?? [];
6638
6926
  }
6639
6927
  subscribeSyncConflicts(cb) {
6928
+ if (this.isReleased()) return () => {
6929
+ };
6640
6930
  if (!this.syncPort) {
6641
6931
  cb([]);
6642
6932
  return () => {
@@ -6645,10 +6935,11 @@ var UITableController = class {
6645
6935
  return this.syncPort.subscribeConflicts(cb);
6646
6936
  }
6647
6937
  async syncNow() {
6648
- if (!this.syncPort) return null;
6938
+ if (this.isReleased() || !this.syncPort) return null;
6649
6939
  return this.syncPort.sync();
6650
6940
  }
6651
6941
  async resolveSyncConflict(conflictId, choice) {
6942
+ if (this.isReleased()) return;
6652
6943
  await this.syncPort?.resolveConflict(conflictId, choice);
6653
6944
  }
6654
6945
  /** Apply a remote cell value (used by SyncPort; dual-writes via engine). */
@@ -6697,24 +6988,17 @@ var UITableController = class {
6697
6988
  changePage(page) {
6698
6989
  this.engine.dispatch({ type: "CHANGE_PAGE", store: this.store, tableKey: this.tableKey, page });
6699
6990
  }
6991
+ /** Editor of this cell → session draft (written per `cellEditCommit`); otherwise write-through. */
6700
6992
  onCellChange(rowId, columnId, value) {
6701
- if (this.engine.isEditingCell(rowId, columnId)) {
6702
- this.engine.patchCellEdit(rowId, columnId, { value });
6703
- return;
6704
- }
6705
- this.engine.dispatch({ type: "CHANGE_CELL_VALUE", store: this.store, tableKey: this.tableKey, rowId, columnId, newValue: value });
6993
+ return this.engine.inputCellValue(rowId, columnId, value);
6706
6994
  }
6707
6995
  onDescriptionSave(rowId, flag) {
6708
6996
  this.engine.dispatch({ type: "SAVE_DESCRIPTION", store: this.store, tableKey: this.tableKey, rowId, flag });
6709
6997
  }
6710
6998
  onCellDisplayChange(rowId, columnId, value) {
6711
- if (this.engine.isEditingCell(rowId, columnId)) {
6712
- this.engine.patchCellEdit(rowId, columnId, { display: value });
6713
- return;
6714
- }
6715
- this.engine.dispatch({ type: "CHANGE_CELL_DISPLAY_VALUE", store: this.store, tableKey: this.tableKey, rowId, columnId, newValue: value });
6999
+ return this.engine.inputCellDisplay(rowId, columnId, value);
6716
7000
  }
6717
- setActiveCell(rowId, columnId) {
7001
+ async setActiveCell(rowId, columnId) {
6718
7002
  const prev = this.engine.getActiveCell();
6719
7003
  if (prev?.rowId === rowId && prev?.columnId === columnId) {
6720
7004
  if (this.engine.getCellEditOptions().singleClickEdit && !this.engine.getCellEdit()) {
@@ -6731,14 +7015,14 @@ var UITableController = class {
6731
7015
  rowId
6732
7016
  });
6733
7017
  }
6734
- this.engine.dispatch({ type: "ACTIVE_CELL", tableKey: this.tableKey, rowId, columnId });
6735
7018
  this.publishPresenceCell(rowId, columnId);
7019
+ await this.engine.dispatch({ type: "ACTIVE_CELL", tableKey: this.tableKey, rowId, columnId });
6736
7020
  }
6737
7021
  setActiveRow(rowId) {
6738
7022
  this.engine.dispatch({ type: "ACTIVE_ROW", store: this.store, tableKey: this.tableKey, rowId });
6739
7023
  }
6740
7024
  moveActiveCellVertical(rowId, columnId, direction) {
6741
- this.engine.dispatch({ type: "MOVE_ACTIVE_CELL_VERTICAL", store: this.store, tableKey: this.tableKey, rowId, columnId, direction });
7025
+ return this.engine.dispatch({ type: "MOVE_ACTIVE_CELL_VERTICAL", store: this.store, tableKey: this.tableKey, rowId, columnId, direction });
6742
7026
  }
6743
7027
  reorderRow(draggedRowId, droppedRowId, position) {
6744
7028
  this.engine.dispatch({ type: "ROW_REORDERED", store: this.store, tableKey: this.tableKey, draggedRowId, droppedRowId, position });
@@ -6814,7 +7098,7 @@ var UITableController = class {
6814
7098
  this.engine.dispatch({ type: "ACTIVATE_FIRST_CELL", tableKey: this.tableKey, rowId });
6815
7099
  }
6816
7100
  moveToNextCell(rowId, columnId, direction = "next") {
6817
- this.engine.dispatch({ type: "MOVE_ACTIVE_CELL", store: this.store, tableKey: this.tableKey, rowId, columnId, direction });
7101
+ return this.engine.dispatch({ type: "MOVE_ACTIVE_CELL", store: this.store, tableKey: this.tableKey, rowId, columnId, direction });
6818
7102
  }
6819
7103
  async setTableAction(action) {
6820
7104
  await this.engine.dispatch({ type: "SET_TABLE_ACTION", store: this.store, tableKey: this.tableKey, action });
@@ -7082,7 +7366,7 @@ var UITableController = class {
7082
7366
  ) ?? 0;
7083
7367
  }
7084
7368
  subscribeChangelog(cb, filter = {}) {
7085
- if (!this.changelogProjection) return () => {
7369
+ if (!this.changelogProjection || this.isReleased()) return () => {
7086
7370
  };
7087
7371
  return this.safe(
7088
7372
  () => this.changelogProjection.subscribe(cb, { tableId: this.tableKey, ...filter }),
@@ -7105,7 +7389,7 @@ var UITableController = class {
7105
7389
  ) ?? 0;
7106
7390
  }
7107
7391
  subscribeHistoryItems(cb, filter = {}) {
7108
- if (!this.historyPanelViewModel) return () => {
7392
+ if (!this.historyPanelViewModel || this.isReleased()) return () => {
7109
7393
  };
7110
7394
  return this.safe(
7111
7395
  () => this.historyPanelViewModel.subscribe(cb, { tableId: this.tableKey, ...filter }),
@@ -7178,6 +7462,40 @@ var UITableController = class {
7178
7462
  commitCellEdit() {
7179
7463
  return this.safe(() => this.engine.commitCellEdit(), "commitCellEdit") ?? Promise.resolve(0);
7180
7464
  }
7465
+ /**
7466
+ * Write what is still in the cell editor (the edit stays open) and wait
7467
+ * until every pending cell write is durable. `getFullSnapShot` /
7468
+ * `iterateRowsFromDb` / exports already do this; call it before reading
7469
+ * synchronous APIs (`getRowView`, `getSnapShot`) for a save.
7470
+ */
7471
+ flushCellEdit() {
7472
+ return this.engine.flushCellEdit().catch((error) => {
7473
+ this.audit.log({
7474
+ type: "ERROR",
7475
+ message: "UITableController.flushCellEdit failed",
7476
+ payload: { error, tableKey: this.tableKey }
7477
+ });
7478
+ });
7479
+ }
7480
+ /** DataGrid: focus / pointer left the grid (see `stopEditingWhenCellsLoseFocus`). */
7481
+ leaveGridFocus() {
7482
+ return this.engine.leaveGridFocus().catch((error) => {
7483
+ this.audit.log({
7484
+ type: "ERROR",
7485
+ message: "UITableController.leaveGridFocus failed",
7486
+ payload: { error, tableKey: this.tableKey }
7487
+ });
7488
+ });
7489
+ }
7490
+ settleEditsForTeardown() {
7491
+ return this.engine.settleEditsForTeardown().catch((error) => {
7492
+ this.audit.log({
7493
+ type: "ERROR",
7494
+ message: "UITableController: committing the open edit before teardown failed",
7495
+ payload: { error, tableKey: this.tableKey }
7496
+ });
7497
+ });
7498
+ }
7181
7499
  getEditDraft(rowId, columnId) {
7182
7500
  return this.safe(
7183
7501
  () => this.engine.getEditDraft(rowId, columnId),
@@ -7298,12 +7616,68 @@ var UITableController = class {
7298
7616
  this.engine.addCellRange(range);
7299
7617
  }
7300
7618
  // ─── lifecycle ───────────────────────────────────────────────────────
7619
+ /**
7620
+ * Full dispose: `TABLE_DISPOSED` (windowed repo: drop the RAM page window,
7621
+ * other repos: `repo.delete` — unchanged data semantics) followed by
7622
+ * {@link releaseResources}. Idempotent; the promise settles once teardown
7623
+ * finished and never rejects.
7624
+ */
7301
7625
  dispose() {
7302
- if (this.disposed) return;
7626
+ if (this.disposed) return this.releasePromise ?? this.releaseResources();
7303
7627
  this.disposed = true;
7304
7628
  this.presencePort?.dispose();
7305
7629
  this.presencePort = void 0;
7306
- void this.engine.dispatch({ type: "TABLE_DISPOSED", store: this.store, tableKey: this.tableKey });
7630
+ const dispatched = this.settleEditsForTeardown().then(
7631
+ () => this.engine.dispatch({ type: "TABLE_DISPOSED", store: this.store, tableKey: this.tableKey })
7632
+ ).catch((error) => {
7633
+ this.audit.log({
7634
+ type: "ERROR",
7635
+ message: "UITableController.dispose: TABLE_DISPOSED failed",
7636
+ payload: { error, tableKey: this.tableKey }
7637
+ });
7638
+ });
7639
+ return dispatched.then(() => this.releaseResources());
7640
+ }
7641
+ /**
7642
+ * Resource-only teardown: stops subscriptions, projections, sync, presence,
7643
+ * plugins, server row model and closes an event store the grid opened.
7644
+ * Does NOT dispatch `TABLE_DISPOSED` — persisted table data is untouched.
7645
+ * Idempotent (same promise on every call); never rejects.
7646
+ */
7647
+ releaseResources() {
7648
+ if (!this.releasePromise) {
7649
+ this.releasePromise = this.runResourceTeardown();
7650
+ }
7651
+ return this.releasePromise;
7652
+ }
7653
+ async runResourceTeardown() {
7654
+ const fail = (what, error) => {
7655
+ this.audit.log({
7656
+ type: "ERROR",
7657
+ message: `UITableController.releaseResources: ${what} failed`,
7658
+ payload: { error, tableKey: this.tableKey }
7659
+ });
7660
+ };
7661
+ await this.settleEditsForTeardown();
7662
+ const presence = this.presencePort;
7663
+ const sync = this.syncPort;
7664
+ this.presencePort = void 0;
7665
+ this.syncPort = void 0;
7666
+ try {
7667
+ presence?.dispose();
7668
+ } catch (error) {
7669
+ fail("presence dispose", error);
7670
+ }
7671
+ try {
7672
+ sync?.dispose?.();
7673
+ } catch (error) {
7674
+ fail("sync dispose", error);
7675
+ }
7676
+ try {
7677
+ await this.resourceTeardown?.();
7678
+ } catch (error) {
7679
+ fail("teardown", error);
7680
+ }
7307
7681
  }
7308
7682
  // ─── main dispatch ───────────────────────────────────────────────────
7309
7683
  async dispatch(command) {
@@ -7327,13 +7701,13 @@ var UITableController = class {
7327
7701
  this.changePage(command.page);
7328
7702
  return;
7329
7703
  case "UI_CELL_CHANGE":
7330
- this.onCellChange(command.rowId, command.columnId, command.value);
7704
+ await this.onCellChange(command.rowId, command.columnId, command.value);
7331
7705
  return;
7332
7706
  case "UI_DESCRIPTION_SAVE":
7333
7707
  this.onDescriptionSave(command.rowId, command.flag);
7334
7708
  return;
7335
7709
  case "UI_CELL_CHANGE_DISPLAY":
7336
- this.onCellDisplayChange(command.rowId, command.columnId, command.value);
7710
+ await this.onCellDisplayChange(command.rowId, command.columnId, command.value);
7337
7711
  return;
7338
7712
  case "ADD_EMPTY_ROW":
7339
7713
  await this.addEmptyRow();
@@ -7373,13 +7747,13 @@ var UITableController = class {
7373
7747
  shiftKey: Boolean(command.shiftKey),
7374
7748
  ctrlKey: Boolean(command.ctrlKey)
7375
7749
  });
7376
- this.setActiveCell(command.rowId, command.columnId);
7750
+ await this.setActiveCell(command.rowId, command.columnId);
7377
7751
  return;
7378
7752
  case "UI_MOVE_ACTIVE_CELL":
7379
- this.moveToNextCell(command.rowId, command.columnId, command.direction);
7753
+ await this.moveToNextCell(command.rowId, command.columnId, command.direction);
7380
7754
  return;
7381
7755
  case "UI_MOVE_ACTIVE_CELL_VERTICAL":
7382
- this.moveActiveCellVertical(command.rowId, command.columnId, command.direction);
7756
+ await this.moveActiveCellVertical(command.rowId, command.columnId, command.direction);
7383
7757
  return;
7384
7758
  case "UI_ACTIVE_ROW":
7385
7759
  this.setActiveRow(command.rowId);
@@ -7594,6 +7968,7 @@ var PluginHost = class {
7594
7968
  return this.plugins.some((p) => p.name === pluginName);
7595
7969
  }
7596
7970
  async requestExport(options) {
7971
+ await this.deps.beforeRead?.();
7597
7972
  const bus = this.createBus();
7598
7973
  const correlationId = options.correlationId ?? createEventId();
7599
7974
  const pluginName = options.format === "pdf" ? "pdf-export" : options.format === "csv" ? "csv-export" : "excel-export";
@@ -8079,6 +8454,40 @@ var ConsoleAuditLogger = class {
8079
8454
  }
8080
8455
  };
8081
8456
 
8457
+ // infrastructure/eventStore/trackedEventStore.ts
8458
+ function trackEventStoreSubscriptions(inner) {
8459
+ const open = /* @__PURE__ */ new Set();
8460
+ let released = false;
8461
+ const track = (subscribe) => {
8462
+ if (released) return () => void 0;
8463
+ const raw = subscribe();
8464
+ let done = false;
8465
+ const unsub = () => {
8466
+ if (done) return;
8467
+ done = true;
8468
+ open.delete(unsub);
8469
+ raw();
8470
+ };
8471
+ open.add(unsub);
8472
+ return unsub;
8473
+ };
8474
+ const store = {
8475
+ append: (streamId, events, expectedVersion) => inner.append(streamId, events, expectedVersion),
8476
+ read: (streamId, fromVersion) => inner.read(streamId, fromVersion),
8477
+ readAll: (fromPosition) => inner.readAll(fromPosition),
8478
+ subscribe: (streamId, onEvent) => track(() => inner.subscribe(streamId, onEvent)),
8479
+ subscribeAll: (onEvent) => track(() => inner.subscribeAll(onEvent))
8480
+ };
8481
+ return {
8482
+ store,
8483
+ release() {
8484
+ released = true;
8485
+ for (const unsub of [...open]) unsub();
8486
+ open.clear();
8487
+ }
8488
+ };
8489
+ }
8490
+
8082
8491
  // infrastructure/history/HistoryPanelViewModel.ts
8083
8492
  var HISTORY_CLASSIFICATIONS = {
8084
8493
  CELL_CHANGED: { category: "data", severity: "info" },
@@ -8290,6 +8699,8 @@ var ChangelogProjection = class {
8290
8699
  this.subscribers = /* @__PURE__ */ new Set();
8291
8700
  this.seenEventIds = /* @__PURE__ */ new Set();
8292
8701
  this.started = false;
8702
+ /** Incremented by start() and stop(); invalidates an in-flight catch-up. */
8703
+ this.run = 0;
8293
8704
  }
8294
8705
  /**
8295
8706
  * Catch up via readAll, then subscribe for live events.
@@ -8303,17 +8714,25 @@ var ChangelogProjection = class {
8303
8714
  this.seenEventIds.add(event.eventId);
8304
8715
  this.project(event);
8305
8716
  };
8717
+ const run2 = ++this.run;
8306
8718
  this.unsubscribe = this.eventStore.subscribeAll(projectOnce);
8307
8719
  const past = await this.eventStore.readAll(0);
8720
+ if (run2 !== this.run) return;
8308
8721
  for (const event of past) {
8309
8722
  projectOnce(event);
8310
8723
  }
8311
8724
  }
8312
8725
  stop() {
8726
+ this.run += 1;
8313
8727
  this.unsubscribe?.();
8314
8728
  this.unsubscribe = null;
8315
8729
  this.started = false;
8316
8730
  }
8731
+ /** Stop and drop all subscribers (grid teardown). Entries stay readable. */
8732
+ dispose() {
8733
+ this.stop();
8734
+ this.subscribers.clear();
8735
+ }
8317
8736
  query(filter = {}) {
8318
8737
  let result = this.entries;
8319
8738
  if (filter.tableId) {
@@ -8895,6 +9314,33 @@ function isCellEditorTarget(target) {
8895
9314
  if (!editor) return false;
8896
9315
  return Boolean(editor.closest('[role="gridcell"], [data-erp-cell-textarea]'));
8897
9316
  }
9317
+ var CELL_SELECTOR = '[role="gridcell"], [data-erp-cell-textarea]';
9318
+ function isGridCellTarget(target) {
9319
+ if (!target || !(target instanceof Element)) return false;
9320
+ if (target.closest('[role="menu"], [role="dialog"], .erp-table-modal')) {
9321
+ return false;
9322
+ }
9323
+ return Boolean(target.closest(CELL_SELECTOR));
9324
+ }
9325
+ var TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
9326
+ "",
9327
+ "text",
9328
+ "search",
9329
+ "number",
9330
+ "tel",
9331
+ "url",
9332
+ "email",
9333
+ "password"
9334
+ ]);
9335
+ function isTextEditorTarget(target) {
9336
+ if (!isCellEditorTarget(target)) return false;
9337
+ const el = target;
9338
+ if (el.isContentEditable || el.getAttribute("contenteditable") === "true") return true;
9339
+ const tag = el.tagName;
9340
+ if (tag === "TEXTAREA") return true;
9341
+ if (tag !== "INPUT") return false;
9342
+ return TEXT_INPUT_TYPES.has((el.getAttribute("type") ?? "").toLowerCase());
9343
+ }
8898
9344
  function closestGridRoot(target) {
8899
9345
  if (!target || !(target instanceof Element)) return null;
8900
9346
  return target.closest('[role="grid"], [role="treegrid"]');
@@ -8949,6 +9395,13 @@ var UITableKeyboardHandler = class {
8949
9395
  setPasteCells(handler) {
8950
9396
  this.pasteCells = handler;
8951
9397
  }
9398
+ /** More than one cell selected (Shift+Arrow / drag): clipboard keys act on the grid. */
9399
+ hasMultiCellRange() {
9400
+ const ranges = this.engine.getCellRanges();
9401
+ if (ranges.length > 1) return true;
9402
+ const range = ranges[0];
9403
+ return Boolean(range && (range.startRow !== range.endRow || range.colIds.length > 1));
9404
+ }
8952
9405
  async handle(e, ctx2) {
8953
9406
  try {
8954
9407
  await this.handleInternal(e, ctx2);
@@ -8968,6 +9421,8 @@ var UITableKeyboardHandler = class {
8968
9421
  const { activeColumnId } = ctx2;
8969
9422
  const selectedRows = this.engine.getSelectedRows();
8970
9423
  const activeRowId = ctx2.activeRowId ?? this.engine.getActiveRow() ?? (selectedRows ? Number(Object.keys(selectedRows).find((id) => selectedRows[Number(id)])) || null : null);
9424
+ const inTextEditor = isTextEditorTarget(e.target);
9425
+ const clipboardToEditor = inTextEditor && !this.hasMultiCellRange();
8971
9426
  switch (code) {
8972
9427
  case "KeyN": {
8973
9428
  if (altKey) {
@@ -8999,7 +9454,7 @@ var UITableKeyboardHandler = class {
8999
9454
  });
9000
9455
  return;
9001
9456
  }
9002
- if (!ctrlKey && !shiftKey && !altKey && activeRowId && activeColumnId) {
9457
+ if (!ctrlKey && !shiftKey && !altKey && !inTextEditor && activeRowId && activeColumnId) {
9003
9458
  e.preventDefault();
9004
9459
  await this.dispatchIntent({
9005
9460
  type: "JUMP_ACTIVE_CELL_ROW_EDGE",
@@ -9033,7 +9488,7 @@ var UITableKeyboardHandler = class {
9033
9488
  });
9034
9489
  return;
9035
9490
  }
9036
- if (!ctrlKey && !shiftKey && !altKey && activeRowId && activeColumnId) {
9491
+ if (!ctrlKey && !shiftKey && !altKey && !inTextEditor && activeRowId && activeColumnId) {
9037
9492
  e.preventDefault();
9038
9493
  await this.dispatchIntent({
9039
9494
  type: "JUMP_ACTIVE_CELL_ROW_EDGE",
@@ -9061,28 +9516,28 @@ var UITableKeyboardHandler = class {
9061
9516
  type: "ACTIVATE_FIRST_CELL",
9062
9517
  rowId: activeRowId
9063
9518
  });
9064
- } else if (this.engine.getCellEdit()) {
9519
+ } else if (!this.engine.getCellEdit() && !this.engine.getCellEditOptions().singleClickEdit) {
9520
+ this.engine.startCellEdit(activeRowId, activeColumnId);
9521
+ } else if (this.engine.getCellEditOptions().enterNavigation === "down") {
9065
9522
  await this.dispatchIntent({
9066
9523
  type: "MOVE_ACTIVE_CELL_VERTICAL",
9067
9524
  rowId: activeRowId,
9068
9525
  columnId: activeColumnId,
9069
9526
  direction: shiftKey ? "up" : "down"
9070
9527
  });
9071
- } else if (!this.engine.getCellEditOptions().singleClickEdit) {
9072
- this.engine.startCellEdit(activeRowId, activeColumnId);
9073
9528
  } else {
9074
9529
  await this.dispatchIntent({
9075
- type: "MOVE_ACTIVE_CELL_VERTICAL",
9530
+ type: "MOVE_ACTIVE_CELL",
9076
9531
  rowId: activeRowId,
9077
9532
  columnId: activeColumnId,
9078
- direction: shiftKey ? "up" : "down"
9533
+ direction: shiftKey ? "prev" : "next"
9079
9534
  });
9080
9535
  }
9081
9536
  }
9082
9537
  return;
9083
9538
  }
9084
9539
  case "Tab": {
9085
- if (!isCellEditorTarget(e.target)) {
9540
+ if (!isGridCellTarget(e.target)) {
9086
9541
  return;
9087
9542
  }
9088
9543
  if (activeRowId && activeColumnId) {
@@ -9272,7 +9727,7 @@ var UITableKeyboardHandler = class {
9272
9727
  return;
9273
9728
  }
9274
9729
  case "KeyA": {
9275
- if (ctrlKey) {
9730
+ if (ctrlKey && !inTextEditor) {
9276
9731
  e.preventDefault();
9277
9732
  await this.dispatchIntent({ type: "SELECT_ALL_CELLS" });
9278
9733
  }
@@ -9280,6 +9735,7 @@ var UITableKeyboardHandler = class {
9280
9735
  }
9281
9736
  case "KeyC": {
9282
9737
  if (ctrlKey && !altKey) {
9738
+ if (clipboardToEditor) return;
9283
9739
  e.preventDefault();
9284
9740
  if (this.copyCells) {
9285
9741
  await this.copyCells();
@@ -9294,6 +9750,7 @@ var UITableKeyboardHandler = class {
9294
9750
  }
9295
9751
  case "KeyX": {
9296
9752
  if (ctrlKey && !altKey) {
9753
+ if (clipboardToEditor) return;
9297
9754
  e.preventDefault();
9298
9755
  if (this.cutCells) {
9299
9756
  await this.cutCells();
@@ -9308,6 +9765,7 @@ var UITableKeyboardHandler = class {
9308
9765
  }
9309
9766
  case "KeyV": {
9310
9767
  if (ctrlKey && !altKey) {
9768
+ if (clipboardToEditor) return;
9311
9769
  e.preventDefault();
9312
9770
  if (this.pasteCells) {
9313
9771
  await this.pasteCells();
@@ -9358,6 +9816,7 @@ var UITableKeyboardHandler = class {
9358
9816
  return;
9359
9817
  }
9360
9818
  case "KeyZ": {
9819
+ if (inTextEditor) return;
9361
9820
  if (ctrlKey && !shiftKey) {
9362
9821
  e.preventDefault();
9363
9822
  await this.dispatchIntent({ type: "UNDO" });
@@ -9370,7 +9829,7 @@ var UITableKeyboardHandler = class {
9370
9829
  return;
9371
9830
  }
9372
9831
  case "KeyY": {
9373
- if (ctrlKey) {
9832
+ if (ctrlKey && !inTextEditor) {
9374
9833
  e.preventDefault();
9375
9834
  await this.dispatchIntent({ type: "REDO" });
9376
9835
  }
@@ -11048,14 +11507,14 @@ function createPermissionPolicy(config, columnMeta, table, repo, audit) {
11048
11507
  };
11049
11508
  }
11050
11509
  function createTableAppService(repo, audit, permissionPolicy, { columnMeta, remoteApi }, eventStore) {
11051
- const changeCellHandler = new chunkDXRCYW42_cjs.ChangeCellValueHandler(repo, audit, permissionPolicy, { columnMeta, remoteApi });
11052
- const applyDynamicProjection = new chunkDXRCYW42_cjs.ApplyDynamicProjectionHandler(repo, audit, permissionPolicy, { columnMeta, remoteApi });
11510
+ const changeCellHandler = new chunk52LFOPXD_cjs.ChangeCellValueHandler(repo, audit, permissionPolicy, { columnMeta, remoteApi });
11511
+ const applyDynamicProjection = new chunk52LFOPXD_cjs.ApplyDynamicProjectionHandler(repo, audit, permissionPolicy, { columnMeta, remoteApi });
11053
11512
  let rowVisibilityHandler;
11054
- const applyRowVisibility = () => rowVisibilityHandler ?? (rowVisibilityHandler = import('./applyRowVisibilityHandler-LXCOGESZ.cjs').then(
11513
+ const applyRowVisibility = () => rowVisibilityHandler ?? (rowVisibilityHandler = import('./applyRowVisibilityHandler-Q64ZFC7A.cjs').then(
11055
11514
  (m) => new m.ApplyRowVisibilityHandler(repo, audit, permissionPolicy, { columnMeta, remoteApi })
11056
11515
  ));
11057
- const changeCellDisplayHandler = new chunkDXRCYW42_cjs.ChangeCellDisplayValueHandler(repo, audit, permissionPolicy);
11058
- const saveDescriptionHandler = new chunkDXRCYW42_cjs.SaveDescriptionHandler(repo, audit, permissionPolicy);
11516
+ const changeCellDisplayHandler = new chunk52LFOPXD_cjs.ChangeCellDisplayValueHandler(repo, audit, permissionPolicy);
11517
+ const saveDescriptionHandler = new chunk52LFOPXD_cjs.SaveDescriptionHandler(repo, audit, permissionPolicy);
11059
11518
  const addNewRowHandler = new AddNewRowCommandHandler(repo, audit, permissionPolicy);
11060
11519
  const addNewRowsHandler = new AddNewRowsCommandHandler(repo, audit, permissionPolicy);
11061
11520
  const duplicateRowHandler = new DuplicateRowCommandHandler(repo, audit, permissionPolicy);
@@ -11108,14 +11567,39 @@ function createTableAppService(repo, audit, permissionPolicy, { columnMeta, remo
11108
11567
 
11109
11568
  // bootstrap/index.ts
11110
11569
  async function resolveEventStore(config, persist) {
11111
- if (config.eventStore) return config.eventStore;
11112
- if (config.persistence === "paged-idb") return new InMemoryEventStore();
11570
+ if (config.eventStore) return { eventStore: config.eventStore };
11571
+ if (config.persistence === "paged-idb") {
11572
+ return { eventStore: new InMemoryEventStore() };
11573
+ }
11113
11574
  if (persist && typeof indexedDB !== "undefined") {
11114
- return IndexedDbEventStore.open(
11575
+ const opened = await IndexedDbEventStore.open(
11115
11576
  eventStoreDbName(config.store, config.eventStoreName)
11116
11577
  );
11578
+ return { eventStore: opened, close: () => opened.close() };
11117
11579
  }
11118
- return new InMemoryEventStore();
11580
+ return { eventStore: new InMemoryEventStore() };
11581
+ }
11582
+ function createDisposerRegistry() {
11583
+ const disposers = [];
11584
+ let running;
11585
+ return {
11586
+ add(disposer) {
11587
+ disposers.push(disposer);
11588
+ },
11589
+ run() {
11590
+ if (!running) {
11591
+ running = (async () => {
11592
+ for (const disposer of disposers.splice(0).reverse()) {
11593
+ try {
11594
+ await disposer();
11595
+ } catch {
11596
+ }
11597
+ }
11598
+ })();
11599
+ }
11600
+ return running;
11601
+ }
11602
+ };
11119
11603
  }
11120
11604
  function createInitialRows(collectionColumns) {
11121
11605
  const rows = [];
@@ -11147,6 +11631,15 @@ function resolveHostRows(config, columns) {
11147
11631
  );
11148
11632
  }
11149
11633
  async function assembleGrid(config) {
11634
+ const registry = createDisposerRegistry();
11635
+ try {
11636
+ return await assembleGridInner(config, registry);
11637
+ } catch (error) {
11638
+ await registry.run();
11639
+ throw error;
11640
+ }
11641
+ }
11642
+ async function assembleGridInner(config, registry) {
11150
11643
  const { store, tableKey, actor, persist } = resolveCreateGridDefaults(config);
11151
11644
  const resolved = {
11152
11645
  ...config,
@@ -11162,8 +11655,14 @@ async function assembleGrid(config) {
11162
11655
  const windowed = isWindowedRepository(repo);
11163
11656
  const audit = config.audit ?? new ConsoleAuditLogger();
11164
11657
  const remoteApi = config.remoteApi ?? new NoopRemoteApi();
11165
- const eventStore = await resolveEventStore(resolved, persist);
11166
- const changelogProjection = new ChangelogProjection(eventStore);
11658
+ const resolvedStore = await resolveEventStore(resolved, persist);
11659
+ const { eventStore } = resolvedStore;
11660
+ if (resolvedStore.close) registry.add(resolvedStore.close);
11661
+ const tracked = trackEventStoreSubscriptions(eventStore);
11662
+ registry.add(() => tracked.release());
11663
+ const internalStore = tracked.store;
11664
+ const changelogProjection = new ChangelogProjection(internalStore);
11665
+ registry.add(() => changelogProjection.dispose());
11167
11666
  const historyPanelViewModel = new HistoryPanelViewModel(changelogProjection);
11168
11667
  const cellUndoStack = new CellUndoStack();
11169
11668
  await changelogProjection.start();
@@ -11172,11 +11671,12 @@ async function assembleGrid(config) {
11172
11671
  connectivity: config.connectivity ?? createBrowserConnectivity(),
11173
11672
  watermark: createLocalStorageWatermark(`pdg-sync-watermark:${store}:${tableKey}`)
11174
11673
  });
11175
- await offlineStatus.start(eventStore);
11674
+ registry.add(() => offlineStatus.dispose());
11675
+ await offlineStatus.start(internalStore);
11176
11676
  const engineRef = {};
11177
11677
  const snapshotStrategy = config.snapshotStore ? new SnapshotStrategy(
11178
11678
  config.snapshotStore,
11179
- eventStore,
11679
+ internalStore,
11180
11680
  () => engineRef.current?.getSnapShot("active") ?? {
11181
11681
  rows: []
11182
11682
  },
@@ -11291,17 +11791,20 @@ async function assembleGrid(config) {
11291
11791
  remoteApi,
11292
11792
  offlineStatus
11293
11793
  );
11794
+ controller.setResourceTeardown(() => registry.run());
11294
11795
  let syncPort;
11295
11796
  if (config.syncTransport) {
11296
- const { GridSyncService } = await import('./GridSyncService-TAPAHABV.cjs');
11797
+ const { GridSyncService } = await import('./GridSyncService-3JQLEY65.cjs');
11297
11798
  syncPort = new GridSyncService({
11298
11799
  tableId: tableKey,
11299
- eventStore,
11800
+ eventStore: internalStore,
11300
11801
  offlineStatus,
11301
11802
  transport: config.syncTransport,
11302
11803
  policy: config.syncConflictPolicy,
11303
11804
  applyCellValue: (rowId, columnId, value) => controller.applySyncedCellValue(rowId, columnId, value)
11304
11805
  });
11806
+ const syncService = syncPort;
11807
+ registry.add(() => syncService.dispose?.());
11305
11808
  controller.setSyncPort(syncPort);
11306
11809
  }
11307
11810
  let presencePort;
@@ -11316,6 +11819,8 @@ async function assembleGrid(config) {
11316
11819
  label: config.presenceLabel ?? actor.userId,
11317
11820
  transport: presenceTransport
11318
11821
  });
11822
+ const presenceService = presencePort;
11823
+ registry.add(() => presenceService.dispose());
11319
11824
  controller.setPresencePort(presencePort);
11320
11825
  }
11321
11826
  const { definitions: columnDefinitions, bindings: columnBindings } = mapColumnSchemas(hostColumns);
@@ -11333,7 +11838,11 @@ async function assembleGrid(config) {
11333
11838
  engine.setCellEditOptions({
11334
11839
  ...typeof config.singleClickEdit === "boolean" ? { singleClickEdit: config.singleClickEdit } : {},
11335
11840
  ...typeof config.stopEditingWhenCellsLoseFocus === "boolean" ? { stopEditingWhenCellsLoseFocus: config.stopEditingWhenCellsLoseFocus } : {},
11336
- ...config.editType === "cell" || config.editType === "fullRow" ? { editType: config.editType } : {}
11841
+ ...config.editType === "cell" || config.editType === "fullRow" ? { editType: config.editType } : {},
11842
+ // Page-window (document) grids keep the arianERP write-per-change
11843
+ // contract; the event-sourced grid avoids one EventStore write per key.
11844
+ commit: config.cellEditCommit === "immediate" || config.cellEditCommit === "deferred" ? config.cellEditCommit : pagedIdb ? "immediate" : "deferred",
11845
+ ...config.enterNavigation === "nextCell" || config.enterNavigation === "down" ? { enterNavigation: config.enterNavigation } : {}
11337
11846
  });
11338
11847
  controller.configureClipboard({
11339
11848
  copyFormattedValue: config.copyFormattedValue,
@@ -11375,6 +11884,7 @@ async function assembleGrid(config) {
11375
11884
  maxBlocksInCache: config.maxBlocksInCache
11376
11885
  });
11377
11886
  refreshServerSide = () => handle.refreshServerSide();
11887
+ registry.add(() => handle.dispose());
11378
11888
  }
11379
11889
  if (snapshotStrategy) {
11380
11890
  await snapshotStrategy.loadWithCatchUp(`table:${tableKey}`);
@@ -11382,10 +11892,12 @@ async function assembleGrid(config) {
11382
11892
  const pluginHost = new PluginHost({
11383
11893
  tableId: tableKey,
11384
11894
  actorId: actor.userId,
11385
- eventStore,
11895
+ eventStore: internalStore,
11386
11896
  session,
11387
- query: createEngineGridQuery(engine)
11897
+ query: createEngineGridQuery(engine),
11898
+ beforeRead: () => engine.flushCellEdit()
11388
11899
  });
11900
+ registry.add(() => pluginHost.dispose());
11389
11901
  if (config.plugins?.length) {
11390
11902
  pluginHost.register(config.plugins);
11391
11903
  }
@@ -11396,14 +11908,15 @@ async function assembleGrid(config) {
11396
11908
  getRowId: config.getRowId,
11397
11909
  refreshServerSide
11398
11910
  });
11399
- await attachColumnStatePersistence({
11911
+ const detachColumnState = await attachColumnStatePersistence({
11400
11912
  api,
11401
11913
  controller,
11402
11914
  tableId: tableKey,
11403
11915
  actorId: actor.userId,
11404
11916
  stateKey: config.stateKey,
11405
- eventStore
11917
+ eventStore: internalStore
11406
11918
  });
11919
+ registry.add(detachColumnState);
11407
11920
  return {
11408
11921
  engine,
11409
11922
  controller,
@@ -11782,5 +12295,5 @@ exports.toColumnViewBinding = toColumnViewBinding;
11782
12295
  exports.toMinorUnits = toMinorUnits;
11783
12296
  exports.trapTabKey = trapTabKey;
11784
12297
  exports.writeSystemClipboard = writeSystemClipboard;
11785
- //# sourceMappingURL=chunk-IIISFUW4.cjs.map
11786
- //# sourceMappingURL=chunk-IIISFUW4.cjs.map
12298
+ //# sourceMappingURL=chunk-WTHTVKVI.cjs.map
12299
+ //# sourceMappingURL=chunk-WTHTVKVI.cjs.map