@lumir-company/editor 0.4.18 → 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 +13 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +274 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +270 -15
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +41 -0
- package/package.json +1 -1
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,18 @@ 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
|
+
|
|
1481
1494
|
### v0.4.18
|
|
1482
1495
|
|
|
1483
1496
|
- **표 열 삭제 버그 수정 (병합 셀 포함)**
|
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/
|
|
3719
|
+
// src/extensions/tableScaling.ts
|
|
3718
3720
|
var import_prosemirror_state4 = require("prosemirror-state");
|
|
3719
|
-
var
|
|
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
|
|
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
|
|
3890
|
-
var
|
|
3891
|
-
var tableAlignmentDecoKey = new
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
4792
|
-
|
|
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");
|
|
@@ -4984,6 +5178,10 @@ function LumirTableHandlesController() {
|
|
|
4984
5178
|
const onEndExtend = (0, import_react36.useCallback)(() => {
|
|
4985
5179
|
editor.tableHandles?.unfreezeHandles();
|
|
4986
5180
|
}, [editor]);
|
|
5181
|
+
const tableCorner = useTableCornerPositioning(
|
|
5182
|
+
coreState?.referencePosTable ?? null,
|
|
5183
|
+
!!coreState?.widgetContainer
|
|
5184
|
+
);
|
|
4987
5185
|
const menuHandlers = (0, import_react36.useMemo)(() => {
|
|
4988
5186
|
const mk = (kind) => ({
|
|
4989
5187
|
freeze: () => {
|
|
@@ -5033,6 +5231,48 @@ function LumirTableHandlesController() {
|
|
|
5033
5231
|
}, [editor, recompute]);
|
|
5034
5232
|
const noop = (0, import_react36.useCallback)(() => {
|
|
5035
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
|
+
);
|
|
5036
5276
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
|
|
5037
5277
|
/* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { ref: setMenuContainerRef }),
|
|
5038
5278
|
th && focused && menuContainerRef && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_react35.FloatingPortal, { root: focused.widgetContainer, children: [
|
|
@@ -5148,6 +5388,16 @@ function LumirTableHandlesController() {
|
|
|
5148
5388
|
}
|
|
5149
5389
|
)
|
|
5150
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
|
+
}
|
|
5151
5401
|
)
|
|
5152
5402
|
] })
|
|
5153
5403
|
] });
|
|
@@ -5404,7 +5654,7 @@ function liftFontSize(blocks) {
|
|
|
5404
5654
|
}
|
|
5405
5655
|
|
|
5406
5656
|
// src/utils/table-delete.ts
|
|
5407
|
-
var
|
|
5657
|
+
var import_prosemirror_tables3 = require("prosemirror-tables");
|
|
5408
5658
|
function measureRowHeights(view, tablePos) {
|
|
5409
5659
|
try {
|
|
5410
5660
|
const at = view?.domAtPos?.(tablePos + 1);
|
|
@@ -5423,7 +5673,7 @@ function measureRowHeights(view, tablePos) {
|
|
|
5423
5673
|
function buildDeleteColumnTr(state, tablePos, col, rowPx) {
|
|
5424
5674
|
const table = state.doc.nodeAt(tablePos);
|
|
5425
5675
|
if (!table || table.type.name !== "table") return null;
|
|
5426
|
-
const map =
|
|
5676
|
+
const map = import_prosemirror_tables3.TableMap.get(table);
|
|
5427
5677
|
const W = map.width;
|
|
5428
5678
|
const H = map.height;
|
|
5429
5679
|
if (col < 0 || col >= W || W <= 1) return null;
|
|
@@ -5529,9 +5779,9 @@ function removeFocusedRowOrColumn(editor, index, direction) {
|
|
|
5529
5779
|
const rowStart = state.doc.resolve(tableInside.posAtIndex(index) + 1);
|
|
5530
5780
|
const cellPos = state.doc.resolve(rowStart.posAtIndex(0));
|
|
5531
5781
|
const selState = state.apply(
|
|
5532
|
-
state.tr.setSelection(new
|
|
5782
|
+
state.tr.setSelection(new import_prosemirror_tables3.CellSelection(cellPos))
|
|
5533
5783
|
);
|
|
5534
|
-
return (0,
|
|
5784
|
+
return (0, import_prosemirror_tables3.deleteRow)(selState, (tr) => tiptap.view.dispatch(tr));
|
|
5535
5785
|
} catch {
|
|
5536
5786
|
return false;
|
|
5537
5787
|
}
|
|
@@ -6078,6 +6328,7 @@ function LumirEditor({
|
|
|
6078
6328
|
className = "",
|
|
6079
6329
|
placeholder,
|
|
6080
6330
|
sideMenuAddButton = false,
|
|
6331
|
+
columnDivider = false,
|
|
6081
6332
|
floatingMenu = false,
|
|
6082
6333
|
floatingMenuPosition = "sticky",
|
|
6083
6334
|
// callbacks / refs
|
|
@@ -6592,7 +6843,11 @@ function LumirEditor({
|
|
|
6592
6843
|
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
6593
6844
|
"div",
|
|
6594
6845
|
{
|
|
6595
|
-
className: cn(
|
|
6846
|
+
className: cn(
|
|
6847
|
+
"lumirEditor",
|
|
6848
|
+
columnDivider && "lumir-column-divider",
|
|
6849
|
+
className
|
|
6850
|
+
),
|
|
6596
6851
|
style: { position: "relative", display: "flex", flexDirection: "column" },
|
|
6597
6852
|
children: [
|
|
6598
6853
|
floatingMenu && editor && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
|