@lumir-company/editor 0.4.22 → 0.4.25

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.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 getCurrentSize = () => {
2582
- try {
2583
- return editor?.getActiveStyles?.()?.fontSize || "";
2584
- } catch {
2585
- return "";
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 = getCurrentSize();
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
- editor?.addStyles?.({
2624
- fontSize: toFontSizeValue(currentPx + delta)
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
- editor?.addStyles?.({ fontSize: toFontSizeValue(n) });
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(TextSelection.create(tr.doc, insertPos + 4));
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 ? ed.getActiveStyles().fontSize || "" : ""
4686
+ fontSizeInSchema ? readSelectionFontSize(editor) : ""
4588
4687
  );
4589
4688
  useEditorContentOrSelectionChange(() => {
4590
4689
  if (fontSizeInSchema) {
4591
- setCurrentSize(ed.getActiveStyles().fontSize || "");
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
- ed.addStyles({ fontSize: toFontSizeValue(currentPx + delta) });
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
- ed.addStyles({ fontSize: toFontSizeValue(n) });
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,