@lumir-company/editor 0.4.22 → 0.4.23
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 +5 -0
- package/dist/index.js +133 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +123 -18
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +6 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1579,6 +1579,40 @@ function clampFontSizePx(px) {
|
|
|
1579
1579
|
function toFontSizeValue(px) {
|
|
1580
1580
|
return `${clampFontSizePx(px)}px`;
|
|
1581
1581
|
}
|
|
1582
|
+
function readSelectionFontSize(editor) {
|
|
1583
|
+
const ed = editor;
|
|
1584
|
+
const fallback = () => {
|
|
1585
|
+
try {
|
|
1586
|
+
return ed?.getActiveStyles?.().fontSize || "";
|
|
1587
|
+
} catch {
|
|
1588
|
+
return "";
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
try {
|
|
1592
|
+
const tt = ed._tiptapEditor;
|
|
1593
|
+
const state = tt?.state;
|
|
1594
|
+
if (!state) return fallback();
|
|
1595
|
+
const sel = state.selection;
|
|
1596
|
+
if (sel.empty) {
|
|
1597
|
+
const marks = state.storedMarks || sel.$to.marks();
|
|
1598
|
+
const m = marks?.find?.((mk) => mk.type?.name === "fontSize");
|
|
1599
|
+
return m?.attrs?.stringValue || "";
|
|
1600
|
+
}
|
|
1601
|
+
let value = null;
|
|
1602
|
+
let mixed = false;
|
|
1603
|
+
state.doc.nodesBetween(sel.from, sel.to, (node) => {
|
|
1604
|
+
if (mixed || !node.isText) return !mixed;
|
|
1605
|
+
const m = node.marks?.find?.((mk) => mk.type?.name === "fontSize");
|
|
1606
|
+
const v = m?.attrs?.stringValue || "";
|
|
1607
|
+
if (value === null) value = v;
|
|
1608
|
+
else if (value !== v) mixed = true;
|
|
1609
|
+
return !mixed;
|
|
1610
|
+
});
|
|
1611
|
+
return mixed ? "" : value || "";
|
|
1612
|
+
} catch {
|
|
1613
|
+
return fallback();
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1582
1616
|
|
|
1583
1617
|
// src/blocks/HtmlPreview.tsx
|
|
1584
1618
|
import { useState as useState3, useRef as useRef3, useCallback as useCallback3, useEffect as useEffect3 } from "react";
|
|
@@ -2578,14 +2612,16 @@ var toLabel = (size) => size.replace(/px$/, "");
|
|
|
2578
2612
|
var FontSizeButton = ({ editor }) => {
|
|
2579
2613
|
const [isOpen, setIsOpen] = useState5(false);
|
|
2580
2614
|
const dropdownRef = useRef5(null);
|
|
2581
|
-
const
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2615
|
+
const live = readSelectionFontSize(editor);
|
|
2616
|
+
const [optimistic, setOptimistic] = useState5(null);
|
|
2617
|
+
const lastLiveRef = useRef5(live);
|
|
2618
|
+
useEffect5(() => {
|
|
2619
|
+
if (live !== lastLiveRef.current) {
|
|
2620
|
+
lastLiveRef.current = live;
|
|
2621
|
+
setOptimistic(null);
|
|
2586
2622
|
}
|
|
2587
|
-
};
|
|
2588
|
-
const currentSize =
|
|
2623
|
+
}, [live]);
|
|
2624
|
+
const currentSize = optimistic ?? live;
|
|
2589
2625
|
const currentPx = parseFontSizePx(currentSize);
|
|
2590
2626
|
const [inputValue, setInputValue] = useState5(String(currentPx));
|
|
2591
2627
|
useEffect5(() => {
|
|
@@ -2606,8 +2642,10 @@ var FontSizeButton = ({ editor }) => {
|
|
|
2606
2642
|
if (!editor) return;
|
|
2607
2643
|
if (size === "") {
|
|
2608
2644
|
editor.removeStyles?.({ fontSize: "" });
|
|
2645
|
+
setOptimistic(null);
|
|
2609
2646
|
} else {
|
|
2610
2647
|
editor.addStyles?.({ fontSize: size });
|
|
2648
|
+
setOptimistic(size);
|
|
2611
2649
|
}
|
|
2612
2650
|
setIsOpen(false);
|
|
2613
2651
|
setTimeout(() => editor.focus?.());
|
|
@@ -2620,9 +2658,9 @@ var FontSizeButton = ({ editor }) => {
|
|
|
2620
2658
|
const stepBy = useCallback10(
|
|
2621
2659
|
(delta) => {
|
|
2622
2660
|
try {
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2661
|
+
const value = toFontSizeValue(currentPx + delta);
|
|
2662
|
+
editor?.addStyles?.({ fontSize: value });
|
|
2663
|
+
setOptimistic(value);
|
|
2626
2664
|
} catch (err) {
|
|
2627
2665
|
console.error("Font size step failed:", err);
|
|
2628
2666
|
}
|
|
@@ -2633,7 +2671,9 @@ var FontSizeButton = ({ editor }) => {
|
|
|
2633
2671
|
const n = parseInt(inputValue, 10);
|
|
2634
2672
|
if (Number.isFinite(n)) {
|
|
2635
2673
|
try {
|
|
2636
|
-
|
|
2674
|
+
const value = toFontSizeValue(n);
|
|
2675
|
+
editor?.addStyles?.({ fontSize: value });
|
|
2676
|
+
setOptimistic(value);
|
|
2637
2677
|
} catch (err) {
|
|
2638
2678
|
console.error("Font size apply failed:", err);
|
|
2639
2679
|
}
|
|
@@ -4234,8 +4274,67 @@ var TableSelectAllExtension = Extension4.create({
|
|
|
4234
4274
|
}
|
|
4235
4275
|
});
|
|
4236
4276
|
|
|
4277
|
+
// src/extensions/InactiveSelectionExtension.ts
|
|
4278
|
+
import { Extension as Extension5 } from "@tiptap/core";
|
|
4279
|
+
import { Plugin as Plugin7, PluginKey as PluginKey7, TextSelection } from "prosemirror-state";
|
|
4280
|
+
import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
|
|
4281
|
+
var inactiveSelectionKey = new PluginKey7("lumirInactiveSelection");
|
|
4282
|
+
var InactiveSelectionExtension = Extension5.create({
|
|
4283
|
+
name: "lumirInactiveSelection",
|
|
4284
|
+
addProseMirrorPlugins() {
|
|
4285
|
+
return [
|
|
4286
|
+
new Plugin7({
|
|
4287
|
+
key: inactiveSelectionKey,
|
|
4288
|
+
// 플러그인 state = 에디터 포커스 여부
|
|
4289
|
+
state: {
|
|
4290
|
+
init: () => false,
|
|
4291
|
+
apply: (tr, value) => {
|
|
4292
|
+
const meta = tr.getMeta(inactiveSelectionKey);
|
|
4293
|
+
return typeof meta === "boolean" ? meta : value;
|
|
4294
|
+
}
|
|
4295
|
+
},
|
|
4296
|
+
props: {
|
|
4297
|
+
decorations(state) {
|
|
4298
|
+
const hasFocus = inactiveSelectionKey.getState(state);
|
|
4299
|
+
if (hasFocus) {
|
|
4300
|
+
return null;
|
|
4301
|
+
}
|
|
4302
|
+
const { selection } = state;
|
|
4303
|
+
if (selection.empty || !(selection instanceof TextSelection)) {
|
|
4304
|
+
return null;
|
|
4305
|
+
}
|
|
4306
|
+
return DecorationSet5.create(state.doc, [
|
|
4307
|
+
Decoration5.inline(selection.from, selection.to, {
|
|
4308
|
+
class: "bn-inactive-selection"
|
|
4309
|
+
})
|
|
4310
|
+
]);
|
|
4311
|
+
}
|
|
4312
|
+
},
|
|
4313
|
+
view(view) {
|
|
4314
|
+
const sync = () => {
|
|
4315
|
+
const has = view.hasFocus();
|
|
4316
|
+
if (inactiveSelectionKey.getState(view.state) !== has) {
|
|
4317
|
+
view.dispatch(view.state.tr.setMeta(inactiveSelectionKey, has));
|
|
4318
|
+
}
|
|
4319
|
+
};
|
|
4320
|
+
view.dom.addEventListener("focus", sync);
|
|
4321
|
+
view.dom.addEventListener("blur", sync);
|
|
4322
|
+
const raf = requestAnimationFrame(sync);
|
|
4323
|
+
return {
|
|
4324
|
+
destroy() {
|
|
4325
|
+
cancelAnimationFrame(raf);
|
|
4326
|
+
view.dom.removeEventListener("focus", sync);
|
|
4327
|
+
view.dom.removeEventListener("blur", sync);
|
|
4328
|
+
}
|
|
4329
|
+
};
|
|
4330
|
+
}
|
|
4331
|
+
})
|
|
4332
|
+
];
|
|
4333
|
+
}
|
|
4334
|
+
});
|
|
4335
|
+
|
|
4237
4336
|
// src/blocks/columns/insertColumns.ts
|
|
4238
|
-
import { TextSelection } from "prosemirror-state";
|
|
4337
|
+
import { TextSelection as TextSelection2 } from "prosemirror-state";
|
|
4239
4338
|
function insertTwoColumns(editor, showDivider = false) {
|
|
4240
4339
|
const tiptap = editor?._tiptapEditor;
|
|
4241
4340
|
if (!tiptap) {
|
|
@@ -4267,7 +4366,7 @@ function insertTwoColumns(editor, showDivider = false) {
|
|
|
4267
4366
|
try {
|
|
4268
4367
|
let tr = state.tr.insert(insertPos, list);
|
|
4269
4368
|
try {
|
|
4270
|
-
tr = tr.setSelection(
|
|
4369
|
+
tr = tr.setSelection(TextSelection2.create(tr.doc, insertPos + 4));
|
|
4271
4370
|
} catch {
|
|
4272
4371
|
}
|
|
4273
4372
|
tiptap.view.dispatch(tr.scrollIntoView());
|
|
@@ -4584,11 +4683,11 @@ function FontSizeButton2() {
|
|
|
4584
4683
|
const fontSizeInSchema = styleSchema.fontSize?.type === "fontSize" && styleSchema.fontSize?.propSchema === "string";
|
|
4585
4684
|
const selectedBlocks = useSelectedBlocks4(editor);
|
|
4586
4685
|
const [currentSize, setCurrentSize] = useState9(
|
|
4587
|
-
fontSizeInSchema ?
|
|
4686
|
+
fontSizeInSchema ? readSelectionFontSize(editor) : ""
|
|
4588
4687
|
);
|
|
4589
4688
|
useEditorContentOrSelectionChange(() => {
|
|
4590
4689
|
if (fontSizeInSchema) {
|
|
4591
|
-
setCurrentSize(
|
|
4690
|
+
setCurrentSize(readSelectionFontSize(editor));
|
|
4592
4691
|
}
|
|
4593
4692
|
}, editor);
|
|
4594
4693
|
const currentPx = parseFontSizePx(currentSize);
|
|
@@ -4606,7 +4705,9 @@ function FontSizeButton2() {
|
|
|
4606
4705
|
);
|
|
4607
4706
|
const stepBy = useCallback18(
|
|
4608
4707
|
(delta) => {
|
|
4609
|
-
|
|
4708
|
+
const value = toFontSizeValue(currentPx + delta);
|
|
4709
|
+
ed.addStyles({ fontSize: value });
|
|
4710
|
+
setCurrentSize(value);
|
|
4610
4711
|
},
|
|
4611
4712
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4612
4713
|
[currentPx]
|
|
@@ -4614,7 +4715,9 @@ function FontSizeButton2() {
|
|
|
4614
4715
|
const applyInput = useCallback18(() => {
|
|
4615
4716
|
const n = parseInt(inputValue, 10);
|
|
4616
4717
|
if (Number.isFinite(n)) {
|
|
4617
|
-
|
|
4718
|
+
const value = toFontSizeValue(n);
|
|
4719
|
+
ed.addStyles({ fontSize: value });
|
|
4720
|
+
setCurrentSize(value);
|
|
4618
4721
|
} else {
|
|
4619
4722
|
setInputValue(String(currentPx));
|
|
4620
4723
|
}
|
|
@@ -6847,7 +6950,9 @@ function LumirEditor({
|
|
|
6847
6950
|
// 표 블록 정렬(좌/가운데/우) attr.
|
|
6848
6951
|
TableAlignmentExtension,
|
|
6849
6952
|
// 셀 포커스 시 Ctrl/Cmd+A → 표 전체 선택.
|
|
6850
|
-
TableSelectAllExtension
|
|
6953
|
+
TableSelectAllExtension,
|
|
6954
|
+
// blur 상태(툴바 드롭다운 조작 등)에서도 텍스트 선택 하이라이트 유지.
|
|
6955
|
+
InactiveSelectionExtension
|
|
6851
6956
|
]
|
|
6852
6957
|
},
|
|
6853
6958
|
placeholders: placeholder ? { default: placeholder, emptyDocument: placeholder } : void 0,
|