@lumir-company/editor 0.4.18 → 0.4.21

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/dist/index.js CHANGED
@@ -48,7 +48,7 @@ module.exports = __toCommonJS(index_exports);
48
48
  var import_react37 = require("react");
49
49
  var import_react38 = require("@blocknote/react");
50
50
  var import_mantine = require("@blocknote/mantine");
51
- var import_core9 = require("@blocknote/core");
51
+ var import_core10 = require("@blocknote/core");
52
52
  var import_locales = require("@blocknote/core/locales");
53
53
 
54
54
  // src/utils/cn.ts
@@ -1490,6 +1490,16 @@ var ColumnList = (0, import_core.createStronglyTypedTiptapNode)({
1490
1490
  name: "columnList",
1491
1491
  group: "childContainer bnBlock blockGroupChild",
1492
1492
  content: "column column+",
1493
+ addAttributes() {
1494
+ return {
1495
+ // 블록별 중앙 세로 구분선 표시 여부(드래그핸들 메뉴에서 토글). data-divider로 렌더.
1496
+ showDivider: {
1497
+ default: false,
1498
+ parseHTML: (element) => element.getAttribute("data-divider") === "true",
1499
+ renderHTML: (attributes) => attributes.showDivider ? { "data-divider": "true" } : {}
1500
+ }
1501
+ };
1502
+ },
1493
1503
  parseHTML() {
1494
1504
  return [{ tag: 'div[data-node-type="columnList"]' }];
1495
1505
  },
@@ -1938,7 +1948,8 @@ var HtmlPreviewBlock = (0, import_react6.createReactBlockSpec)(
1938
1948
  );
1939
1949
  var ColumnListBlock = (0, import_core3.createBlockSpecFromStronglyTypedTiptapNode)(
1940
1950
  ColumnList,
1941
- {}
1951
+ // showDivider를 블록 prop으로 등록 → onContentChange JSON 직렬화 + 재로드 라운드트립.
1952
+ { showDivider: { default: false } }
1942
1953
  );
1943
1954
  var ColumnBlock = (0, import_core3.createBlockSpecFromStronglyTypedTiptapNode)(Column, {});
1944
1955
  var schema = import_core3.BlockNoteSchema.create({
@@ -3436,6 +3447,8 @@ var ALLOWED_VIDEO_EXTENSIONS = [
3436
3447
  ];
3437
3448
  var ROW_RESIZE_MIN_HEIGHT = 24;
3438
3449
  var ROW_RESIZE_HANDLE_WIDTH = 5;
3450
+ var TABLE_SCALE_MIN_COL_WIDTH = 24;
3451
+ var TABLE_SCALE_MAX = 6;
3439
3452
 
3440
3453
  // src/extensions/rowResizing.ts
3441
3454
  var rowResizingPluginKey = new import_prosemirror_state3.PluginKey(
@@ -3714,9 +3727,165 @@ function handleDecorations(state, pluginState) {
3714
3727
  return import_prosemirror_view2.DecorationSet.create(state.doc, decorations);
3715
3728
  }
3716
3729
 
3717
- // src/extensions/tableCellAttrPreserve.ts
3730
+ // src/extensions/tableScaling.ts
3718
3731
  var import_prosemirror_state4 = require("prosemirror-state");
3719
- var tableCellAttrPreserveKey = new import_prosemirror_state4.PluginKey(
3732
+ var import_prosemirror_view3 = require("prosemirror-view");
3733
+ var import_prosemirror_tables2 = require("prosemirror-tables");
3734
+ var tableScalingPluginKey = new import_prosemirror_state4.PluginKey(
3735
+ "lumirTableScaling"
3736
+ );
3737
+ function tableScaling() {
3738
+ return new import_prosemirror_state4.Plugin({
3739
+ key: tableScalingPluginKey,
3740
+ state: {
3741
+ init: () => null,
3742
+ apply(tr, prev) {
3743
+ const meta = tr.getMeta(tableScalingPluginKey);
3744
+ if (meta !== void 0) return meta.preview;
3745
+ if (prev && tr.docChanged) {
3746
+ return { ...prev, tablePos: tr.mapping.map(prev.tablePos, -1) };
3747
+ }
3748
+ return prev;
3749
+ }
3750
+ },
3751
+ props: {
3752
+ decorations(state) {
3753
+ const p = tableScalingPluginKey.getState(state);
3754
+ return p ? buildHeightDecorations(state, p) : null;
3755
+ }
3756
+ },
3757
+ view: (view) => ({
3758
+ update: () => {
3759
+ const p = tableScalingPluginKey.getState(view.state);
3760
+ if (p) applyColgroupPreview(view, p);
3761
+ }
3762
+ })
3763
+ });
3764
+ }
3765
+ function setTableScalePreview(view, preview) {
3766
+ view.dispatch(view.state.tr.setMeta(tableScalingPluginKey, { preview }));
3767
+ }
3768
+ function measureTableForScale(view, tablePos) {
3769
+ const tableEl = findTableEl(view.nodeDOM(tablePos));
3770
+ const node = view.state.doc.nodeAt(tablePos);
3771
+ if (!tableEl || !node || node.type.name !== "table") return null;
3772
+ const map = import_prosemirror_tables2.TableMap.get(node);
3773
+ const rect = tableEl.getBoundingClientRect();
3774
+ const body = tableEl.tBodies[0];
3775
+ const rowHeights = body ? Array.from(body.rows).map((tr) => tr.getBoundingClientRect().height) : [];
3776
+ const colWidths = measureColWidths(tableEl, map.width);
3777
+ if (rowHeights.length !== map.height || colWidths.length !== map.width) {
3778
+ return null;
3779
+ }
3780
+ return {
3781
+ tablePos,
3782
+ colWidths,
3783
+ rowHeights,
3784
+ origW: rect.width,
3785
+ origH: rect.height,
3786
+ scale: 1
3787
+ };
3788
+ }
3789
+ function commitTableScale(view, preview) {
3790
+ const { state } = view;
3791
+ const { tablePos, colWidths, rowHeights, scale } = preview;
3792
+ const table = state.doc.nodeAt(tablePos);
3793
+ if (!table || table.type.name !== "table") return;
3794
+ const map = import_prosemirror_tables2.TableMap.get(table);
3795
+ const start = tablePos + 1;
3796
+ const tr = state.tr;
3797
+ const seen = /* @__PURE__ */ new Set();
3798
+ for (const relPos of map.map) {
3799
+ if (seen.has(relPos)) continue;
3800
+ seen.add(relPos);
3801
+ const node = table.nodeAt(relPos);
3802
+ if (!node) continue;
3803
+ const rect = map.findCell(relPos);
3804
+ const colwidth = [];
3805
+ for (let c = rect.left; c < rect.right; c++) {
3806
+ colwidth.push(Math.round((colWidths[c] ?? 0) * scale));
3807
+ }
3808
+ let h = 0;
3809
+ for (let r = rect.top; r < rect.bottom; r++) h += rowHeights[r] ?? 0;
3810
+ const rowHeight = Math.round(h * scale);
3811
+ tr.setNodeMarkup(start + relPos, void 0, {
3812
+ ...node.attrs,
3813
+ colwidth: colwidth.some((w) => w > 0) ? colwidth : null,
3814
+ rowHeight: rowHeight > 0 ? rowHeight : null
3815
+ });
3816
+ }
3817
+ if (tr.docChanged) view.dispatch(tr);
3818
+ }
3819
+ function measureColWidths(tableEl, width) {
3820
+ const widths = new Array(width).fill(0);
3821
+ const colgroup = tableEl.querySelector("colgroup");
3822
+ if (colgroup && colgroup.children.length === width) {
3823
+ let allSet = true;
3824
+ for (let i = 0; i < width; i++) {
3825
+ const w = parseFloat(colgroup.children[i].style.width);
3826
+ if (Number.isFinite(w) && w > 0) widths[i] = w;
3827
+ else allSet = false;
3828
+ }
3829
+ if (allSet) return widths;
3830
+ }
3831
+ const firstRow = tableEl.tBodies[0]?.rows[0];
3832
+ if (firstRow) {
3833
+ let col = 0;
3834
+ for (const cell of Array.from(firstRow.cells)) {
3835
+ const span = cell.colSpan || 1;
3836
+ const w = cell.getBoundingClientRect().width / span;
3837
+ for (let s = 0; s < span && col < width; s++) widths[col++] = w;
3838
+ }
3839
+ }
3840
+ return widths;
3841
+ }
3842
+ function applyColgroupPreview(view, p) {
3843
+ const tableEl = findTableEl(view.nodeDOM(p.tablePos));
3844
+ const colgroup = tableEl?.querySelector("colgroup");
3845
+ if (!colgroup) return;
3846
+ const cols = colgroup.children;
3847
+ for (let i = 0; i < cols.length && i < p.colWidths.length; i++) {
3848
+ cols[i].style.width = Math.round(p.colWidths[i] * p.scale) + "px";
3849
+ }
3850
+ }
3851
+ function buildHeightDecorations(state, p) {
3852
+ const table = state.doc.nodeAt(p.tablePos);
3853
+ if (!table || table.type.name !== "table") return import_prosemirror_view3.DecorationSet.empty;
3854
+ const map = import_prosemirror_tables2.TableMap.get(table);
3855
+ const start = p.tablePos + 1;
3856
+ const decorations = [];
3857
+ const seen = /* @__PURE__ */ new Set();
3858
+ for (const relPos of map.map) {
3859
+ if (seen.has(relPos)) continue;
3860
+ seen.add(relPos);
3861
+ const node = table.nodeAt(relPos);
3862
+ if (!node) continue;
3863
+ const rect = map.findCell(relPos);
3864
+ let h = 0;
3865
+ for (let r = rect.top; r < rect.bottom; r++) h += p.rowHeights[r] ?? 0;
3866
+ const from = start + relPos;
3867
+ const to = from + node.nodeSize;
3868
+ decorations.push(
3869
+ import_prosemirror_view3.Decoration.node(from, to, {
3870
+ class: "lumir-table-scale-dragging",
3871
+ style: `height: ${Math.round(h * p.scale)}px`
3872
+ })
3873
+ );
3874
+ }
3875
+ return import_prosemirror_view3.DecorationSet.create(state.doc, decorations);
3876
+ }
3877
+ function findTableEl(dom) {
3878
+ if (!dom) return null;
3879
+ const el = dom;
3880
+ if (el.nodeName === "TABLE") return el;
3881
+ const inner = el.querySelector?.("table");
3882
+ if (inner) return inner;
3883
+ return el.closest?.("table") ?? null;
3884
+ }
3885
+
3886
+ // src/extensions/tableCellAttrPreserve.ts
3887
+ var import_prosemirror_state5 = require("prosemirror-state");
3888
+ var tableCellAttrPreserveKey = new import_prosemirror_state5.PluginKey(
3720
3889
  "lumirTableCellAttrPreserve"
3721
3890
  );
3722
3891
  var PRESERVED_ATTRS = [
@@ -3773,7 +3942,7 @@ function collectCellAttrs(doc) {
3773
3942
  return result;
3774
3943
  }
3775
3944
  function tableCellAttrPreserve() {
3776
- return new import_prosemirror_state4.Plugin({
3945
+ return new import_prosemirror_state5.Plugin({
3777
3946
  key: tableCellAttrPreserveKey,
3778
3947
  appendTransaction(transactions, oldState, newState) {
3779
3948
  if (!transactions.some((tr2) => tr2.docChanged)) {
@@ -3879,6 +4048,7 @@ var RowHeightExtension = import_core5.Extension.create({
3879
4048
  const plugins = [tableCellAttrPreserve()];
3880
4049
  if (this.options.resizable) {
3881
4050
  plugins.push(rowResizing());
4051
+ plugins.push(tableScaling());
3882
4052
  }
3883
4053
  return plugins;
3884
4054
  }
@@ -3886,11 +4056,11 @@ var RowHeightExtension = import_core5.Extension.create({
3886
4056
 
3887
4057
  // src/extensions/TableAlignmentExtension.ts
3888
4058
  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");
4059
+ var import_prosemirror_state6 = require("prosemirror-state");
4060
+ var import_prosemirror_view4 = require("prosemirror-view");
4061
+ var tableAlignmentDecoKey = new import_prosemirror_state6.PluginKey("lumirTableAlignmentDeco");
3892
4062
  function tableAlignmentDecorationPlugin() {
3893
- return new import_prosemirror_state5.Plugin({
4063
+ return new import_prosemirror_state6.Plugin({
3894
4064
  key: tableAlignmentDecoKey,
3895
4065
  props: {
3896
4066
  decorations(state) {
@@ -3900,7 +4070,7 @@ function tableAlignmentDecorationPlugin() {
3900
4070
  const align = node.attrs.tableAlignment;
3901
4071
  if (align && align !== "left") {
3902
4072
  decorations.push(
3903
- import_prosemirror_view3.Decoration.node(pos, pos + node.nodeSize, {
4073
+ import_prosemirror_view4.Decoration.node(pos, pos + node.nodeSize, {
3904
4074
  "data-table-alignment": align
3905
4075
  })
3906
4076
  );
@@ -3909,7 +4079,7 @@ function tableAlignmentDecorationPlugin() {
3909
4079
  }
3910
4080
  return void 0;
3911
4081
  });
3912
- return import_prosemirror_view3.DecorationSet.create(state.doc, decorations);
4082
+ return import_prosemirror_view4.DecorationSet.create(state.doc, decorations);
3913
4083
  }
3914
4084
  }
3915
4085
  });
@@ -3940,9 +4110,56 @@ var TableAlignmentExtension = import_core6.Extension.create({
3940
4110
  }
3941
4111
  });
3942
4112
 
4113
+ // src/extensions/TableSelectAllExtension.ts
4114
+ var import_core7 = require("@tiptap/core");
4115
+ var import_prosemirror_tables3 = require("prosemirror-tables");
4116
+ var TableSelectAllExtension = import_core7.Extension.create({
4117
+ name: "lumirTableSelectAll",
4118
+ priority: 1e3,
4119
+ addKeyboardShortcuts() {
4120
+ return {
4121
+ "Mod-a": () => {
4122
+ const view = this.editor.view;
4123
+ const { state } = view;
4124
+ const sel = state.selection;
4125
+ const $from = sel.$from;
4126
+ let depth = -1;
4127
+ for (let d = $from.depth; d > 0; d--) {
4128
+ if ($from.node(d).type.name === "table") {
4129
+ depth = d;
4130
+ break;
4131
+ }
4132
+ }
4133
+ if (depth < 0) return false;
4134
+ const table = $from.node(depth);
4135
+ const tableStart = $from.start(depth);
4136
+ const map = import_prosemirror_tables3.TableMap.get(table);
4137
+ const firstRel = map.map[0];
4138
+ const lastRel = map.map[map.map.length - 1];
4139
+ if (sel instanceof import_prosemirror_tables3.CellSelection) {
4140
+ const a = sel.$anchorCell.pos - tableStart;
4141
+ const h = sel.$headCell.pos - tableStart;
4142
+ if (Math.min(a, h) === firstRel && Math.max(a, h) === lastRel) {
4143
+ return false;
4144
+ }
4145
+ }
4146
+ const tr = state.tr.setSelection(
4147
+ import_prosemirror_tables3.CellSelection.create(
4148
+ state.doc,
4149
+ tableStart + firstRel,
4150
+ tableStart + lastRel
4151
+ )
4152
+ );
4153
+ view.dispatch(tr);
4154
+ return true;
4155
+ }
4156
+ };
4157
+ }
4158
+ });
4159
+
3943
4160
  // src/blocks/columns/insertColumns.ts
3944
- var import_prosemirror_state6 = require("prosemirror-state");
3945
- function insertTwoColumns(editor) {
4161
+ var import_prosemirror_state7 = require("prosemirror-state");
4162
+ function insertTwoColumns(editor, showDivider = false) {
3946
4163
  const tiptap = editor?._tiptapEditor;
3947
4164
  if (!tiptap) {
3948
4165
  return false;
@@ -3969,11 +4186,11 @@ function insertTwoColumns(editor) {
3969
4186
  const insertPos = $from.after(depth);
3970
4187
  const mkBlock = () => blockContainer.create(null, paragraph.create());
3971
4188
  const mkColumn = () => column.create(null, mkBlock());
3972
- const list = columnList.create(null, [mkColumn(), mkColumn()]);
4189
+ const list = columnList.create({ showDivider }, [mkColumn(), mkColumn()]);
3973
4190
  try {
3974
4191
  let tr = state.tr.insert(insertPos, list);
3975
4192
  try {
3976
- tr = tr.setSelection(import_prosemirror_state6.TextSelection.create(tr.doc, insertPos + 4));
4193
+ tr = tr.setSelection(import_prosemirror_state7.TextSelection.create(tr.doc, insertPos + 4));
3977
4194
  } catch {
3978
4195
  }
3979
4196
  tiptap.view.dispatch(tr.scrollIntoView());
@@ -3987,7 +4204,7 @@ function insertTwoColumns(editor) {
3987
4204
  var import_react30 = require("@blocknote/react");
3988
4205
 
3989
4206
  // src/components/TextAlignButtonWithVA.tsx
3990
- var import_core7 = require("@blocknote/core");
4207
+ var import_core8 = require("@blocknote/core");
3991
4208
  var import_react20 = require("react");
3992
4209
  var import_react21 = require("@blocknote/react");
3993
4210
  var import_jsx_runtime19 = require("react/jsx-runtime");
@@ -4009,7 +4226,7 @@ var TextAlignButtonWithVA = (props) => {
4009
4226
  const selectedBlocks = (0, import_react21.useSelectedBlocks)(editor);
4010
4227
  const textAlignment = (0, import_react20.useMemo)(() => {
4011
4228
  const block = selectedBlocks[0];
4012
- if ((0, import_core7.checkBlockHasDefaultProp)("textAlignment", block, editor)) {
4229
+ if ((0, import_core8.checkBlockHasDefaultProp)("textAlignment", block, editor)) {
4013
4230
  return block.props.textAlignment;
4014
4231
  }
4015
4232
  if (block.type === "table") {
@@ -4018,7 +4235,7 @@ var TextAlignButtonWithVA = (props) => {
4018
4235
  return;
4019
4236
  }
4020
4237
  const allCellsInTable = cellSelection.cells.map(
4021
- ({ row, col }) => (0, import_core7.mapTableCell)(
4238
+ ({ row, col }) => (0, import_core8.mapTableCell)(
4022
4239
  block.content.rows[row].cells[col]
4023
4240
  ).props.textAlignment
4024
4241
  );
@@ -4050,7 +4267,7 @@ var TextAlignButtonWithVA = (props) => {
4050
4267
  }
4051
4268
  }
4052
4269
  tiptap.view?.dispatch(tr);
4053
- } else if ((0, import_core7.checkBlockTypeHasDefaultProp)("textAlignment", block.type, editor)) {
4270
+ } else if ((0, import_core8.checkBlockTypeHasDefaultProp)("textAlignment", block.type, editor)) {
4054
4271
  editor.updateBlock(block, {
4055
4272
  props: { textAlignment: newAlignment }
4056
4273
  });
@@ -4323,7 +4540,7 @@ function FontSizeButton2() {
4323
4540
  }
4324
4541
 
4325
4542
  // src/components/color/LumirColorControls.tsx
4326
- var import_core8 = require("@blocknote/core");
4543
+ var import_core9 = require("@blocknote/core");
4327
4544
  var import_react28 = require("@blocknote/react");
4328
4545
  var import_react29 = require("react");
4329
4546
  var import_jsx_runtime23 = require("react/jsx-runtime");
@@ -4571,7 +4788,7 @@ function LumirCellColorPickerButton(props) {
4571
4788
  const updateColor = (color, type) => {
4572
4789
  const newTable = props.block.content.rows.map((row) => ({
4573
4790
  ...row,
4574
- cells: row.cells.map((cell) => (0, import_core8.mapTableCell)(cell))
4791
+ cells: row.cells.map((cell) => (0, import_core9.mapTableCell)(cell))
4575
4792
  }));
4576
4793
  if (type === "text") {
4577
4794
  newTable[props.rowIndex].cells[props.colIndex].props.textColor = color;
@@ -4602,11 +4819,11 @@ function LumirCellColorPickerButton(props) {
4602
4819
  textTitle: "\uC140 \uAE00\uC790\uC0C9",
4603
4820
  backgroundTitle: "\uC140 \uBC30\uACBD",
4604
4821
  text: editor.settings.tables.cellTextColor ? {
4605
- color: (0, import_core8.isTableCell)(currentCell) ? currentCell.props.textColor : "default",
4822
+ color: (0, import_core9.isTableCell)(currentCell) ? currentCell.props.textColor : "default",
4606
4823
  setColor: (color) => updateColor(color, "text")
4607
4824
  } : void 0,
4608
4825
  background: editor.settings.tables.cellBackgroundColor ? {
4609
- color: (0, import_core8.isTableCell)(currentCell) ? currentCell.props.backgroundColor : "default",
4826
+ color: (0, import_core9.isTableCell)(currentCell) ? currentCell.props.backgroundColor : "default",
4610
4827
  setColor: (color) => updateColor(color, "background")
4611
4828
  } : void 0
4612
4829
  }
@@ -4787,9 +5004,14 @@ var import_react33 = require("react");
4787
5004
  function useFocusedCellHandlePositioning(cellEl, tbodyEl, orientation, show) {
4788
5005
  const { refs, floatingStyles, context, update } = (0, import_react32.useFloating)({
4789
5006
  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)],
5007
+ placement: orientation === "row" ? "left" : orientation === "col" ? "top" : orientation === "corner" ? "bottom-start" : "right",
5008
+ // col/row/cell: 가장자리에 14px hit-area 중앙 정렬(-7).
5009
+ // corner: 18px hit-zone이 표 우하단 모서리에 걸치도록 위/좌로 살짝 당김(모서리 hover 자연).
5010
+ middleware: [
5011
+ (0, import_react32.offset)(
5012
+ orientation === "corner" ? { mainAxis: -6, crossAxis: -6 } : -7
5013
+ )
5014
+ ],
4793
5015
  whileElementsMounted: import_react32.autoUpdate
4794
5016
  });
4795
5017
  const { isMounted, styles } = (0, import_react32.useTransitionStyles)(context);
@@ -4817,6 +5039,9 @@ function useFocusedCellHandlePositioning(cellEl, tbodyEl, orientation, show) {
4817
5039
  if (orientation === "row") {
4818
5040
  return new DOMRect(t.left, c.top, 0, c.height);
4819
5041
  }
5042
+ if (orientation === "corner") {
5043
+ return new DOMRect(t.right, t.bottom, 0, 0);
5044
+ }
4820
5045
  return c;
4821
5046
  }
4822
5047
  });
@@ -4834,6 +5059,33 @@ function useFocusedCellHandlePositioning(cellEl, tbodyEl, orientation, show) {
4834
5059
  [floatingStyles, isMounted, refs.setFloating, styles]
4835
5060
  );
4836
5061
  }
5062
+ function useTableCornerPositioning(referencePosTable, show) {
5063
+ const { refs, floatingStyles, context } = (0, import_react32.useFloating)({
5064
+ open: show,
5065
+ placement: "bottom-start",
5066
+ // 18px hit-zone을 모서리에서 안쪽(위/좌)으로 당겨 표 위에 걸치게 한다.
5067
+ middleware: [(0, import_react32.offset)({ mainAxis: -12, crossAxis: -12 })],
5068
+ whileElementsMounted: import_react32.autoUpdate
5069
+ });
5070
+ const { isMounted, styles } = (0, import_react32.useTransitionStyles)(context);
5071
+ (0, import_react33.useEffect)(() => {
5072
+ if (!referencePosTable) {
5073
+ refs.setReference(null);
5074
+ return;
5075
+ }
5076
+ refs.setReference({
5077
+ getBoundingClientRect: () => new DOMRect(referencePosTable.right, referencePosTable.bottom, 0, 0)
5078
+ });
5079
+ }, [referencePosTable, refs]);
5080
+ return (0, import_react33.useMemo)(
5081
+ () => ({
5082
+ isMounted,
5083
+ ref: refs.setFloating,
5084
+ style: { ...styles, ...floatingStyles }
5085
+ }),
5086
+ [floatingStyles, isMounted, refs.setFloating, styles]
5087
+ );
5088
+ }
4837
5089
 
4838
5090
  // src/components/LumirTableHandlesController.tsx
4839
5091
  var import_jsx_runtime26 = require("react/jsx-runtime");
@@ -4984,6 +5236,10 @@ function LumirTableHandlesController() {
4984
5236
  const onEndExtend = (0, import_react36.useCallback)(() => {
4985
5237
  editor.tableHandles?.unfreezeHandles();
4986
5238
  }, [editor]);
5239
+ const tableCorner = useTableCornerPositioning(
5240
+ coreState?.referencePosTable ?? null,
5241
+ !!coreState?.widgetContainer
5242
+ );
4987
5243
  const menuHandlers = (0, import_react36.useMemo)(() => {
4988
5244
  const mk = (kind) => ({
4989
5245
  freeze: () => {
@@ -5033,6 +5289,48 @@ function LumirTableHandlesController() {
5033
5289
  }, [editor, recompute]);
5034
5290
  const noop = (0, import_react36.useCallback)(() => {
5035
5291
  }, []);
5292
+ const onScaleStart = (0, import_react36.useCallback)(
5293
+ (e) => {
5294
+ e.preventDefault();
5295
+ e.stopPropagation();
5296
+ const view = editor.prosemirrorView;
5297
+ const blockId = coreState?.block?.id;
5298
+ if (!view || !blockId) return;
5299
+ const tablePos = findTableNodePos(editor._tiptapEditor, blockId);
5300
+ if (tablePos < 0) return;
5301
+ const base = measureTableForScale(view, tablePos);
5302
+ if (!base) return;
5303
+ const minScale = Math.max(
5304
+ TABLE_SCALE_MIN_COL_WIDTH / Math.max(1, Math.min(...base.colWidths)),
5305
+ ROW_RESIZE_MIN_HEIGHT / Math.max(1, Math.min(...base.rowHeights))
5306
+ );
5307
+ const startX = e.clientX;
5308
+ const startY = e.clientY;
5309
+ let current = base;
5310
+ editor.tableHandles?.freezeHandles();
5311
+ const onMove = (me) => {
5312
+ if (me.buttons === 0) return onUp();
5313
+ const newW = base.origW + (me.clientX - startX);
5314
+ const newH = base.origH + (me.clientY - startY);
5315
+ const scale = Math.min(
5316
+ TABLE_SCALE_MAX,
5317
+ Math.max(minScale, Math.max(newW / base.origW, newH / base.origH))
5318
+ );
5319
+ current = { ...base, scale };
5320
+ setTableScalePreview(view, current);
5321
+ };
5322
+ const onUp = () => {
5323
+ window.removeEventListener("pointermove", onMove);
5324
+ window.removeEventListener("pointerup", onUp);
5325
+ setTableScalePreview(view, null);
5326
+ commitTableScale(view, current);
5327
+ editor.tableHandles?.unfreezeHandles();
5328
+ };
5329
+ window.addEventListener("pointermove", onMove);
5330
+ window.addEventListener("pointerup", onUp);
5331
+ },
5332
+ [editor, coreState]
5333
+ );
5036
5334
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
5037
5335
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { ref: setMenuContainerRef }),
5038
5336
  th && focused && menuContainerRef && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_react35.FloatingPortal, { root: focused.widgetContainer, children: [
@@ -5148,6 +5446,16 @@ function LumirTableHandlesController() {
5148
5446
  }
5149
5447
  )
5150
5448
  }
5449
+ ),
5450
+ tableCorner.isMounted && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
5451
+ "div",
5452
+ {
5453
+ ref: tableCorner.ref,
5454
+ style: tableCorner.style,
5455
+ className: "lumir-tbl-scale-handle",
5456
+ title: "\uD45C \uD06C\uAE30 \uC870\uC808 (\uC885\uD6A1\uBE44 \uACE0\uC815)",
5457
+ onPointerDown: onScaleStart
5458
+ }
5151
5459
  )
5152
5460
  ] })
5153
5461
  ] });
@@ -5404,7 +5712,7 @@ function liftFontSize(blocks) {
5404
5712
  }
5405
5713
 
5406
5714
  // src/utils/table-delete.ts
5407
- var import_prosemirror_tables2 = require("prosemirror-tables");
5715
+ var import_prosemirror_tables4 = require("prosemirror-tables");
5408
5716
  function measureRowHeights(view, tablePos) {
5409
5717
  try {
5410
5718
  const at = view?.domAtPos?.(tablePos + 1);
@@ -5423,7 +5731,7 @@ function measureRowHeights(view, tablePos) {
5423
5731
  function buildDeleteColumnTr(state, tablePos, col, rowPx) {
5424
5732
  const table = state.doc.nodeAt(tablePos);
5425
5733
  if (!table || table.type.name !== "table") return null;
5426
- const map = import_prosemirror_tables2.TableMap.get(table);
5734
+ const map = import_prosemirror_tables4.TableMap.get(table);
5427
5735
  const W = map.width;
5428
5736
  const H = map.height;
5429
5737
  if (col < 0 || col >= W || W <= 1) return null;
@@ -5529,9 +5837,9 @@ function removeFocusedRowOrColumn(editor, index, direction) {
5529
5837
  const rowStart = state.doc.resolve(tableInside.posAtIndex(index) + 1);
5530
5838
  const cellPos = state.doc.resolve(rowStart.posAtIndex(0));
5531
5839
  const selState = state.apply(
5532
- state.tr.setSelection(new import_prosemirror_tables2.CellSelection(cellPos))
5840
+ state.tr.setSelection(new import_prosemirror_tables4.CellSelection(cellPos))
5533
5841
  );
5534
- return (0, import_prosemirror_tables2.deleteRow)(selState, (tr) => tiptap.view.dispatch(tr));
5842
+ return (0, import_prosemirror_tables4.deleteRow)(selState, (tr) => tiptap.view.dispatch(tr));
5535
5843
  } catch {
5536
5844
  return false;
5537
5845
  }
@@ -5681,6 +5989,20 @@ function normalizeAlign(ta) {
5681
5989
  if (v === "justify") return "justify";
5682
5990
  return "";
5683
5991
  }
5992
+ function mapVerticalAlign(v) {
5993
+ const s = (v || "").trim().toLowerCase();
5994
+ if (s === "middle" || s === "center") return "middle";
5995
+ if (s === "bottom") return "bottom";
5996
+ return null;
5997
+ }
5998
+ function fontSizeToPx(raw) {
5999
+ if (!raw) return null;
6000
+ const m = String(raw).trim().match(/^([\d.]+)\s*(px|pt)?$/i);
6001
+ if (!m) return null;
6002
+ const v = parseFloat(m[1]);
6003
+ if (!Number.isFinite(v) || v <= 0) return null;
6004
+ return (m[2] || "px").toLowerCase() === "pt" ? v * (96 / 72) : v;
6005
+ }
5684
6006
  function applyCellFormatting(el, fmt) {
5685
6007
  if (fmt.bgRgb && !fmt.bgTransparent) {
5686
6008
  const v = nearestBackgroundColorValue(fmt.bgRgb);
@@ -5700,6 +6022,13 @@ function applyCellFormatting(el, fmt) {
5700
6022
  if (fmt.bold) inner = `<strong>${inner}</strong>`;
5701
6023
  el.innerHTML = inner;
5702
6024
  }
6025
+ if (fmt.verticalAlign) {
6026
+ el.setAttribute("data-vertical-alignment", fmt.verticalAlign);
6027
+ }
6028
+ if (fmt.fontSizePx && Math.abs(fmt.fontSizePx - 14) > 1 && el.innerHTML.trim()) {
6029
+ const v = `${Math.round(fmt.fontSizePx)}px`;
6030
+ el.innerHTML = `<span data-style-type="fontSize" data-value="${v}" style="font-size:${v}">` + el.innerHTML + `</span>`;
6031
+ }
5703
6032
  }
5704
6033
  function readComputedFormat(el) {
5705
6034
  const cs = getComputedStyle(el);
@@ -5713,7 +6042,13 @@ function readComputedFormat(el) {
5713
6042
  align: normalizeAlign(cs.textAlign),
5714
6043
  bold: fw === "bold" || fw === "bolder" || !isNaN(fwNum) && fwNum >= 600,
5715
6044
  italic: (cs.fontStyle || "").toLowerCase().includes("italic"),
5716
- underline: decoration.toLowerCase().includes("underline")
6045
+ underline: decoration.toLowerCase().includes("underline"),
6046
+ fontSizePx: fontSizeToPx(cs.fontSize),
6047
+ // ⚠️ computed vertical-align은 td 기본값이 "middle"이라 셀마다 잘못 붙는다.
6048
+ // 명시적 inline style / valign 속성만 읽는다(기본값 노이즈 방지).
6049
+ verticalAlign: mapVerticalAlign(
6050
+ el.style?.verticalAlign || el.getAttribute("valign")
6051
+ )
5717
6052
  };
5718
6053
  }
5719
6054
  function readInlineFormat(el) {
@@ -5729,7 +6064,11 @@ function readInlineFormat(el) {
5729
6064
  align: normalizeAlign(sm["text-align"] || el.getAttribute("align")),
5730
6065
  bold: fw === "bold" || fw === "bolder" || parseInt(fw, 10) >= 600,
5731
6066
  italic: (sm["font-style"] || "").toLowerCase().includes("italic"),
5732
- underline: decoration.toLowerCase().includes("underline")
6067
+ underline: decoration.toLowerCase().includes("underline"),
6068
+ fontSizePx: fontSizeToPx(sm["font-size"]),
6069
+ verticalAlign: mapVerticalAlign(
6070
+ sm["vertical-align"] || el.getAttribute("valign")
6071
+ )
5733
6072
  };
5734
6073
  }
5735
6074
  function normalizeExcelTableHtml(html) {
@@ -5743,7 +6082,7 @@ function normalizeExcelTableHtml(html) {
5743
6082
  try {
5744
6083
  host = document.createElement("div");
5745
6084
  host.setAttribute("aria-hidden", "true");
5746
- host.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none";
6085
+ host.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;font-size:14px";
5747
6086
  const shadow = host.attachShadow({ mode: "open" });
5748
6087
  const styles = Array.from(doc.querySelectorAll("style")).map((s) => s.outerHTML).join("");
5749
6088
  shadow.innerHTML = styles + doc.body.innerHTML;
@@ -5766,6 +6105,100 @@ function normalizeExcelTableHtml(html) {
5766
6105
  }
5767
6106
  }
5768
6107
 
6108
+ // src/utils/table-paste-fit.ts
6109
+ var MIN_COL_PX = 24;
6110
+ function toPx(raw, maxWidth) {
6111
+ if (!raw) return null;
6112
+ const m = String(raw).trim().match(/^([\d.]+)\s*(pt|px|%)?$/i);
6113
+ if (!m) return null;
6114
+ const v = parseFloat(m[1]);
6115
+ if (!Number.isFinite(v) || v <= 0) return null;
6116
+ const unit = (m[2] || "px").toLowerCase();
6117
+ if (unit === "pt") return v * (96 / 72);
6118
+ if (unit === "%") return v / 100 * maxWidth;
6119
+ return v;
6120
+ }
6121
+ function elWidthPx(el, maxWidth) {
6122
+ const styleW = el.style?.width;
6123
+ return toPx(styleW, maxWidth) ?? toPx(el.getAttribute("width"), maxWidth);
6124
+ }
6125
+ function readColumnWidths(table, maxWidth) {
6126
+ const colEls = table.querySelector("colgroup")?.querySelectorAll("col");
6127
+ if (colEls && colEls.length > 0) {
6128
+ const widths2 = [];
6129
+ let ok2 = true;
6130
+ colEls.forEach((c) => {
6131
+ const span = parseInt(c.getAttribute("span") || "1", 10) || 1;
6132
+ const w = elWidthPx(c, maxWidth);
6133
+ if (w == null) ok2 = false;
6134
+ for (let i = 0; i < span; i++) widths2.push(w ?? 0);
6135
+ });
6136
+ if (ok2 && widths2.length > 0) return widths2;
6137
+ }
6138
+ const firstRow = table.querySelector("tr");
6139
+ if (!firstRow) return null;
6140
+ const widths = [];
6141
+ let ok = true;
6142
+ Array.from(firstRow.children).forEach((cell) => {
6143
+ if (cell.tagName !== "TD" && cell.tagName !== "TH") return;
6144
+ const span = parseInt(cell.getAttribute("colspan") || "1", 10) || 1;
6145
+ const w = elWidthPx(cell, maxWidth);
6146
+ if (w == null) ok = false;
6147
+ const per = (w ?? 0) / span;
6148
+ for (let i = 0; i < span; i++) widths.push(per);
6149
+ });
6150
+ return ok && widths.length > 0 ? widths : null;
6151
+ }
6152
+ function fitWidths(widths, maxWidth) {
6153
+ const total = widths.reduce((a, b) => a + b, 0);
6154
+ if (total <= 0) return widths;
6155
+ const scale = total > maxWidth ? maxWidth / total : 1;
6156
+ return widths.map((w) => Math.max(MIN_COL_PX, Math.round(w * scale)));
6157
+ }
6158
+ function computeFittedColumnWidthsPerTable(html, maxWidth) {
6159
+ if (!html || typeof DOMParser === "undefined" || !(maxWidth > 0)) return [];
6160
+ let doc;
6161
+ try {
6162
+ doc = new DOMParser().parseFromString(html, "text/html");
6163
+ } catch {
6164
+ return [];
6165
+ }
6166
+ return Array.from(doc.querySelectorAll("table")).map((t) => {
6167
+ const widths = readColumnWidths(t, maxWidth);
6168
+ return widths ? fitWidths(widths, maxWidth) : null;
6169
+ });
6170
+ }
6171
+ function collectTableBlocks(blocks) {
6172
+ const out = [];
6173
+ const walk = (bs) => {
6174
+ for (const b of bs) {
6175
+ if (b?.type === "table") out.push(b);
6176
+ if (b?.children?.length) walk(b.children);
6177
+ }
6178
+ };
6179
+ walk(blocks || []);
6180
+ return out;
6181
+ }
6182
+ function applyFittedWidthsToNewTables(editor, beforeIds, perTable) {
6183
+ if (!editor || perTable.length === 0) return;
6184
+ const newTables = collectTableBlocks(editor.document).filter(
6185
+ (b) => !beforeIds.has(b.id)
6186
+ );
6187
+ newTables.forEach((tb, i) => {
6188
+ const widths = perTable[i];
6189
+ const current = tb?.content?.columnWidths;
6190
+ if (widths && Array.isArray(current) && current.length === widths.length) {
6191
+ try {
6192
+ editor.updateBlock(tb, {
6193
+ type: "table",
6194
+ content: { ...tb.content, columnWidths: widths }
6195
+ });
6196
+ } catch {
6197
+ }
6198
+ }
6199
+ });
6200
+ }
6201
+
5769
6202
  // src/components/LumirEditor.tsx
5770
6203
  var import_jsx_runtime27 = require("react/jsx-runtime");
5771
6204
  var DEBUG_LOG = (loc, msg, data) => {
@@ -6078,6 +6511,7 @@ function LumirEditor({
6078
6511
  className = "",
6079
6512
  placeholder,
6080
6513
  sideMenuAddButton = false,
6514
+ columnDivider = false,
6081
6515
  floatingMenu = false,
6082
6516
  floatingMenuPosition = "sticky",
6083
6517
  // callbacks / refs
@@ -6191,7 +6625,9 @@ function LumirEditor({
6191
6625
  // tableHandles prop으로 게이트(기존 grip 컨트롤러와 동일 게이트).
6192
6626
  RowHeightExtension.configure({ resizable: tableHandles }),
6193
6627
  // 표 블록 정렬(좌/가운데/우) attr.
6194
- TableAlignmentExtension
6628
+ TableAlignmentExtension,
6629
+ // 셀 포커스 시 Ctrl/Cmd+A → 표 전체 선택.
6630
+ TableSelectAllExtension
6195
6631
  ]
6196
6632
  },
6197
6633
  placeholders: placeholder ? { default: placeholder, emptyDocument: placeholder } : void 0,
@@ -6297,7 +6733,14 @@ function LumirEditor({
6297
6733
  hasFiles: !!event?.clipboardData?.files?.length
6298
6734
  });
6299
6735
  event.preventDefault();
6736
+ const pmDom = editor2.prosemirrorView?.dom;
6737
+ const maxWidth = pmDom?.clientWidth ? pmDom.clientWidth - 8 : 0;
6738
+ const fittedWidths = computeFittedColumnWidthsPerTable(pastedHtml, maxWidth);
6739
+ const beforeTableIds = new Set(
6740
+ collectTableBlocks(editor2.document).map((b) => b.id)
6741
+ );
6300
6742
  editor2.pasteHTML(normalizeExcelTableHtml(pastedHtml));
6743
+ applyFittedWidthsToNewTables(editor2, beforeTableIds, fittedWidths);
6301
6744
  return true;
6302
6745
  }
6303
6746
  const fileList = event?.clipboardData?.files ?? null;
@@ -6592,7 +7035,11 @@ function LumirEditor({
6592
7035
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
6593
7036
  "div",
6594
7037
  {
6595
- className: cn("lumirEditor", className),
7038
+ className: cn(
7039
+ "lumirEditor",
7040
+ columnDivider && "lumir-column-divider",
7041
+ className
7042
+ ),
6596
7043
  style: { position: "relative", display: "flex", flexDirection: "column" },
6597
7044
  children: [
6598
7045
  floatingMenu && editor && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
@@ -6789,38 +7236,57 @@ function LumirEditor({
6789
7236
  ),
6790
7237
  subtext: "HTML \uD30C\uC77C\uC744 \uBBF8\uB9AC\uBCF4\uAE30\uB85C \uC0BD\uC785"
6791
7238
  };
7239
+ const columnIcon = (withDivider) => /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
7240
+ "svg",
7241
+ {
7242
+ width: "18",
7243
+ height: "18",
7244
+ viewBox: "0 0 24 24",
7245
+ fill: "none",
7246
+ stroke: "currentColor",
7247
+ strokeWidth: "2",
7248
+ strokeLinecap: "round",
7249
+ strokeLinejoin: "round",
7250
+ children: [
7251
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("rect", { x: "3", y: "4", width: "7", height: "16", rx: "1" }),
7252
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("rect", { x: "14", y: "4", width: "7", height: "16", rx: "1" }),
7253
+ withDivider && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("line", { x1: "12", y1: "3", x2: "12", y2: "21", strokeDasharray: "2 2" })
7254
+ ]
7255
+ }
7256
+ );
6792
7257
  const columnItem = {
6793
7258
  title: "2\uB2E8 \uCEEC\uB7FC",
6794
- onItemClick: () => {
6795
- insertTwoColumns(editor);
6796
- },
7259
+ onItemClick: () => insertTwoColumns(editor, false),
6797
7260
  aliases: ["columns", "column", "2col", "\uB2E8", "\uCEEC\uB7FC", "\uB2E4\uB2E8", "\uBD84\uD560"],
6798
7261
  group: "Basic blocks",
6799
- icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
6800
- "svg",
6801
- {
6802
- width: "18",
6803
- height: "18",
6804
- viewBox: "0 0 24 24",
6805
- fill: "none",
6806
- stroke: "currentColor",
6807
- strokeWidth: "2",
6808
- strokeLinecap: "round",
6809
- strokeLinejoin: "round",
6810
- children: [
6811
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("rect", { x: "3", y: "4", width: "7", height: "16", rx: "1" }),
6812
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("rect", { x: "14", y: "4", width: "7", height: "16", rx: "1" })
6813
- ]
6814
- }
6815
- ),
7262
+ icon: columnIcon(false),
6816
7263
  subtext: "\uBE14\uB85D\uC744 \uC88C\uC6B0 2\uB2E8\uC73C\uB85C \uBC30\uCE58"
6817
7264
  };
6818
- const allItems = [...filtered, htmlPreviewItem, columnItem];
7265
+ const columnDividerItem = {
7266
+ title: "2\uB2E8 \uCEEC\uB7FC (\uAD6C\uBD84\uC120)",
7267
+ onItemClick: () => insertTwoColumns(editor, true),
7268
+ aliases: [
7269
+ "columns divider",
7270
+ "\uAD6C\uBD84\uC120",
7271
+ "\uCEEC\uB7FC \uAD6C\uBD84\uC120",
7272
+ "2\uB2E8 \uAD6C\uBD84\uC120",
7273
+ "divider"
7274
+ ],
7275
+ group: "Basic blocks",
7276
+ icon: columnIcon(true),
7277
+ subtext: "\uAC00\uC6B4\uB370 \uC138\uB85C \uAD6C\uBD84\uC120\uC774 \uC788\uB294 2\uB2E8 \uCEEC\uB7FC"
7278
+ };
7279
+ const allItems = [
7280
+ ...filtered,
7281
+ htmlPreviewItem,
7282
+ columnItem,
7283
+ columnDividerItem
7284
+ ];
6819
7285
  if (linkPreview?.apiEndpoint) {
6820
7286
  allItems.push({
6821
7287
  title: "Link Preview",
6822
7288
  onItemClick: () => {
6823
- (0, import_core9.insertOrUpdateBlock)(editor, {
7289
+ (0, import_core10.insertOrUpdateBlock)(editor, {
6824
7290
  type: "linkPreview",
6825
7291
  props: { url: "" }
6826
7292
  });