@lumir-company/editor 0.4.17 → 0.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1478,6 +1478,13 @@ const url = await uploader(imageFile);
1478
1478
 
1479
1479
  ## 변경 로그
1480
1480
 
1481
+ ### v0.4.18
1482
+
1483
+ - **표 열 삭제 버그 수정 (병합 셀 포함)**
1484
+ - 그립 메뉴로 첫 열/행 삭제 시, 그립 클릭으로 ProseMirror 선택이 표 밖으로 빠져 삭제가 무시되던 문제 수정(삭제 대상 표를 포커스 표 기준으로 결정적으로 탐색)
1485
+ - **세로 병합(rowspan) 인접 열**이 있을 때 첫 열이 삭제되지 않던 문제 수정 — 코어 prosemirror-tables가 행의 유일 셀을 지울 때 빈 셀을 남겨(`tableRow+` 스키마) `fixTables`가 원복하던 케이스. 열 삭제를 표 재구성 방식으로 변경해 colspan/rowspan 병합을 안전하게 처리
1486
+ - 병합 셀이 행 축소로 collapse될 때 **행 높이를 보존**(원래 차지하던 행 높이 합을 유지)
1487
+
1481
1488
  ### v0.4.17
1482
1489
 
1483
1490
  - **표 행 높이(세로) 리사이즈**
package/dist/index.js CHANGED
@@ -4858,6 +4858,9 @@ function LumirTableHandlesController() {
4858
4858
  const frozenRef = (0, import_react36.useRef)(false);
4859
4859
  const menuOpenRef = (0, import_react36.useRef)(false);
4860
4860
  const draggingRef = (0, import_react36.useRef)(false);
4861
+ (0, import_react36.useEffect)(() => {
4862
+ editor.__lumirActiveTableId = focused?.block?.id ?? null;
4863
+ }, [editor, focused]);
4861
4864
  const recompute = (0, import_react36.useCallback)(() => {
4862
4865
  if (frozenRef.current) {
4863
4866
  return;
@@ -5400,6 +5403,140 @@ function liftFontSize(blocks) {
5400
5403
  return mapPreservingRef(blocks, (b) => mapBlock(b, "lift"));
5401
5404
  }
5402
5405
 
5406
+ // src/utils/table-delete.ts
5407
+ var import_prosemirror_tables2 = require("prosemirror-tables");
5408
+ function measureRowHeights(view, tablePos) {
5409
+ try {
5410
+ const at = view?.domAtPos?.(tablePos + 1);
5411
+ let el = at?.node;
5412
+ if (el && el.nodeType === 3) el = el.parentElement;
5413
+ const tableEl = el?.closest?.("table") ?? null;
5414
+ const body = tableEl?.tBodies?.[0];
5415
+ if (!body) return null;
5416
+ return Array.from(body.rows).map(
5417
+ (tr) => Math.round(tr.getBoundingClientRect().height)
5418
+ );
5419
+ } catch {
5420
+ return null;
5421
+ }
5422
+ }
5423
+ function buildDeleteColumnTr(state, tablePos, col, rowPx) {
5424
+ const table = state.doc.nodeAt(tablePos);
5425
+ if (!table || table.type.name !== "table") return null;
5426
+ const map = import_prosemirror_tables2.TableMap.get(table);
5427
+ const W = map.width;
5428
+ const H = map.height;
5429
+ if (col < 0 || col >= W || W <= 1) return null;
5430
+ const cells = [];
5431
+ const seen = /* @__PURE__ */ new Set();
5432
+ for (let r = 0; r < H; r++) {
5433
+ for (let c = 0; c < W; c++) {
5434
+ const pos = map.map[r * W + c];
5435
+ if (seen.has(pos)) continue;
5436
+ seen.add(pos);
5437
+ const node = table.nodeAt(pos);
5438
+ if (!node) continue;
5439
+ cells.push({
5440
+ node,
5441
+ top: r,
5442
+ left: c,
5443
+ rowspan: node.attrs.rowspan ?? 1,
5444
+ colspan: node.attrs.colspan ?? 1
5445
+ });
5446
+ }
5447
+ }
5448
+ const spansCol = (c) => c.left <= col && col < c.left + c.colspan;
5449
+ const isDropped = (c) => spansCol(c) && c.colspan === 1;
5450
+ const survivingByRow = new Array(H).fill(0);
5451
+ for (const c of cells) if (!isDropped(c)) survivingByRow[c.top]++;
5452
+ const rowDies = survivingByRow.map((n) => n === 0);
5453
+ const newRowIndex = [];
5454
+ let ni = 0;
5455
+ for (let r = 0; r < H; r++) newRowIndex[r] = rowDies[r] ? -1 : ni++;
5456
+ const newH = ni;
5457
+ if (newH === 0) return null;
5458
+ const newRows = Array.from({ length: newH }, () => []);
5459
+ for (const c of cells) {
5460
+ if (isDropped(c)) continue;
5461
+ const newColspan = c.colspan - (spansCol(c) ? 1 : 0);
5462
+ let dyingWithin = 0;
5463
+ for (let r = c.top; r < c.top + c.rowspan; r++) if (rowDies[r]) dyingWithin++;
5464
+ const newRowspan = Math.max(1, c.rowspan - dyingWithin);
5465
+ const attrs = { ...c.node.attrs, colspan: newColspan, rowspan: newRowspan };
5466
+ if (spansCol(c) && Array.isArray(attrs.colwidth) && attrs.colwidth.length) {
5467
+ const cw = attrs.colwidth.slice();
5468
+ cw.splice(col - c.left, 1);
5469
+ attrs.colwidth = cw.some((w) => w > 0) ? cw : null;
5470
+ }
5471
+ if (dyingWithin > 0 && rowPx) {
5472
+ let sum = 0;
5473
+ let ok = true;
5474
+ for (let r = c.top; r < c.top + c.rowspan; r++) {
5475
+ if (typeof rowPx[r] !== "number") {
5476
+ ok = false;
5477
+ break;
5478
+ }
5479
+ sum += rowPx[r];
5480
+ }
5481
+ if (ok && sum > 0) attrs.rowHeight = Math.round(sum);
5482
+ }
5483
+ const target = newRowIndex[c.top];
5484
+ if (target >= 0) newRows[target].push(c.node.type.create(attrs, c.node.content));
5485
+ }
5486
+ const rowType = table.child(0)?.type;
5487
+ if (!rowType) return null;
5488
+ const rowNodes = newRows.map((rowCells) => rowType.create(null, rowCells));
5489
+ const newTable = table.type.create(table.attrs, rowNodes);
5490
+ const tr = state.tr;
5491
+ tr.replaceWith(tablePos, tablePos + table.nodeSize, newTable);
5492
+ return tr.docChanged ? tr : null;
5493
+ }
5494
+ function removeFocusedRowOrColumn(editor, index, direction) {
5495
+ const tiptap = editor?._tiptapEditor;
5496
+ if (!tiptap) return false;
5497
+ const { state } = tiptap;
5498
+ let tablePos = -1;
5499
+ const activeId = editor?.__lumirActiveTableId;
5500
+ if (activeId) {
5501
+ const p = findTableNodePos(tiptap, activeId);
5502
+ if (p >= 0 && state.doc.nodeAt(p)?.type.name === "table") tablePos = p;
5503
+ }
5504
+ if (tablePos < 0) {
5505
+ const $from = state.selection.$from;
5506
+ for (let d = $from.depth; d > 0; d--) {
5507
+ if ($from.node(d).type.name === "table") {
5508
+ tablePos = $from.before(d);
5509
+ break;
5510
+ }
5511
+ }
5512
+ }
5513
+ if (tablePos < 0) {
5514
+ const vp = editor?.tableHandles?.view?.tablePos;
5515
+ if (typeof vp === "number" && state.doc.nodeAt(vp)?.type.name === "table") {
5516
+ tablePos = vp;
5517
+ }
5518
+ }
5519
+ if (tablePos < 0) return false;
5520
+ try {
5521
+ if (direction === "column") {
5522
+ const rowPx = measureRowHeights(tiptap.view, tablePos);
5523
+ const tr = buildDeleteColumnTr(state, tablePos, index, rowPx);
5524
+ if (!tr) return false;
5525
+ tiptap.view.dispatch(tr);
5526
+ return true;
5527
+ }
5528
+ const tableInside = state.doc.resolve(tablePos + 1);
5529
+ const rowStart = state.doc.resolve(tableInside.posAtIndex(index) + 1);
5530
+ const cellPos = state.doc.resolve(rowStart.posAtIndex(0));
5531
+ const selState = state.apply(
5532
+ state.tr.setSelection(new import_prosemirror_tables2.CellSelection(cellPos))
5533
+ );
5534
+ return (0, import_prosemirror_tables2.deleteRow)(selState, (tr) => tiptap.view.dispatch(tr));
5535
+ } catch {
5536
+ return false;
5537
+ }
5538
+ }
5539
+
5403
5540
  // src/utils/excel-paste.ts
5404
5541
  var NAMED_COLORS = {
5405
5542
  black: "#000000",
@@ -6253,6 +6390,18 @@ function LumirEditor({
6253
6390
  editor.isEditable = editable;
6254
6391
  }
6255
6392
  }, [editor, editable]);
6393
+ (0, import_react37.useEffect)(() => {
6394
+ if (!editor) return;
6395
+ const th = editor.tableHandles;
6396
+ if (!th || th.__lumirColDelPatched || typeof th.removeRowOrColumn !== "function")
6397
+ return;
6398
+ const orig = th.removeRowOrColumn.bind(th);
6399
+ th.removeRowOrColumn = (index, dir) => {
6400
+ if (removeFocusedRowOrColumn(editor, index, dir)) return;
6401
+ return orig(index, dir);
6402
+ };
6403
+ th.__lumirColDelPatched = true;
6404
+ }, [editor]);
6256
6405
  (0, import_react37.useEffect)(() => {
6257
6406
  if (!editor || !floatingMenu) return;
6258
6407
  const ft = editor.formattingToolbar;