@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 CHANGED
@@ -1479,6 +1479,11 @@ const url = await uploader(imageFile);
1479
1479
 
1480
1480
  ## 변경 로그
1481
1481
 
1482
+ ### v0.4.23 (2026-06-22)
1483
+
1484
+ - **다중 블록(여러 문단/리스트) 선택 시 글자 크기 스테퍼 누적 안 되던 버그 수정**: BlockNote `getActiveStyles()`는 선택 끝(`$to`)의 마크만 읽어 다중 블록 선택에서 빈 값을 돌려줬고, 그 결과 스테퍼 표기가 14에 고정돼 +/− 연속 클릭이 매번 같은 값을 재적용했음. 선택 범위 전체를 스캔하는 `readSelectionFontSize` + 낙관적 갱신으로 해결(연속 증감 누적, 표기 정상 갱신)
1485
+ - **툴바 드롭다운 조작 시 선택 하이라이트가 사라지던 문제 보완**: 텍스트를 범위 선택한 뒤 글자 크기 등 툴바 드롭다운을 열면 에디터가 blur되어 브라우저 네이티브 선택 하이라이트가 사라졌음(적용 자체는 정상). blur 상태에서도 선택 범위에 인라인 데코레이션을 입히는 `InactiveSelectionExtension` 추가로 선택 영역이 계속 보이도록 함(색상 CSS 변수 `--lumir-inactive-selection`)
1486
+
1482
1487
  ### v0.4.22 (2026-06-22)
1483
1488
 
1484
1489
  - **글자 크기 1px 단위 조절** *(신규)*
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ module.exports = __toCommonJS(index_exports);
55
55
  var import_react36 = require("react");
56
56
  var import_react37 = require("@blocknote/react");
57
57
  var import_mantine = require("@blocknote/mantine");
58
- var import_core11 = require("@blocknote/core");
58
+ var import_core12 = require("@blocknote/core");
59
59
  var import_locales = require("@blocknote/core/locales");
60
60
 
61
61
  // src/utils/cn.ts
@@ -1610,6 +1610,40 @@ function clampFontSizePx(px) {
1610
1610
  function toFontSizeValue(px) {
1611
1611
  return `${clampFontSizePx(px)}px`;
1612
1612
  }
1613
+ function readSelectionFontSize(editor) {
1614
+ const ed = editor;
1615
+ const fallback = () => {
1616
+ try {
1617
+ return ed?.getActiveStyles?.().fontSize || "";
1618
+ } catch {
1619
+ return "";
1620
+ }
1621
+ };
1622
+ try {
1623
+ const tt = ed._tiptapEditor;
1624
+ const state = tt?.state;
1625
+ if (!state) return fallback();
1626
+ const sel = state.selection;
1627
+ if (sel.empty) {
1628
+ const marks = state.storedMarks || sel.$to.marks();
1629
+ const m = marks?.find?.((mk) => mk.type?.name === "fontSize");
1630
+ return m?.attrs?.stringValue || "";
1631
+ }
1632
+ let value = null;
1633
+ let mixed = false;
1634
+ state.doc.nodesBetween(sel.from, sel.to, (node) => {
1635
+ if (mixed || !node.isText) return !mixed;
1636
+ const m = node.marks?.find?.((mk) => mk.type?.name === "fontSize");
1637
+ const v = m?.attrs?.stringValue || "";
1638
+ if (value === null) value = v;
1639
+ else if (value !== v) mixed = true;
1640
+ return !mixed;
1641
+ });
1642
+ return mixed ? "" : value || "";
1643
+ } catch {
1644
+ return fallback();
1645
+ }
1646
+ }
1613
1647
 
1614
1648
  // src/blocks/HtmlPreview.tsx
1615
1649
  var import_react6 = require("react");
@@ -2609,14 +2643,16 @@ var toLabel = (size) => size.replace(/px$/, "");
2609
2643
  var FontSizeButton = ({ editor }) => {
2610
2644
  const [isOpen, setIsOpen] = (0, import_react13.useState)(false);
2611
2645
  const dropdownRef = (0, import_react13.useRef)(null);
2612
- const getCurrentSize = () => {
2613
- try {
2614
- return editor?.getActiveStyles?.()?.fontSize || "";
2615
- } catch {
2616
- return "";
2646
+ const live = readSelectionFontSize(editor);
2647
+ const [optimistic, setOptimistic] = (0, import_react13.useState)(null);
2648
+ const lastLiveRef = (0, import_react13.useRef)(live);
2649
+ (0, import_react13.useEffect)(() => {
2650
+ if (live !== lastLiveRef.current) {
2651
+ lastLiveRef.current = live;
2652
+ setOptimistic(null);
2617
2653
  }
2618
- };
2619
- const currentSize = getCurrentSize();
2654
+ }, [live]);
2655
+ const currentSize = optimistic ?? live;
2620
2656
  const currentPx = parseFontSizePx(currentSize);
2621
2657
  const [inputValue, setInputValue] = (0, import_react13.useState)(String(currentPx));
2622
2658
  (0, import_react13.useEffect)(() => {
@@ -2637,8 +2673,10 @@ var FontSizeButton = ({ editor }) => {
2637
2673
  if (!editor) return;
2638
2674
  if (size === "") {
2639
2675
  editor.removeStyles?.({ fontSize: "" });
2676
+ setOptimistic(null);
2640
2677
  } else {
2641
2678
  editor.addStyles?.({ fontSize: size });
2679
+ setOptimistic(size);
2642
2680
  }
2643
2681
  setIsOpen(false);
2644
2682
  setTimeout(() => editor.focus?.());
@@ -2651,9 +2689,9 @@ var FontSizeButton = ({ editor }) => {
2651
2689
  const stepBy = (0, import_react13.useCallback)(
2652
2690
  (delta) => {
2653
2691
  try {
2654
- editor?.addStyles?.({
2655
- fontSize: toFontSizeValue(currentPx + delta)
2656
- });
2692
+ const value = toFontSizeValue(currentPx + delta);
2693
+ editor?.addStyles?.({ fontSize: value });
2694
+ setOptimistic(value);
2657
2695
  } catch (err) {
2658
2696
  console.error("Font size step failed:", err);
2659
2697
  }
@@ -2664,7 +2702,9 @@ var FontSizeButton = ({ editor }) => {
2664
2702
  const n = parseInt(inputValue, 10);
2665
2703
  if (Number.isFinite(n)) {
2666
2704
  try {
2667
- editor?.addStyles?.({ fontSize: toFontSizeValue(n) });
2705
+ const value = toFontSizeValue(n);
2706
+ editor?.addStyles?.({ fontSize: value });
2707
+ setOptimistic(value);
2668
2708
  } catch (err) {
2669
2709
  console.error("Font size apply failed:", err);
2670
2710
  }
@@ -4265,8 +4305,67 @@ var TableSelectAllExtension = import_core8.Extension.create({
4265
4305
  }
4266
4306
  });
4267
4307
 
4268
- // src/blocks/columns/insertColumns.ts
4308
+ // src/extensions/InactiveSelectionExtension.ts
4309
+ var import_core9 = require("@tiptap/core");
4269
4310
  var import_prosemirror_state7 = require("prosemirror-state");
4311
+ var import_prosemirror_view5 = require("prosemirror-view");
4312
+ var inactiveSelectionKey = new import_prosemirror_state7.PluginKey("lumirInactiveSelection");
4313
+ var InactiveSelectionExtension = import_core9.Extension.create({
4314
+ name: "lumirInactiveSelection",
4315
+ addProseMirrorPlugins() {
4316
+ return [
4317
+ new import_prosemirror_state7.Plugin({
4318
+ key: inactiveSelectionKey,
4319
+ // 플러그인 state = 에디터 포커스 여부
4320
+ state: {
4321
+ init: () => false,
4322
+ apply: (tr, value) => {
4323
+ const meta = tr.getMeta(inactiveSelectionKey);
4324
+ return typeof meta === "boolean" ? meta : value;
4325
+ }
4326
+ },
4327
+ props: {
4328
+ decorations(state) {
4329
+ const hasFocus = inactiveSelectionKey.getState(state);
4330
+ if (hasFocus) {
4331
+ return null;
4332
+ }
4333
+ const { selection } = state;
4334
+ if (selection.empty || !(selection instanceof import_prosemirror_state7.TextSelection)) {
4335
+ return null;
4336
+ }
4337
+ return import_prosemirror_view5.DecorationSet.create(state.doc, [
4338
+ import_prosemirror_view5.Decoration.inline(selection.from, selection.to, {
4339
+ class: "bn-inactive-selection"
4340
+ })
4341
+ ]);
4342
+ }
4343
+ },
4344
+ view(view) {
4345
+ const sync = () => {
4346
+ const has = view.hasFocus();
4347
+ if (inactiveSelectionKey.getState(view.state) !== has) {
4348
+ view.dispatch(view.state.tr.setMeta(inactiveSelectionKey, has));
4349
+ }
4350
+ };
4351
+ view.dom.addEventListener("focus", sync);
4352
+ view.dom.addEventListener("blur", sync);
4353
+ const raf = requestAnimationFrame(sync);
4354
+ return {
4355
+ destroy() {
4356
+ cancelAnimationFrame(raf);
4357
+ view.dom.removeEventListener("focus", sync);
4358
+ view.dom.removeEventListener("blur", sync);
4359
+ }
4360
+ };
4361
+ }
4362
+ })
4363
+ ];
4364
+ }
4365
+ });
4366
+
4367
+ // src/blocks/columns/insertColumns.ts
4368
+ var import_prosemirror_state8 = require("prosemirror-state");
4270
4369
  function insertTwoColumns(editor, showDivider = false) {
4271
4370
  const tiptap = editor?._tiptapEditor;
4272
4371
  if (!tiptap) {
@@ -4298,7 +4397,7 @@ function insertTwoColumns(editor, showDivider = false) {
4298
4397
  try {
4299
4398
  let tr = state.tr.insert(insertPos, list);
4300
4399
  try {
4301
- tr = tr.setSelection(import_prosemirror_state7.TextSelection.create(tr.doc, insertPos + 4));
4400
+ tr = tr.setSelection(import_prosemirror_state8.TextSelection.create(tr.doc, insertPos + 4));
4302
4401
  } catch {
4303
4402
  }
4304
4403
  tiptap.view.dispatch(tr.scrollIntoView());
@@ -4312,7 +4411,7 @@ function insertTwoColumns(editor, showDivider = false) {
4312
4411
  var import_react29 = require("@blocknote/react");
4313
4412
 
4314
4413
  // src/components/TextAlignButtonWithVA.tsx
4315
- var import_core9 = require("@blocknote/core");
4414
+ var import_core10 = require("@blocknote/core");
4316
4415
  var import_react19 = require("react");
4317
4416
  var import_react20 = require("@blocknote/react");
4318
4417
  var import_jsx_runtime18 = require("react/jsx-runtime");
@@ -4334,7 +4433,7 @@ var TextAlignButtonWithVA = (props) => {
4334
4433
  const selectedBlocks = (0, import_react20.useSelectedBlocks)(editor);
4335
4434
  const textAlignment = (0, import_react19.useMemo)(() => {
4336
4435
  const block = selectedBlocks[0];
4337
- if ((0, import_core9.checkBlockHasDefaultProp)("textAlignment", block, editor)) {
4436
+ if ((0, import_core10.checkBlockHasDefaultProp)("textAlignment", block, editor)) {
4338
4437
  return block.props.textAlignment;
4339
4438
  }
4340
4439
  if (block.type === "table") {
@@ -4343,7 +4442,7 @@ var TextAlignButtonWithVA = (props) => {
4343
4442
  return;
4344
4443
  }
4345
4444
  const allCellsInTable = cellSelection.cells.map(
4346
- ({ row, col }) => (0, import_core9.mapTableCell)(
4445
+ ({ row, col }) => (0, import_core10.mapTableCell)(
4347
4446
  block.content.rows[row].cells[col]
4348
4447
  ).props.textAlignment
4349
4448
  );
@@ -4375,7 +4474,7 @@ var TextAlignButtonWithVA = (props) => {
4375
4474
  }
4376
4475
  }
4377
4476
  tiptap.view?.dispatch(tr);
4378
- } else if ((0, import_core9.checkBlockTypeHasDefaultProp)("textAlignment", block.type, editor)) {
4477
+ } else if ((0, import_core10.checkBlockTypeHasDefaultProp)("textAlignment", block.type, editor)) {
4379
4478
  editor.updateBlock(block, {
4380
4479
  props: { textAlignment: newAlignment }
4381
4480
  });
@@ -4580,11 +4679,11 @@ function FontSizeButton2() {
4580
4679
  const fontSizeInSchema = styleSchema.fontSize?.type === "fontSize" && styleSchema.fontSize?.propSchema === "string";
4581
4680
  const selectedBlocks = (0, import_react25.useSelectedBlocks)(editor);
4582
4681
  const [currentSize, setCurrentSize] = (0, import_react26.useState)(
4583
- fontSizeInSchema ? ed.getActiveStyles().fontSize || "" : ""
4682
+ fontSizeInSchema ? readSelectionFontSize(editor) : ""
4584
4683
  );
4585
4684
  (0, import_react25.useEditorContentOrSelectionChange)(() => {
4586
4685
  if (fontSizeInSchema) {
4587
- setCurrentSize(ed.getActiveStyles().fontSize || "");
4686
+ setCurrentSize(readSelectionFontSize(editor));
4588
4687
  }
4589
4688
  }, editor);
4590
4689
  const currentPx = parseFontSizePx(currentSize);
@@ -4602,7 +4701,9 @@ function FontSizeButton2() {
4602
4701
  );
4603
4702
  const stepBy = (0, import_react26.useCallback)(
4604
4703
  (delta) => {
4605
- ed.addStyles({ fontSize: toFontSizeValue(currentPx + delta) });
4704
+ const value = toFontSizeValue(currentPx + delta);
4705
+ ed.addStyles({ fontSize: value });
4706
+ setCurrentSize(value);
4606
4707
  },
4607
4708
  // eslint-disable-next-line react-hooks/exhaustive-deps
4608
4709
  [currentPx]
@@ -4610,7 +4711,9 @@ function FontSizeButton2() {
4610
4711
  const applyInput = (0, import_react26.useCallback)(() => {
4611
4712
  const n = parseInt(inputValue, 10);
4612
4713
  if (Number.isFinite(n)) {
4613
- ed.addStyles({ fontSize: toFontSizeValue(n) });
4714
+ const value = toFontSizeValue(n);
4715
+ ed.addStyles({ fontSize: value });
4716
+ setCurrentSize(value);
4614
4717
  } else {
4615
4718
  setInputValue(String(currentPx));
4616
4719
  }
@@ -4724,7 +4827,7 @@ function FontSizeButton2() {
4724
4827
  }
4725
4828
 
4726
4829
  // src/components/color/LumirColorControls.tsx
4727
- var import_core10 = require("@blocknote/core");
4830
+ var import_core11 = require("@blocknote/core");
4728
4831
  var import_react27 = require("@blocknote/react");
4729
4832
  var import_react28 = require("react");
4730
4833
  var import_jsx_runtime22 = require("react/jsx-runtime");
@@ -4972,7 +5075,7 @@ function LumirCellColorPickerButton(props) {
4972
5075
  const updateColor = (color, type) => {
4973
5076
  const newTable = props.block.content.rows.map((row) => ({
4974
5077
  ...row,
4975
- cells: row.cells.map((cell) => (0, import_core10.mapTableCell)(cell))
5078
+ cells: row.cells.map((cell) => (0, import_core11.mapTableCell)(cell))
4976
5079
  }));
4977
5080
  if (type === "text") {
4978
5081
  newTable[props.rowIndex].cells[props.colIndex].props.textColor = color;
@@ -5003,11 +5106,11 @@ function LumirCellColorPickerButton(props) {
5003
5106
  textTitle: "\uC140 \uAE00\uC790\uC0C9",
5004
5107
  backgroundTitle: "\uC140 \uBC30\uACBD",
5005
5108
  text: editor.settings.tables.cellTextColor ? {
5006
- color: (0, import_core10.isTableCell)(currentCell) ? currentCell.props.textColor : "default",
5109
+ color: (0, import_core11.isTableCell)(currentCell) ? currentCell.props.textColor : "default",
5007
5110
  setColor: (color) => updateColor(color, "text")
5008
5111
  } : void 0,
5009
5112
  background: editor.settings.tables.cellBackgroundColor ? {
5010
- color: (0, import_core10.isTableCell)(currentCell) ? currentCell.props.backgroundColor : "default",
5113
+ color: (0, import_core11.isTableCell)(currentCell) ? currentCell.props.backgroundColor : "default",
5011
5114
  setColor: (color) => updateColor(color, "background")
5012
5115
  } : void 0
5013
5116
  }
@@ -6811,7 +6914,9 @@ function LumirEditor({
6811
6914
  // 표 블록 정렬(좌/가운데/우) attr.
6812
6915
  TableAlignmentExtension,
6813
6916
  // 셀 포커스 시 Ctrl/Cmd+A → 표 전체 선택.
6814
- TableSelectAllExtension
6917
+ TableSelectAllExtension,
6918
+ // blur 상태(툴바 드롭다운 조작 등)에서도 텍스트 선택 하이라이트 유지.
6919
+ InactiveSelectionExtension
6815
6920
  ]
6816
6921
  },
6817
6922
  placeholders: placeholder ? { default: placeholder, emptyDocument: placeholder } : void 0,
@@ -7470,7 +7575,7 @@ function LumirEditor({
7470
7575
  allItems.push({
7471
7576
  title: "Link Preview",
7472
7577
  onItemClick: () => {
7473
- (0, import_core11.insertOrUpdateBlock)(editor, {
7578
+ (0, import_core12.insertOrUpdateBlock)(editor, {
7474
7579
  type: "linkPreview",
7475
7580
  props: { url: "" }
7476
7581
  });