@lumir-company/editor 0.4.17 → 0.4.19

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
@@ -1261,6 +1261,7 @@ interface LumirEditorProps {
1261
1261
  tableHandles?: boolean; // 테이블 핸들 표시 (기본: true)
1262
1262
  floatingMenu?: boolean; // 상단 고정 플로팅 메뉴 표시 (기본: false)
1263
1263
  floatingMenuPosition?: "sticky" | "fixed"; // 플로팅 메뉴 위치 (기본: "sticky")
1264
+ columnDivider?: boolean; // 2단(다단) 컬럼 사이 중앙 세로 구분선 표시 (기본: false)
1264
1265
  className?: string; // 컨테이너 CSS 클래스
1265
1266
 
1266
1267
  // === 링크 프리뷰 설정 ===
@@ -1478,6 +1479,25 @@ const url = await uploader(imageFile);
1478
1479
 
1479
1480
  ## 변경 로그
1480
1481
 
1482
+ ### v0.4.19
1483
+
1484
+ - **2단 컬럼 중앙 세로 구분선 (`columnDivider` 옵션)** *(신규)*
1485
+ - `columnDivider` prop(기본 `false`)으로 2단(다단) 컬럼 사이 중앙에 세로 구분선 표시
1486
+ - 구분선 양쪽에 드래그 핸들(grip) 너비만큼 여백을 둬 핸들과 겹치지 않음
1487
+ - 색·여백을 CSS 변수로 조절: `--lumir-column-divider-color`(기본 `#e5e7eb`), `--lumir-column-grip-space`(기본 28px)
1488
+ - **표 전체 종횡비 고정 스케일 (코너 드래그)** *(신규)*
1489
+ - 표 우하단 모서리 hover 시 대각 리사이즈 커서 → 드래그로 표 전체를 종횡비 고정 균일 배율로 확대/축소
1490
+ - 모든 열 너비(`colwidth`)·행 높이(`rowHeight`)에 동일 배율 적용 → 행·열 상대 비율과 표 종횡비 유지
1491
+ - 셀 focus 없이 표 hover만으로 동작(코어 hover 상태 기반), 기존 행/열 개별 리사이즈와 충돌 없음
1492
+ - `tableHandles` prop으로 게이트(행/열 리사이즈와 동일)
1493
+
1494
+ ### v0.4.18
1495
+
1496
+ - **표 열 삭제 버그 수정 (병합 셀 포함)**
1497
+ - 그립 메뉴로 첫 열/행 삭제 시, 그립 클릭으로 ProseMirror 선택이 표 밖으로 빠져 삭제가 무시되던 문제 수정(삭제 대상 표를 포커스 표 기준으로 결정적으로 탐색)
1498
+ - **세로 병합(rowspan) 인접 열**이 있을 때 첫 열이 삭제되지 않던 문제 수정 — 코어 prosemirror-tables가 행의 유일 셀을 지울 때 빈 셀을 남겨(`tableRow+` 스키마) `fixTables`가 원복하던 케이스. 열 삭제를 표 재구성 방식으로 변경해 colspan/rowspan 병합을 안전하게 처리
1499
+ - 병합 셀이 행 축소로 collapse될 때 **행 높이를 보존**(원래 차지하던 행 높이 합을 유지)
1500
+
1481
1501
  ### v0.4.17
1482
1502
 
1483
1503
  - **표 행 높이(세로) 리사이즈**
package/dist/index.d.mts CHANGED
@@ -119,6 +119,7 @@ interface LumirEditorProps {
119
119
  onSelectionChange?: () => void;
120
120
  className?: string;
121
121
  sideMenuAddButton?: boolean;
122
+ columnDivider?: boolean;
122
123
  floatingMenu?: boolean;
123
124
  floatingMenuPosition?: "sticky" | "fixed";
124
125
  linkPreview?: {
@@ -255,7 +256,7 @@ declare class EditorConfig {
255
256
  */
256
257
  static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean, allowFile?: boolean): string[];
257
258
  }
258
- declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, maxImageFileSize, maxVideoFileSize, linkPreview, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, placeholder, sideMenuAddButton, floatingMenu, floatingMenuPosition, onContentChange, onError, onImageDelete, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
259
+ declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, maxImageFileSize, maxVideoFileSize, linkPreview, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, placeholder, sideMenuAddButton, columnDivider, floatingMenu, floatingMenuPosition, onContentChange, onError, onImageDelete, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
259
260
 
260
261
  declare function cn(...inputs: (string | undefined | null | false)[]): string;
261
262
 
package/dist/index.d.ts CHANGED
@@ -119,6 +119,7 @@ interface LumirEditorProps {
119
119
  onSelectionChange?: () => void;
120
120
  className?: string;
121
121
  sideMenuAddButton?: boolean;
122
+ columnDivider?: boolean;
122
123
  floatingMenu?: boolean;
123
124
  floatingMenuPosition?: "sticky" | "fixed";
124
125
  linkPreview?: {
@@ -255,7 +256,7 @@ declare class EditorConfig {
255
256
  */
256
257
  static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean, allowFile?: boolean): string[];
257
258
  }
258
- declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, maxImageFileSize, maxVideoFileSize, linkPreview, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, placeholder, sideMenuAddButton, floatingMenu, floatingMenuPosition, onContentChange, onError, onImageDelete, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
259
+ declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, maxImageFileSize, maxVideoFileSize, linkPreview, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, placeholder, sideMenuAddButton, columnDivider, floatingMenu, floatingMenuPosition, onContentChange, onError, onImageDelete, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
259
260
 
260
261
  declare function cn(...inputs: (string | undefined | null | false)[]): string;
261
262
 
package/dist/index.js CHANGED
@@ -3436,6 +3436,8 @@ var ALLOWED_VIDEO_EXTENSIONS = [
3436
3436
  ];
3437
3437
  var ROW_RESIZE_MIN_HEIGHT = 24;
3438
3438
  var ROW_RESIZE_HANDLE_WIDTH = 5;
3439
+ var TABLE_SCALE_MIN_COL_WIDTH = 24;
3440
+ var TABLE_SCALE_MAX = 6;
3439
3441
 
3440
3442
  // src/extensions/rowResizing.ts
3441
3443
  var rowResizingPluginKey = new import_prosemirror_state3.PluginKey(
@@ -3714,9 +3716,165 @@ function handleDecorations(state, pluginState) {
3714
3716
  return import_prosemirror_view2.DecorationSet.create(state.doc, decorations);
3715
3717
  }
3716
3718
 
3717
- // src/extensions/tableCellAttrPreserve.ts
3719
+ // src/extensions/tableScaling.ts
3718
3720
  var import_prosemirror_state4 = require("prosemirror-state");
3719
- var tableCellAttrPreserveKey = new import_prosemirror_state4.PluginKey(
3721
+ var import_prosemirror_view3 = require("prosemirror-view");
3722
+ var import_prosemirror_tables2 = require("prosemirror-tables");
3723
+ var tableScalingPluginKey = new import_prosemirror_state4.PluginKey(
3724
+ "lumirTableScaling"
3725
+ );
3726
+ function tableScaling() {
3727
+ return new import_prosemirror_state4.Plugin({
3728
+ key: tableScalingPluginKey,
3729
+ state: {
3730
+ init: () => null,
3731
+ apply(tr, prev) {
3732
+ const meta = tr.getMeta(tableScalingPluginKey);
3733
+ if (meta !== void 0) return meta.preview;
3734
+ if (prev && tr.docChanged) {
3735
+ return { ...prev, tablePos: tr.mapping.map(prev.tablePos, -1) };
3736
+ }
3737
+ return prev;
3738
+ }
3739
+ },
3740
+ props: {
3741
+ decorations(state) {
3742
+ const p = tableScalingPluginKey.getState(state);
3743
+ return p ? buildHeightDecorations(state, p) : null;
3744
+ }
3745
+ },
3746
+ view: (view) => ({
3747
+ update: () => {
3748
+ const p = tableScalingPluginKey.getState(view.state);
3749
+ if (p) applyColgroupPreview(view, p);
3750
+ }
3751
+ })
3752
+ });
3753
+ }
3754
+ function setTableScalePreview(view, preview) {
3755
+ view.dispatch(view.state.tr.setMeta(tableScalingPluginKey, { preview }));
3756
+ }
3757
+ function measureTableForScale(view, tablePos) {
3758
+ const tableEl = findTableEl(view.nodeDOM(tablePos));
3759
+ const node = view.state.doc.nodeAt(tablePos);
3760
+ if (!tableEl || !node || node.type.name !== "table") return null;
3761
+ const map = import_prosemirror_tables2.TableMap.get(node);
3762
+ const rect = tableEl.getBoundingClientRect();
3763
+ const body = tableEl.tBodies[0];
3764
+ const rowHeights = body ? Array.from(body.rows).map((tr) => tr.getBoundingClientRect().height) : [];
3765
+ const colWidths = measureColWidths(tableEl, map.width);
3766
+ if (rowHeights.length !== map.height || colWidths.length !== map.width) {
3767
+ return null;
3768
+ }
3769
+ return {
3770
+ tablePos,
3771
+ colWidths,
3772
+ rowHeights,
3773
+ origW: rect.width,
3774
+ origH: rect.height,
3775
+ scale: 1
3776
+ };
3777
+ }
3778
+ function commitTableScale(view, preview) {
3779
+ const { state } = view;
3780
+ const { tablePos, colWidths, rowHeights, scale } = preview;
3781
+ const table = state.doc.nodeAt(tablePos);
3782
+ if (!table || table.type.name !== "table") return;
3783
+ const map = import_prosemirror_tables2.TableMap.get(table);
3784
+ const start = tablePos + 1;
3785
+ const tr = state.tr;
3786
+ const seen = /* @__PURE__ */ new Set();
3787
+ for (const relPos of map.map) {
3788
+ if (seen.has(relPos)) continue;
3789
+ seen.add(relPos);
3790
+ const node = table.nodeAt(relPos);
3791
+ if (!node) continue;
3792
+ const rect = map.findCell(relPos);
3793
+ const colwidth = [];
3794
+ for (let c = rect.left; c < rect.right; c++) {
3795
+ colwidth.push(Math.round((colWidths[c] ?? 0) * scale));
3796
+ }
3797
+ let h = 0;
3798
+ for (let r = rect.top; r < rect.bottom; r++) h += rowHeights[r] ?? 0;
3799
+ const rowHeight = Math.round(h * scale);
3800
+ tr.setNodeMarkup(start + relPos, void 0, {
3801
+ ...node.attrs,
3802
+ colwidth: colwidth.some((w) => w > 0) ? colwidth : null,
3803
+ rowHeight: rowHeight > 0 ? rowHeight : null
3804
+ });
3805
+ }
3806
+ if (tr.docChanged) view.dispatch(tr);
3807
+ }
3808
+ function measureColWidths(tableEl, width) {
3809
+ const widths = new Array(width).fill(0);
3810
+ const colgroup = tableEl.querySelector("colgroup");
3811
+ if (colgroup && colgroup.children.length === width) {
3812
+ let allSet = true;
3813
+ for (let i = 0; i < width; i++) {
3814
+ const w = parseFloat(colgroup.children[i].style.width);
3815
+ if (Number.isFinite(w) && w > 0) widths[i] = w;
3816
+ else allSet = false;
3817
+ }
3818
+ if (allSet) return widths;
3819
+ }
3820
+ const firstRow = tableEl.tBodies[0]?.rows[0];
3821
+ if (firstRow) {
3822
+ let col = 0;
3823
+ for (const cell of Array.from(firstRow.cells)) {
3824
+ const span = cell.colSpan || 1;
3825
+ const w = cell.getBoundingClientRect().width / span;
3826
+ for (let s = 0; s < span && col < width; s++) widths[col++] = w;
3827
+ }
3828
+ }
3829
+ return widths;
3830
+ }
3831
+ function applyColgroupPreview(view, p) {
3832
+ const tableEl = findTableEl(view.nodeDOM(p.tablePos));
3833
+ const colgroup = tableEl?.querySelector("colgroup");
3834
+ if (!colgroup) return;
3835
+ const cols = colgroup.children;
3836
+ for (let i = 0; i < cols.length && i < p.colWidths.length; i++) {
3837
+ cols[i].style.width = Math.round(p.colWidths[i] * p.scale) + "px";
3838
+ }
3839
+ }
3840
+ function buildHeightDecorations(state, p) {
3841
+ const table = state.doc.nodeAt(p.tablePos);
3842
+ if (!table || table.type.name !== "table") return import_prosemirror_view3.DecorationSet.empty;
3843
+ const map = import_prosemirror_tables2.TableMap.get(table);
3844
+ const start = p.tablePos + 1;
3845
+ const decorations = [];
3846
+ const seen = /* @__PURE__ */ new Set();
3847
+ for (const relPos of map.map) {
3848
+ if (seen.has(relPos)) continue;
3849
+ seen.add(relPos);
3850
+ const node = table.nodeAt(relPos);
3851
+ if (!node) continue;
3852
+ const rect = map.findCell(relPos);
3853
+ let h = 0;
3854
+ for (let r = rect.top; r < rect.bottom; r++) h += p.rowHeights[r] ?? 0;
3855
+ const from = start + relPos;
3856
+ const to = from + node.nodeSize;
3857
+ decorations.push(
3858
+ import_prosemirror_view3.Decoration.node(from, to, {
3859
+ class: "lumir-table-scale-dragging",
3860
+ style: `height: ${Math.round(h * p.scale)}px`
3861
+ })
3862
+ );
3863
+ }
3864
+ return import_prosemirror_view3.DecorationSet.create(state.doc, decorations);
3865
+ }
3866
+ function findTableEl(dom) {
3867
+ if (!dom) return null;
3868
+ const el = dom;
3869
+ if (el.nodeName === "TABLE") return el;
3870
+ const inner = el.querySelector?.("table");
3871
+ if (inner) return inner;
3872
+ return el.closest?.("table") ?? null;
3873
+ }
3874
+
3875
+ // src/extensions/tableCellAttrPreserve.ts
3876
+ var import_prosemirror_state5 = require("prosemirror-state");
3877
+ var tableCellAttrPreserveKey = new import_prosemirror_state5.PluginKey(
3720
3878
  "lumirTableCellAttrPreserve"
3721
3879
  );
3722
3880
  var PRESERVED_ATTRS = [
@@ -3773,7 +3931,7 @@ function collectCellAttrs(doc) {
3773
3931
  return result;
3774
3932
  }
3775
3933
  function tableCellAttrPreserve() {
3776
- return new import_prosemirror_state4.Plugin({
3934
+ return new import_prosemirror_state5.Plugin({
3777
3935
  key: tableCellAttrPreserveKey,
3778
3936
  appendTransaction(transactions, oldState, newState) {
3779
3937
  if (!transactions.some((tr2) => tr2.docChanged)) {
@@ -3879,6 +4037,7 @@ var RowHeightExtension = import_core5.Extension.create({
3879
4037
  const plugins = [tableCellAttrPreserve()];
3880
4038
  if (this.options.resizable) {
3881
4039
  plugins.push(rowResizing());
4040
+ plugins.push(tableScaling());
3882
4041
  }
3883
4042
  return plugins;
3884
4043
  }
@@ -3886,11 +4045,11 @@ var RowHeightExtension = import_core5.Extension.create({
3886
4045
 
3887
4046
  // src/extensions/TableAlignmentExtension.ts
3888
4047
  var import_core6 = require("@tiptap/core");
3889
- var import_prosemirror_state5 = require("prosemirror-state");
3890
- var import_prosemirror_view3 = require("prosemirror-view");
3891
- var tableAlignmentDecoKey = new import_prosemirror_state5.PluginKey("lumirTableAlignmentDeco");
4048
+ var import_prosemirror_state6 = require("prosemirror-state");
4049
+ var import_prosemirror_view4 = require("prosemirror-view");
4050
+ var tableAlignmentDecoKey = new import_prosemirror_state6.PluginKey("lumirTableAlignmentDeco");
3892
4051
  function tableAlignmentDecorationPlugin() {
3893
- return new import_prosemirror_state5.Plugin({
4052
+ return new import_prosemirror_state6.Plugin({
3894
4053
  key: tableAlignmentDecoKey,
3895
4054
  props: {
3896
4055
  decorations(state) {
@@ -3900,7 +4059,7 @@ function tableAlignmentDecorationPlugin() {
3900
4059
  const align = node.attrs.tableAlignment;
3901
4060
  if (align && align !== "left") {
3902
4061
  decorations.push(
3903
- import_prosemirror_view3.Decoration.node(pos, pos + node.nodeSize, {
4062
+ import_prosemirror_view4.Decoration.node(pos, pos + node.nodeSize, {
3904
4063
  "data-table-alignment": align
3905
4064
  })
3906
4065
  );
@@ -3909,7 +4068,7 @@ function tableAlignmentDecorationPlugin() {
3909
4068
  }
3910
4069
  return void 0;
3911
4070
  });
3912
- return import_prosemirror_view3.DecorationSet.create(state.doc, decorations);
4071
+ return import_prosemirror_view4.DecorationSet.create(state.doc, decorations);
3913
4072
  }
3914
4073
  }
3915
4074
  });
@@ -3941,7 +4100,7 @@ var TableAlignmentExtension = import_core6.Extension.create({
3941
4100
  });
3942
4101
 
3943
4102
  // src/blocks/columns/insertColumns.ts
3944
- var import_prosemirror_state6 = require("prosemirror-state");
4103
+ var import_prosemirror_state7 = require("prosemirror-state");
3945
4104
  function insertTwoColumns(editor) {
3946
4105
  const tiptap = editor?._tiptapEditor;
3947
4106
  if (!tiptap) {
@@ -3973,7 +4132,7 @@ function insertTwoColumns(editor) {
3973
4132
  try {
3974
4133
  let tr = state.tr.insert(insertPos, list);
3975
4134
  try {
3976
- tr = tr.setSelection(import_prosemirror_state6.TextSelection.create(tr.doc, insertPos + 4));
4135
+ tr = tr.setSelection(import_prosemirror_state7.TextSelection.create(tr.doc, insertPos + 4));
3977
4136
  } catch {
3978
4137
  }
3979
4138
  tiptap.view.dispatch(tr.scrollIntoView());
@@ -4787,9 +4946,14 @@ var import_react33 = require("react");
4787
4946
  function useFocusedCellHandlePositioning(cellEl, tbodyEl, orientation, show) {
4788
4947
  const { refs, floatingStyles, context, update } = (0, import_react32.useFloating)({
4789
4948
  open: show,
4790
- placement: orientation === "row" ? "left" : orientation === "col" ? "top" : "right",
4791
- // col/row: 가장자리 선(zero-size)에, cell: 우측 보더에 14px hit-area 중앙 정렬(-7).
4792
- middleware: [(0, import_react32.offset)(-7)],
4949
+ placement: orientation === "row" ? "left" : orientation === "col" ? "top" : orientation === "corner" ? "bottom-start" : "right",
4950
+ // col/row/cell: 가장자리에 14px hit-area 중앙 정렬(-7).
4951
+ // corner: 18px hit-zone이 표 우하단 모서리에 걸치도록 위/좌로 살짝 당김(모서리 hover 자연).
4952
+ middleware: [
4953
+ (0, import_react32.offset)(
4954
+ orientation === "corner" ? { mainAxis: -6, crossAxis: -6 } : -7
4955
+ )
4956
+ ],
4793
4957
  whileElementsMounted: import_react32.autoUpdate
4794
4958
  });
4795
4959
  const { isMounted, styles } = (0, import_react32.useTransitionStyles)(context);
@@ -4817,6 +4981,9 @@ function useFocusedCellHandlePositioning(cellEl, tbodyEl, orientation, show) {
4817
4981
  if (orientation === "row") {
4818
4982
  return new DOMRect(t.left, c.top, 0, c.height);
4819
4983
  }
4984
+ if (orientation === "corner") {
4985
+ return new DOMRect(t.right, t.bottom, 0, 0);
4986
+ }
4820
4987
  return c;
4821
4988
  }
4822
4989
  });
@@ -4834,6 +5001,33 @@ function useFocusedCellHandlePositioning(cellEl, tbodyEl, orientation, show) {
4834
5001
  [floatingStyles, isMounted, refs.setFloating, styles]
4835
5002
  );
4836
5003
  }
5004
+ function useTableCornerPositioning(referencePosTable, show) {
5005
+ const { refs, floatingStyles, context } = (0, import_react32.useFloating)({
5006
+ open: show,
5007
+ placement: "bottom-start",
5008
+ // 18px hit-zone을 모서리에서 안쪽(위/좌)으로 당겨 표 위에 걸치게 한다.
5009
+ middleware: [(0, import_react32.offset)({ mainAxis: -12, crossAxis: -12 })],
5010
+ whileElementsMounted: import_react32.autoUpdate
5011
+ });
5012
+ const { isMounted, styles } = (0, import_react32.useTransitionStyles)(context);
5013
+ (0, import_react33.useEffect)(() => {
5014
+ if (!referencePosTable) {
5015
+ refs.setReference(null);
5016
+ return;
5017
+ }
5018
+ refs.setReference({
5019
+ getBoundingClientRect: () => new DOMRect(referencePosTable.right, referencePosTable.bottom, 0, 0)
5020
+ });
5021
+ }, [referencePosTable, refs]);
5022
+ return (0, import_react33.useMemo)(
5023
+ () => ({
5024
+ isMounted,
5025
+ ref: refs.setFloating,
5026
+ style: { ...styles, ...floatingStyles }
5027
+ }),
5028
+ [floatingStyles, isMounted, refs.setFloating, styles]
5029
+ );
5030
+ }
4837
5031
 
4838
5032
  // src/components/LumirTableHandlesController.tsx
4839
5033
  var import_jsx_runtime26 = require("react/jsx-runtime");
@@ -4858,6 +5052,9 @@ function LumirTableHandlesController() {
4858
5052
  const frozenRef = (0, import_react36.useRef)(false);
4859
5053
  const menuOpenRef = (0, import_react36.useRef)(false);
4860
5054
  const draggingRef = (0, import_react36.useRef)(false);
5055
+ (0, import_react36.useEffect)(() => {
5056
+ editor.__lumirActiveTableId = focused?.block?.id ?? null;
5057
+ }, [editor, focused]);
4861
5058
  const recompute = (0, import_react36.useCallback)(() => {
4862
5059
  if (frozenRef.current) {
4863
5060
  return;
@@ -4981,6 +5178,10 @@ function LumirTableHandlesController() {
4981
5178
  const onEndExtend = (0, import_react36.useCallback)(() => {
4982
5179
  editor.tableHandles?.unfreezeHandles();
4983
5180
  }, [editor]);
5181
+ const tableCorner = useTableCornerPositioning(
5182
+ coreState?.referencePosTable ?? null,
5183
+ !!coreState?.widgetContainer
5184
+ );
4984
5185
  const menuHandlers = (0, import_react36.useMemo)(() => {
4985
5186
  const mk = (kind) => ({
4986
5187
  freeze: () => {
@@ -5030,6 +5231,48 @@ function LumirTableHandlesController() {
5030
5231
  }, [editor, recompute]);
5031
5232
  const noop = (0, import_react36.useCallback)(() => {
5032
5233
  }, []);
5234
+ const onScaleStart = (0, import_react36.useCallback)(
5235
+ (e) => {
5236
+ e.preventDefault();
5237
+ e.stopPropagation();
5238
+ const view = editor.prosemirrorView;
5239
+ const blockId = coreState?.block?.id;
5240
+ if (!view || !blockId) return;
5241
+ const tablePos = findTableNodePos(editor._tiptapEditor, blockId);
5242
+ if (tablePos < 0) return;
5243
+ const base = measureTableForScale(view, tablePos);
5244
+ if (!base) return;
5245
+ const minScale = Math.max(
5246
+ TABLE_SCALE_MIN_COL_WIDTH / Math.max(1, Math.min(...base.colWidths)),
5247
+ ROW_RESIZE_MIN_HEIGHT / Math.max(1, Math.min(...base.rowHeights))
5248
+ );
5249
+ const startX = e.clientX;
5250
+ const startY = e.clientY;
5251
+ let current = base;
5252
+ editor.tableHandles?.freezeHandles();
5253
+ const onMove = (me) => {
5254
+ if (me.buttons === 0) return onUp();
5255
+ const newW = base.origW + (me.clientX - startX);
5256
+ const newH = base.origH + (me.clientY - startY);
5257
+ const scale = Math.min(
5258
+ TABLE_SCALE_MAX,
5259
+ Math.max(minScale, Math.max(newW / base.origW, newH / base.origH))
5260
+ );
5261
+ current = { ...base, scale };
5262
+ setTableScalePreview(view, current);
5263
+ };
5264
+ const onUp = () => {
5265
+ window.removeEventListener("pointermove", onMove);
5266
+ window.removeEventListener("pointerup", onUp);
5267
+ setTableScalePreview(view, null);
5268
+ commitTableScale(view, current);
5269
+ editor.tableHandles?.unfreezeHandles();
5270
+ };
5271
+ window.addEventListener("pointermove", onMove);
5272
+ window.addEventListener("pointerup", onUp);
5273
+ },
5274
+ [editor, coreState]
5275
+ );
5033
5276
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
5034
5277
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { ref: setMenuContainerRef }),
5035
5278
  th && focused && menuContainerRef && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_react35.FloatingPortal, { root: focused.widgetContainer, children: [
@@ -5145,6 +5388,16 @@ function LumirTableHandlesController() {
5145
5388
  }
5146
5389
  )
5147
5390
  }
5391
+ ),
5392
+ tableCorner.isMounted && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5393
+ "div",
5394
+ {
5395
+ ref: tableCorner.ref,
5396
+ style: tableCorner.style,
5397
+ className: "lumir-tbl-scale-handle",
5398
+ title: "\uD45C \uD06C\uAE30 \uC870\uC808 (\uC885\uD6A1\uBE44 \uACE0\uC815)",
5399
+ onPointerDown: onScaleStart
5400
+ }
5148
5401
  )
5149
5402
  ] })
5150
5403
  ] });
@@ -5400,6 +5653,140 @@ function liftFontSize(blocks) {
5400
5653
  return mapPreservingRef(blocks, (b) => mapBlock(b, "lift"));
5401
5654
  }
5402
5655
 
5656
+ // src/utils/table-delete.ts
5657
+ var import_prosemirror_tables3 = require("prosemirror-tables");
5658
+ function measureRowHeights(view, tablePos) {
5659
+ try {
5660
+ const at = view?.domAtPos?.(tablePos + 1);
5661
+ let el = at?.node;
5662
+ if (el && el.nodeType === 3) el = el.parentElement;
5663
+ const tableEl = el?.closest?.("table") ?? null;
5664
+ const body = tableEl?.tBodies?.[0];
5665
+ if (!body) return null;
5666
+ return Array.from(body.rows).map(
5667
+ (tr) => Math.round(tr.getBoundingClientRect().height)
5668
+ );
5669
+ } catch {
5670
+ return null;
5671
+ }
5672
+ }
5673
+ function buildDeleteColumnTr(state, tablePos, col, rowPx) {
5674
+ const table = state.doc.nodeAt(tablePos);
5675
+ if (!table || table.type.name !== "table") return null;
5676
+ const map = import_prosemirror_tables3.TableMap.get(table);
5677
+ const W = map.width;
5678
+ const H = map.height;
5679
+ if (col < 0 || col >= W || W <= 1) return null;
5680
+ const cells = [];
5681
+ const seen = /* @__PURE__ */ new Set();
5682
+ for (let r = 0; r < H; r++) {
5683
+ for (let c = 0; c < W; c++) {
5684
+ const pos = map.map[r * W + c];
5685
+ if (seen.has(pos)) continue;
5686
+ seen.add(pos);
5687
+ const node = table.nodeAt(pos);
5688
+ if (!node) continue;
5689
+ cells.push({
5690
+ node,
5691
+ top: r,
5692
+ left: c,
5693
+ rowspan: node.attrs.rowspan ?? 1,
5694
+ colspan: node.attrs.colspan ?? 1
5695
+ });
5696
+ }
5697
+ }
5698
+ const spansCol = (c) => c.left <= col && col < c.left + c.colspan;
5699
+ const isDropped = (c) => spansCol(c) && c.colspan === 1;
5700
+ const survivingByRow = new Array(H).fill(0);
5701
+ for (const c of cells) if (!isDropped(c)) survivingByRow[c.top]++;
5702
+ const rowDies = survivingByRow.map((n) => n === 0);
5703
+ const newRowIndex = [];
5704
+ let ni = 0;
5705
+ for (let r = 0; r < H; r++) newRowIndex[r] = rowDies[r] ? -1 : ni++;
5706
+ const newH = ni;
5707
+ if (newH === 0) return null;
5708
+ const newRows = Array.from({ length: newH }, () => []);
5709
+ for (const c of cells) {
5710
+ if (isDropped(c)) continue;
5711
+ const newColspan = c.colspan - (spansCol(c) ? 1 : 0);
5712
+ let dyingWithin = 0;
5713
+ for (let r = c.top; r < c.top + c.rowspan; r++) if (rowDies[r]) dyingWithin++;
5714
+ const newRowspan = Math.max(1, c.rowspan - dyingWithin);
5715
+ const attrs = { ...c.node.attrs, colspan: newColspan, rowspan: newRowspan };
5716
+ if (spansCol(c) && Array.isArray(attrs.colwidth) && attrs.colwidth.length) {
5717
+ const cw = attrs.colwidth.slice();
5718
+ cw.splice(col - c.left, 1);
5719
+ attrs.colwidth = cw.some((w) => w > 0) ? cw : null;
5720
+ }
5721
+ if (dyingWithin > 0 && rowPx) {
5722
+ let sum = 0;
5723
+ let ok = true;
5724
+ for (let r = c.top; r < c.top + c.rowspan; r++) {
5725
+ if (typeof rowPx[r] !== "number") {
5726
+ ok = false;
5727
+ break;
5728
+ }
5729
+ sum += rowPx[r];
5730
+ }
5731
+ if (ok && sum > 0) attrs.rowHeight = Math.round(sum);
5732
+ }
5733
+ const target = newRowIndex[c.top];
5734
+ if (target >= 0) newRows[target].push(c.node.type.create(attrs, c.node.content));
5735
+ }
5736
+ const rowType = table.child(0)?.type;
5737
+ if (!rowType) return null;
5738
+ const rowNodes = newRows.map((rowCells) => rowType.create(null, rowCells));
5739
+ const newTable = table.type.create(table.attrs, rowNodes);
5740
+ const tr = state.tr;
5741
+ tr.replaceWith(tablePos, tablePos + table.nodeSize, newTable);
5742
+ return tr.docChanged ? tr : null;
5743
+ }
5744
+ function removeFocusedRowOrColumn(editor, index, direction) {
5745
+ const tiptap = editor?._tiptapEditor;
5746
+ if (!tiptap) return false;
5747
+ const { state } = tiptap;
5748
+ let tablePos = -1;
5749
+ const activeId = editor?.__lumirActiveTableId;
5750
+ if (activeId) {
5751
+ const p = findTableNodePos(tiptap, activeId);
5752
+ if (p >= 0 && state.doc.nodeAt(p)?.type.name === "table") tablePos = p;
5753
+ }
5754
+ if (tablePos < 0) {
5755
+ const $from = state.selection.$from;
5756
+ for (let d = $from.depth; d > 0; d--) {
5757
+ if ($from.node(d).type.name === "table") {
5758
+ tablePos = $from.before(d);
5759
+ break;
5760
+ }
5761
+ }
5762
+ }
5763
+ if (tablePos < 0) {
5764
+ const vp = editor?.tableHandles?.view?.tablePos;
5765
+ if (typeof vp === "number" && state.doc.nodeAt(vp)?.type.name === "table") {
5766
+ tablePos = vp;
5767
+ }
5768
+ }
5769
+ if (tablePos < 0) return false;
5770
+ try {
5771
+ if (direction === "column") {
5772
+ const rowPx = measureRowHeights(tiptap.view, tablePos);
5773
+ const tr = buildDeleteColumnTr(state, tablePos, index, rowPx);
5774
+ if (!tr) return false;
5775
+ tiptap.view.dispatch(tr);
5776
+ return true;
5777
+ }
5778
+ const tableInside = state.doc.resolve(tablePos + 1);
5779
+ const rowStart = state.doc.resolve(tableInside.posAtIndex(index) + 1);
5780
+ const cellPos = state.doc.resolve(rowStart.posAtIndex(0));
5781
+ const selState = state.apply(
5782
+ state.tr.setSelection(new import_prosemirror_tables3.CellSelection(cellPos))
5783
+ );
5784
+ return (0, import_prosemirror_tables3.deleteRow)(selState, (tr) => tiptap.view.dispatch(tr));
5785
+ } catch {
5786
+ return false;
5787
+ }
5788
+ }
5789
+
5403
5790
  // src/utils/excel-paste.ts
5404
5791
  var NAMED_COLORS = {
5405
5792
  black: "#000000",
@@ -5941,6 +6328,7 @@ function LumirEditor({
5941
6328
  className = "",
5942
6329
  placeholder,
5943
6330
  sideMenuAddButton = false,
6331
+ columnDivider = false,
5944
6332
  floatingMenu = false,
5945
6333
  floatingMenuPosition = "sticky",
5946
6334
  // callbacks / refs
@@ -6253,6 +6641,18 @@ function LumirEditor({
6253
6641
  editor.isEditable = editable;
6254
6642
  }
6255
6643
  }, [editor, editable]);
6644
+ (0, import_react37.useEffect)(() => {
6645
+ if (!editor) return;
6646
+ const th = editor.tableHandles;
6647
+ if (!th || th.__lumirColDelPatched || typeof th.removeRowOrColumn !== "function")
6648
+ return;
6649
+ const orig = th.removeRowOrColumn.bind(th);
6650
+ th.removeRowOrColumn = (index, dir) => {
6651
+ if (removeFocusedRowOrColumn(editor, index, dir)) return;
6652
+ return orig(index, dir);
6653
+ };
6654
+ th.__lumirColDelPatched = true;
6655
+ }, [editor]);
6256
6656
  (0, import_react37.useEffect)(() => {
6257
6657
  if (!editor || !floatingMenu) return;
6258
6658
  const ft = editor.formattingToolbar;
@@ -6443,7 +6843,11 @@ function LumirEditor({
6443
6843
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
6444
6844
  "div",
6445
6845
  {
6446
- className: cn("lumirEditor", className),
6846
+ className: cn(
6847
+ "lumirEditor",
6848
+ columnDivider && "lumir-column-divider",
6849
+ className
6850
+ ),
6447
6851
  style: { position: "relative", display: "flex", flexDirection: "column" },
6448
6852
  children: [
6449
6853
  floatingMenu && editor && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [