@jackuait/blok 0.10.11 → 0.10.12

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.
@@ -1,7 +1,7 @@
1
1
  import type { API } from '../../../types';
2
2
  import { DATA_ATTR } from '../../components/constants/data-attributes';
3
3
 
4
- import { CELL_ATTR, ROW_ATTR, CELL_COL_ATTR } from './table-core';
4
+ import { CELL_ATTR, ROW_ATTR, CELL_ROW_ATTR, CELL_COL_ATTR } from './table-core';
5
5
  import type { TableModel } from './table-model';
6
6
  import type { LegacyCellContent, CellContent } from './types';
7
7
  import { isCellWithBlocks } from './types';
@@ -353,7 +353,9 @@ export class TableCellBlocks {
353
353
  * - Cells that already have block references get those blocks mounted.
354
354
  * - If referenced blocks are missing from BlockManager, a fallback paragraph is created.
355
355
  */
356
- public initializeCells(content: LegacyCellContent[][]): CellContent[][] {
356
+ public initializeCells(
357
+ content: LegacyCellContent[][]
358
+ ): CellContent[][] {
357
359
  const rowElements = this.gridElement.querySelectorAll(`[${ROW_ATTR}]`);
358
360
  const normalizedContent: CellContent[][] = [];
359
361
 
@@ -437,6 +439,63 @@ export class TableCellBlocks {
437
439
  return normalizedContent;
438
440
  }
439
441
 
442
+ /**
443
+ * After a setData/render rebuild, reclaim any blocks the model references
444
+ * whose holders are not yet mounted in their model cell. This catches blocks
445
+ * that were restored via separate Yjs ops in a different transaction order
446
+ * — without it the restored block would float at the top level as an orphan
447
+ * (regression: table-undo-redo-orphans, multi-cell undo restoration).
448
+ */
449
+ public reclaimReferencedBlocks(): void {
450
+ const snapshot = this.model.snapshot();
451
+
452
+ snapshot.content.forEach((row, rowIndex) => {
453
+ row.forEach((cellContent, colIndex) => {
454
+ if (!isCellWithBlocks(cellContent) || cellContent.blocks.length === 0) {
455
+ return;
456
+ }
457
+
458
+ const cell = this.gridElement.querySelector<HTMLElement>(
459
+ `[${CELL_ROW_ATTR}="${rowIndex}"][${CELL_COL_ATTR}="${colIndex}"]`
460
+ );
461
+
462
+ if (!cell) {
463
+ return;
464
+ }
465
+
466
+ const container = cell.querySelector<HTMLElement>(`[${CELL_BLOCKS_ATTR}]`);
467
+
468
+ if (!container) {
469
+ return;
470
+ }
471
+
472
+ for (const blockId of cellContent.blocks) {
473
+ const getIndex = this.api.blocks.getBlockIndex;
474
+ const getByIndex = this.api.blocks.getBlockByIndex;
475
+
476
+ if (typeof getIndex !== 'function' || typeof getByIndex !== 'function') {
477
+ return;
478
+ }
479
+
480
+ const index = getIndex(blockId);
481
+
482
+ if (index === undefined) {
483
+ continue;
484
+ }
485
+ const block = getByIndex(index);
486
+
487
+ if (!block) {
488
+ continue;
489
+ }
490
+ if (container.contains(block.holder)) {
491
+ continue;
492
+ }
493
+ this.claimBlockForCell(cell, blockId);
494
+ }
495
+ });
496
+ });
497
+ }
498
+
440
499
  /**
441
500
  * Remove placeholder attributes from contenteditable elements inside a cell container.
442
501
  * Blocks in table cells should feel like plain table fields, not standalone paragraphs.
@@ -635,7 +694,14 @@ export class TableCellBlocks {
635
694
  * When a block is removed, ensure no cell is left empty.
636
695
  */
637
696
  private handleBlockMutation = (data: unknown): void => {
638
- if (this.isStructuralOpActive()) {
697
+ // While a structural op (setData / paste / row-col change) is rebuilding
698
+ // the table, defer events so they don't operate on stale DOM. EXCEPT for
699
+ // Yjs replay: an undo/redo that restores a previously-owned cell block
700
+ // fires block-added DURING the table's own setData, and discarding it
701
+ // would leave the restored block as a top-level orphan
702
+ // (regression: table-undo-redo-orphans). Process those immediately —
703
+ // recordedCellPos lookup below will route the block back to its cell.
704
+ if (this.isStructuralOpActive() && !this.api.blocks.isSyncingFromYjs) {
639
705
  this.deferredEvents.push(data);
640
706
 
641
707
  return;
@@ -674,6 +740,27 @@ export class TableCellBlocks {
674
740
  return;
675
741
  }
676
742
 
743
+ // Yjs undo replay: a block this table previously owned is being restored.
744
+ // The model's contentGrid still references its id from a prior render but
745
+ // the DOM is empty (we deliberately did not fabricate a replacement, see
746
+ // table-undo-redo-orphans regression). Reattach it to the recorded cell
747
+ // before falling through to adjacency-based heuristics, otherwise the
748
+ // restored block lands as a top-level orphan.
749
+ const recordedCellPos = this.model.findCellForBlock(detail.target.id);
750
+
751
+ if (recordedCellPos) {
752
+ const cellEl = this.gridElement.querySelector<HTMLElement>(
753
+ `[${CELL_ROW_ATTR}="${recordedCellPos.row}"][${CELL_COL_ATTR}="${recordedCellPos.col}"]`
754
+ );
755
+
756
+ if (cellEl) {
757
+ this.claimBlockForCell(cellEl, detail.target.id);
758
+ this.cellsPendingCheck.delete(cellEl);
759
+
760
+ return;
761
+ }
762
+ }
763
+
677
764
  const blockIndex = detail.index;
678
765
 
679
766
  if (blockIndex === undefined) {
@@ -189,9 +189,10 @@ export interface Blocks {
189
189
  *
190
190
  * @param parentId - id of the parent block
191
191
  * @param insertIndex - flat block index where the new block should appear
192
+ * @param childData - optional data override for the child block (default: empty paragraph)
192
193
  * @returns BlockAPI for the newly created child block
193
194
  */
194
- insertInsideParent(parentId: string, insertIndex: number): BlockAPI;
195
+ insertInsideParent(parentId: string, insertIndex: number, childData?: BlockToolData): BlockAPI;
195
196
 
196
197
  /**
197
198
  * Execute a function within a transaction.