@jikjo/ui-kit 0.1.0 → 0.2.1
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.cjs +10 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.css +2 -0
- package/package.json +7 -4
package/dist/index.cjs
CHANGED
|
@@ -164,6 +164,7 @@ const DRAG_DATA_FORMAT = "application/x-lexical-drag-block";
|
|
|
164
164
|
const BTN = 24;
|
|
165
165
|
const GAP = 4;
|
|
166
166
|
const GUTTER_LEFT = 6;
|
|
167
|
+
GUTTER_LEFT + 2 * BTN + GAP;
|
|
167
168
|
const btnBase = {
|
|
168
169
|
display: "flex",
|
|
169
170
|
alignItems: "center",
|
|
@@ -1099,9 +1100,17 @@ function EditorInner({ extensions, toolbarContent, features }) {
|
|
|
1099
1100
|
] });
|
|
1100
1101
|
}
|
|
1101
1102
|
function EditorUI({ extensions = defaultExtensions, namespace = "jikjo", toolbarContent, className, features = [...ALL_FEATURES] }) {
|
|
1103
|
+
const hasBlockToolbar = features.includes("blockHandle") || features.includes("inlineAdd");
|
|
1102
1104
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1103
1105
|
className,
|
|
1104
|
-
...
|
|
1106
|
+
...hasBlockToolbar ? { "data-has-block-toolbar": "" } : {},
|
|
1107
|
+
style: hasBlockToolbar ? {
|
|
1108
|
+
"--jikjo-content-pl": "74px",
|
|
1109
|
+
"--jikjo-placeholder-pl": "74px"
|
|
1110
|
+
} : {
|
|
1111
|
+
"--jikjo-content-pl": "16px",
|
|
1112
|
+
"--jikjo-placeholder-pl": "16px"
|
|
1113
|
+
},
|
|
1105
1114
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__jikjo_core.Editor, {
|
|
1106
1115
|
extensions,
|
|
1107
1116
|
namespace,
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/editor-ui.tsx","../src/components/bubble-menu.tsx","../src/components/slash-menu.tsx","../src/components/block-toolbar.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAqGiB,aAAA;eACF;;;;AADf;;;gBAQmB,CAAA,EAAA,SAAA,GAAA,KAAA;WAMN,CAAA,EAAA,MAAA;EAAK;AA4QlB;;;UAEE,CAAA,EA9QW,KA8QX,CAAA,aAAA,GAAA,WAAA,GAAA,cAAA,GAAA,YAAA,CAAA;;AAEA,iBAJc,QAAA,CAId;EAAA,UAAA;EAAA,SAAA;EAAA,cAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAEC,aAFD,CAAA,EAEc,kBAAA,CAAA,GAAA,CAAA,OAFd;;;;UCxXe,eAAA;;UAEP;iCACuB;UACvB;ADsFV;AAA8B,iBCpBd,UAAA,CDoBc;EAAA,SAAA;EAAA,MAAA;EAAA,cAAA;EAAA;AAAA,CAAA,ECf3B,eDe2B,CAAA,ECfZ,MAAA,CAAA,WAAA,GDeY,IAAA;;;;UEzFb,cAAA;;;SAGR;UACC;EFqFO,OAAA,EAAA,GAAA,GAAA,IAAa;;AACf,iBExCC,SAAA,CFwCD;EAAA,SAAA;EAAA,KAAA;EAAA,KAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EElCZ,cFkCY,CAAA,EElCE,MAAA,CAAA,WAAA,GFkCF,IAAA;;;;UGvEE,iBAAA;;;;;EHsEA,KAAA,EGjER,aHiEqB,EAAA;EAAA,MAAA,EGhEpB,aHgEoB;gBACf,CAAA,EAAA,OAAA;eAOI,CAAA,EAAA,OAAA;;EAMD,aAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA4QF;EAAQ,SAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;AAEtB,
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/editor-ui.tsx","../src/components/bubble-menu.tsx","../src/components/slash-menu.tsx","../src/components/block-toolbar.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAqGiB,aAAA;eACF;;;;AADf;;;gBAQmB,CAAA,EAAA,SAAA,GAAA,KAAA;WAMN,CAAA,EAAA,MAAA;EAAK;AA4QlB;;;UAEE,CAAA,EA9QW,KA8QX,CAAA,aAAA,GAAA,WAAA,GAAA,cAAA,GAAA,YAAA,CAAA;;AAEA,iBAJc,QAAA,CAId;EAAA,UAAA;EAAA,SAAA;EAAA,cAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAEC,aAFD,CAAA,EAEc,kBAAA,CAAA,GAAA,CAAA,OAFd;;;;UCxXe,eAAA;;UAEP;iCACuB;UACvB;ADsFV;AAA8B,iBCpBd,UAAA,CDoBc;EAAA,SAAA;EAAA,MAAA;EAAA,cAAA;EAAA;AAAA,CAAA,ECf3B,eDe2B,CAAA,ECfZ,MAAA,CAAA,WAAA,GDeY,IAAA;;;;UEzFb,cAAA;;;SAGR;UACC;EFqFO,OAAA,EAAA,GAAA,GAAA,IAAa;;AACf,iBExCC,SAAA,CFwCD;EAAA,SAAA;EAAA,KAAA;EAAA,KAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EElCZ,cFkCY,CAAA,EElCE,MAAA,CAAA,WAAA,GFkCF,IAAA;;;;UGvEE,iBAAA;;;;;EHsEA,KAAA,EGjER,aHiEqB,EAAA;EAAA,MAAA,EGhEpB,aHgEoB;gBACf,CAAA,EAAA,OAAA;eAOI,CAAA,EAAA,OAAA;;EAMD,aAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA4QF;EAAQ,SAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;AAEtB,iBG7Rc,YAAA,CH6Rd;EAAA,SAAA;EAAA,OAAA;EAAA,cAAA;EAAA,KAAA;EAAA,MAAA;EAAA,cAAA;EAAA,aAAA;EAAA,aAAA;EAAA;AAAA,CAAA,EGnRC,iBHmRD,CAAA,EGnRkB,kBAAA,CAAA,GAAA,CAAA,OAAA,GHmRlB,IAAA"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/editor-ui.tsx","../src/components/bubble-menu.tsx","../src/components/slash-menu.tsx","../src/components/block-toolbar.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAqGiB,aAAA;eACF;;;;AADf;;;gBAQmB,CAAA,EAAA,SAAA,GAAA,KAAA;WAMN,CAAA,EAAA,MAAA;EAAK;AA4QlB;;;UAEE,CAAA,EA9QW,KA8QX,CAAA,aAAA,GAAA,WAAA,GAAA,cAAA,GAAA,YAAA,CAAA;;AAEA,iBAJc,QAAA,CAId;EAAA,UAAA;EAAA,SAAA;EAAA,cAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAEC,aAFD,CAAA,EAEc,kBAAA,CAAA,GAAA,CAAA,OAFd;;;;UCxXe,eAAA;;UAEP;iCACuB;UACvB;ADsFV;AAA8B,iBCpBd,UAAA,CDoBc;EAAA,SAAA;EAAA,MAAA;EAAA,cAAA;EAAA;AAAA,CAAA,ECf3B,eDe2B,CAAA,ECfZ,MAAA,CAAA,WAAA,GDeY,IAAA;;;;UEzFb,cAAA;;;SAGR;UACC;EFqFO,OAAA,EAAA,GAAA,GAAA,IAAa;;AACf,iBExCC,SAAA,CFwCD;EAAA,SAAA;EAAA,KAAA;EAAA,KAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EElCZ,cFkCY,CAAA,EElCE,MAAA,CAAA,WAAA,GFkCF,IAAA;;;;UGvEE,iBAAA;;;;;EHsEA,KAAA,EGjER,aHiEqB,EAAA;EAAA,MAAA,EGhEpB,aHgEoB;gBACf,CAAA,EAAA,OAAA;eAOI,CAAA,EAAA,OAAA;;EAMD,aAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA4QF;EAAQ,SAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;AAEtB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/editor-ui.tsx","../src/components/bubble-menu.tsx","../src/components/slash-menu.tsx","../src/components/block-toolbar.tsx"],"sourcesContent":[],"mappings":";;;;;;;UAqGiB,aAAA;eACF;;;;AADf;;;gBAQmB,CAAA,EAAA,SAAA,GAAA,KAAA;WAMN,CAAA,EAAA,MAAA;EAAK;AA4QlB;;;UAEE,CAAA,EA9QW,KA8QX,CAAA,aAAA,GAAA,WAAA,GAAA,cAAA,GAAA,YAAA,CAAA;;AAEA,iBAJc,QAAA,CAId;EAAA,UAAA;EAAA,SAAA;EAAA,cAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAEC,aAFD,CAAA,EAEc,kBAAA,CAAA,GAAA,CAAA,OAFd;;;;UCxXe,eAAA;;UAEP;iCACuB;UACvB;ADsFV;AAA8B,iBCpBd,UAAA,CDoBc;EAAA,SAAA;EAAA,MAAA;EAAA,cAAA;EAAA;AAAA,CAAA,ECf3B,eDe2B,CAAA,ECfZ,MAAA,CAAA,WAAA,GDeY,IAAA;;;;UEzFb,cAAA;;;SAGR;UACC;EFqFO,OAAA,EAAA,GAAA,GAAA,IAAa;;AACf,iBExCC,SAAA,CFwCD;EAAA,SAAA;EAAA,KAAA;EAAA,KAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EElCZ,cFkCY,CAAA,EElCE,MAAA,CAAA,WAAA,GFkCF,IAAA;;;;UGvEE,iBAAA;;;;;EHsEA,KAAA,EGjER,aHiEqB,EAAA;EAAA,MAAA,EGhEpB,aHgEoB;gBACf,CAAA,EAAA,OAAA;eAOI,CAAA,EAAA,OAAA;;EAMD,aAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EA4QF;EAAQ,SAAA,CAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,GAAA,IAAA;;AAEtB,iBG7Rc,YAAA,CH6Rd;EAAA,SAAA;EAAA,OAAA;EAAA,cAAA;EAAA,KAAA;EAAA,MAAA;EAAA,cAAA;EAAA,aAAA;EAAA,aAAA;EAAA;AAAA,CAAA,EGnRC,iBHmRD,CAAA,EGnRkB,kBAAA,CAAA,GAAA,CAAA,OAAA,GHmRlB,IAAA"}
|
package/dist/index.js
CHANGED
|
@@ -164,6 +164,7 @@ const DRAG_DATA_FORMAT = "application/x-lexical-drag-block";
|
|
|
164
164
|
const BTN = 24;
|
|
165
165
|
const GAP = 4;
|
|
166
166
|
const GUTTER_LEFT = 6;
|
|
167
|
+
GUTTER_LEFT + 2 * BTN + GAP;
|
|
167
168
|
const btnBase = {
|
|
168
169
|
display: "flex",
|
|
169
170
|
alignItems: "center",
|
|
@@ -1099,9 +1100,17 @@ function EditorInner({ extensions, toolbarContent, features }) {
|
|
|
1099
1100
|
] });
|
|
1100
1101
|
}
|
|
1101
1102
|
function EditorUI({ extensions = defaultExtensions, namespace = "jikjo", toolbarContent, className, features = [...ALL_FEATURES] }) {
|
|
1103
|
+
const hasBlockToolbar = features.includes("blockHandle") || features.includes("inlineAdd");
|
|
1102
1104
|
return /* @__PURE__ */ jsx("div", {
|
|
1103
1105
|
className,
|
|
1104
|
-
...
|
|
1106
|
+
...hasBlockToolbar ? { "data-has-block-toolbar": "" } : {},
|
|
1107
|
+
style: hasBlockToolbar ? {
|
|
1108
|
+
"--jikjo-content-pl": "74px",
|
|
1109
|
+
"--jikjo-placeholder-pl": "74px"
|
|
1110
|
+
} : {
|
|
1111
|
+
"--jikjo-content-pl": "16px",
|
|
1112
|
+
"--jikjo-placeholder-pl": "16px"
|
|
1113
|
+
},
|
|
1105
1114
|
children: /* @__PURE__ */ jsx(Editor, {
|
|
1106
1115
|
extensions,
|
|
1107
1116
|
namespace,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["btnBase: CSSProperties","btnHover: CSSProperties","ICON_MAP: Record<string, React.ReactNode>","targetKey: string | null","top","dragBtnStyle: CSSProperties","addBtnStyle: CSSProperties","ICON_MAP","ICON_MAP: Record<string, ReactNode>","tbtnBase: CSSProperties","tbtnHover: CSSProperties","tbtnActive: CSSProperties","style: CSSProperties","defaultExtensions: Extension[]","key: string | null"],"sources":["../src/components/bubble-menu.tsx","../src/components/block-toolbar.tsx","../src/components/slash-menu.tsx","../src/editor-ui.tsx"],"sourcesContent":["\"use client\";\n\nimport type { SelectionFormatState } from \"@jikjo/core\";\nimport type { LexicalEditor } from \"lexical\";\nimport { Bold, Code, Italic, Strikethrough, Underline } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface BubbleMenuProps {\n isVisible: boolean;\n format: SelectionFormatState;\n onToggleFormat: (format: keyof SelectionFormatState) => void;\n editor: LexicalEditor;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\ninterface AbsolutePos {\n top: number;\n left: number;\n}\n\n/**\n * selection rect를 컨테이너 기준 absolute 좌표로 변환.\n * 컨테이너가 스크롤되어도 absolute 요소는 함께 움직인다.\n */\nfunction getSelectionAbsPos(container: HTMLElement): AbsolutePos | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const range = sel.getRangeAt(0);\n if (range.collapsed) return null;\n\n const selRect = range.getBoundingClientRect();\n if (selRect.width === 0) return null;\n\n const cr = container.getBoundingClientRect();\n const MENU_HEIGHT = 36;\n const GAP = 6;\n\n return {\n top: selRect.top - cr.top + container.scrollTop - MENU_HEIGHT - GAP,\n left: selRect.left - cr.left + container.scrollLeft + selRect.width / 2,\n };\n}\n\n// ─── Button ───────────────────────────────────────────────────────────────────\n\nfunction Btn({\n active,\n label,\n onMouseDown,\n children,\n}: {\n active: boolean;\n label: string;\n onMouseDown: (e: React.MouseEvent) => void;\n children: React.ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n onMouseDown={onMouseDown}\n className={[\n \"flex items-center justify-center w-8 h-8 rounded-md\",\n \"transition-colors duration-75\",\n active\n ? \"bg-white/15 text-white\"\n : \"text-zinc-400 hover:bg-white/10 hover:text-zinc-200\",\n ].join(\" \")}\n >\n {children}\n </button>\n );\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function BubbleMenu({\n isVisible,\n format,\n onToggleFormat,\n editor,\n}: BubbleMenuProps) {\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const [pos, setPos] = useState<AbsolutePos | null>(null);\n\n // editor.getRootElement()가 준비된 후 container를 설정\n useEffect(() => {\n function attachContainer() {\n const rootEl = editor.getRootElement();\n if (!rootEl) return;\n const parent = rootEl.parentElement;\n if (parent) setContainer(parent);\n }\n attachContainer();\n return editor.registerRootListener(attachContainer);\n }, [editor]);\n\n // selection 변화 또는 isVisible 변화 시 좌표 재계산.\n // isVisible=false 될 때만 pos를 null로 초기화 (format 적용 중에는 이전 pos 유지).\n useEffect(() => {\n if (!isVisible || !container) {\n setPos(null);\n return;\n }\n const newPos = getSelectionAbsPos(container);\n // newPos가 null이면 이전 pos 유지 (format 적용 직후 DOM rect가 잠시 없을 때 대비)\n if (newPos !== null) {\n setPos(newPos);\n }\n }, [isVisible, container]);\n\n if (!container) return null;\n\n const menuStyle = pos\n ? { position: \"absolute\" as const, top: pos.top, left: pos.left, x: \"-50%\", zIndex: 50 }\n : { position: \"absolute\" as const, top: -9999, left: -9999, zIndex: 50 };\n\n return createPortal(\n <AnimatePresence>\n {isVisible && (\n <motion.div\n style={menuStyle}\n initial={{ opacity: 0, y: 6 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 6 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n className=\"flex items-center gap-0.5 px-1.5 py-1.5 rounded-lg bg-zinc-800 shadow-xl shadow-black/50\"\n >\n <Btn\n active={format.bold}\n label=\"Bold\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"bold\");\n }}\n >\n <Bold size={13} strokeWidth={2.5} />\n </Btn>\n <Btn\n active={format.italic}\n label=\"Italic\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"italic\");\n }}\n >\n <Italic size={13} strokeWidth={2} />\n </Btn>\n <Btn\n active={format.underline}\n label=\"Underline\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"underline\");\n }}\n >\n <Underline size={13} strokeWidth={2} />\n </Btn>\n\n <div className=\"w-px h-4 bg-zinc-600/50 mx-0.5 shrink-0\" />\n\n <Btn\n active={format.strikethrough}\n label=\"Strikethrough\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"strikethrough\");\n }}\n >\n <Strikethrough size={13} strokeWidth={2} />\n </Btn>\n <Btn\n active={format.code}\n label=\"Code\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"code\");\n }}\n >\n <Code size={13} strokeWidth={2} />\n </Btn>\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n );\n}\n","\"use client\";\n\nimport type { SlashMenuItem } from \"@jikjo/core\";\nimport type { LexicalEditor } from \"lexical\";\nimport {\n $getNearestNodeFromDOMNode,\n $getNodeByKey,\n $isElementNode,\n COMMAND_PRIORITY_HIGH,\n COMMAND_PRIORITY_LOW,\n DRAGOVER_COMMAND,\n DROP_COMMAND,\n} from \"lexical\";\nimport {\n GripVertical,\n Heading1,\n Heading2,\n Heading3,\n List,\n ListOrdered,\n ListTodo,\n Plus,\n Quote,\n Text,\n} from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useCallback, useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface BlockToolbarProps {\n isVisible: boolean;\n nodeKey: string | null;\n /** 현재 커서(포커스)가 있는 블록의 nodeKey */\n focusedNodeKey: string | null;\n items: SlashMenuItem[];\n editor: LexicalEditor;\n showDragHandle?: boolean;\n showAddButton?: boolean;\n /** drag 시작 시 드래그 중인 블록의 nodeKey를 부모에 전달 */\n onDragStarted?: (nodeKey: string) => void;\n /** drag & drop 완료 후 이동된 블록의 nodeKey를 부모에 전달 */\n onDropped?: (nodeKey: string) => void;\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst DRAG_DATA_FORMAT = \"application/x-lexical-drag-block\";\nconst BTN = 24;\nconst GAP = 4;\nconst GUTTER_LEFT = 6;\n\n// ─── Inline styles ────────────────────────────────────────────────────────────\n\nconst btnBase: CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: 4,\n border: \"none\",\n background: \"transparent\",\n color: \"#a1a1aa\",\n cursor: \"pointer\",\n padding: 0,\n transition: \"background 80ms, color 80ms\",\n};\n\nconst btnHover: CSSProperties = {\n background: \"rgba(63,63,70,0.7)\",\n color: \"#e4e4e7\",\n};\n\n// ─── Icon map ─────────────────────────────────────────────────────────────────\n\nconst ICON_MAP: Record<string, React.ReactNode> = {\n paragraph: <Text size={13} strokeWidth={1.75} />,\n heading1: <Heading1 size={13} strokeWidth={1.75} />,\n heading2: <Heading2 size={13} strokeWidth={1.75} />,\n heading3: <Heading3 size={13} strokeWidth={1.75} />,\n bulletList: <List size={13} strokeWidth={1.75} />,\n orderedList: <ListOrdered size={13} strokeWidth={1.75} />,\n taskList: <ListTodo size={13} strokeWidth={1.75} />,\n quote: <Quote size={13} strokeWidth={1.75} />,\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/** blockEl의 top을 container 기준 절대 좌표로 계산 (getBoundingClientRect 기반) */\nfunction getOffsetTopToContainer(blockEl: HTMLElement, container: HTMLElement): number {\n const blockRect = blockEl.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n // container의 scrollTop도 반영 (container가 스크롤 가능한 경우)\n return blockRect.top - containerRect.top + container.scrollTop;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function BlockToolbar({\n isVisible,\n nodeKey,\n focusedNodeKey,\n items,\n editor,\n showDragHandle = true,\n showAddButton = true,\n onDragStarted,\n onDropped,\n}: BlockToolbarProps) {\n const [panelOpen, setPanelOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const [top, setTop] = useState<number | null>(null);\n const [dragHovered, setDragHovered] = useState(false);\n const [addHovered, setAddHovered] = useState(false);\n\n // 드래그 가이드 라인: container 기준 absolute top + 위치 (before/after)\n const [dropLine, setDropLine] = useState<{ top: number; position: \"before\" | \"after\" } | null>(null);\n // 커서 인디케이터: focusedNodeKey 블록의 위치\n const [focusedTop, setFocusedTop] = useState<number | null>(null);\n const [focusedHeight, setFocusedHeight] = useState<number>(0);\n\n const panelRef = useRef<HTMLDivElement>(null);\n const addBtnRef = useRef<HTMLButtonElement>(null);\n\n const numBtns = (showDragHandle ? 1 : 0) + (showAddButton ? 1 : 0);\n\n // ── 컨테이너 취득 ──────────────────────────────────────────────────────────\n\n useEffect(() => {\n return editor.registerRootListener((rootElement) => {\n setContainer(rootElement?.parentElement ?? null);\n });\n }, [editor]);\n\n // ── 수직 위치 계산 ─────────────────────────────────────────────────────────\n\n useEffect(() => {\n function calc() {\n if (!isVisible || !nodeKey || !container) { setTop(null); return; }\n const blockEl = editor.getElementByKey(nodeKey);\n if (!blockEl) { setTop(null); return; }\n const offset = getOffsetTopToContainer(blockEl, container);\n setTop(offset + (blockEl.offsetHeight - BTN) / 2);\n }\n calc();\n return editor.registerUpdateListener(() => calc());\n }, [isVisible, nodeKey, editor, container]);\n\n // ── 커서 인디케이터 위치 계산 ──────────────────────────────────────────────\n\n useEffect(() => {\n function calc() {\n if (!focusedNodeKey || !container) { setFocusedTop(null); return; }\n const blockEl = editor.getElementByKey(focusedNodeKey);\n if (!blockEl) { setFocusedTop(null); return; }\n setFocusedTop(getOffsetTopToContainer(blockEl, container));\n setFocusedHeight(blockEl.offsetHeight);\n }\n calc();\n const unregister = editor.registerUpdateListener(() => calc());\n return unregister;\n }, [focusedNodeKey, editor, container]);\n\n // ── 패널 닫기 ──────────────────────────────────────────────────────────────\n\n useEffect(() => { if (!panelOpen) setActiveIndex(0); }, [panelOpen]);\n useEffect(() => { if (!isVisible) setPanelOpen(false); }, [isVisible]);\n\n\n // 스크롤 시 패널 닫기\n useEffect(() => {\n if (!panelOpen) return;\n function onScroll() { setPanelOpen(false); }\n window.addEventListener(\"scroll\", onScroll, { passive: true, capture: true });\n return () => window.removeEventListener(\"scroll\", onScroll, { capture: true });\n }, [panelOpen]);\n\n useEffect(() => {\n if (!panelOpen) return;\n function onPointerDown(e: PointerEvent) {\n if (panelRef.current && !panelRef.current.contains(e.target as Node)) {\n setPanelOpen(false);\n }\n }\n window.addEventListener(\"pointerdown\", onPointerDown);\n return () => window.removeEventListener(\"pointerdown\", onPointerDown);\n }, [panelOpen]);\n\n useEffect(() => {\n if (!panelOpen) return;\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((p) => Math.min(p + 1, items.length - 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((p) => Math.max(p - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const item = items[activeIndex];\n if (item) { editor.focus(() => { item.onSelect(editor); setPanelOpen(false); }); }\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n setPanelOpen(false);\n }\n }\n window.addEventListener(\"keydown\", onKeyDown, { capture: true });\n return () => window.removeEventListener(\"keydown\", onKeyDown, { capture: true });\n }, [panelOpen, items, activeIndex, editor]);\n\n // ── Drag & Drop (가이드 라인 포함) ─────────────────────────────────────────\n\n useEffect(() => {\n if (!showDragHandle || !container) return;\n\n const unregDragover = editor.registerCommand(\n DRAGOVER_COMMAND,\n (event) => {\n if (!event.dataTransfer?.types.includes(DRAG_DATA_FORMAT)) return false;\n event.preventDefault();\n\n const draggedKey = event.dataTransfer.getData(DRAG_DATA_FORMAT);\n const target = event.target as HTMLElement | null;\n if (!target) return true;\n\n // 드래그 중인 element가 draggedNode의 DOM 안에 있으면 가이드라인 숨김\n if (draggedKey) {\n const draggedDOM = editor.getElementByKey(draggedKey);\n if (draggedDOM?.contains(target)) { setDropLine(null); return true; }\n }\n\n let targetKey: string | null = null;\n editor.read(() => {\n const node = $getNearestNodeFromDOMNode(target);\n if (!node) return;\n // 최상위 블록으로 올라감\n const top = node.isInline() ? node.getTopLevelElement() : node;\n if (top) targetKey = top.getKey();\n });\n if (!targetKey || targetKey === draggedKey) { setDropLine(null); return true; }\n\n const targetDOM = editor.getElementByKey(targetKey);\n if (!targetDOM) { setDropLine(null); return true; }\n\n const { top: domTop, height } = targetDOM.getBoundingClientRect();\n const isBefore = event.clientY < domTop + height / 2;\n const offsetTop = getOffsetTopToContainer(targetDOM, container);\n\n setDropLine({\n top: isBefore ? offsetTop : offsetTop + targetDOM.offsetHeight,\n position: isBefore ? \"before\" : \"after\",\n });\n\n return true;\n },\n COMMAND_PRIORITY_LOW,\n );\n\n const unregDrop = editor.registerCommand(\n DROP_COMMAND,\n (event) => {\n const draggedKey = event.dataTransfer?.getData(DRAG_DATA_FORMAT);\n if (!draggedKey) return false;\n event.preventDefault();\n setDropLine(null);\n\n const target = event.target as HTMLElement | null;\n if (!target) return true;\n\n // 실제로 노드가 이동되었는지 확인하기 위해 현재 에디터 상태에서 사전 검증\n let willMove = false;\n editor.read(() => {\n const draggedNode = $getNodeByKey(draggedKey);\n if (!draggedNode) return;\n const nearestNode = $getNearestNodeFromDOMNode(target);\n if (!nearestNode) return;\n const targetNode = nearestNode.isInline() ? nearestNode.getTopLevelElement() : nearestNode;\n if (!targetNode) return;\n const targetKey = targetNode.getKey();\n if (targetKey === draggedKey) return;\n if ($isElementNode(draggedNode) && draggedNode.getChildren().some(\n (child) => child.getKey() === targetKey,\n )) return;\n willMove = true;\n });\n\n editor.update(() => {\n const draggedNode = $getNodeByKey(draggedKey);\n if (!draggedNode) return;\n\n const nearestNode = $getNearestNodeFromDOMNode(target);\n if (!nearestNode) return;\n\n // 최상위 블록으로 정규화\n const targetNode = nearestNode.isInline()\n ? nearestNode.getTopLevelElement()\n : nearestNode;\n if (!targetNode) return;\n\n const targetKey = targetNode.getKey();\n // 자기 자신 또는 자기 자손으로 drop 방지\n if (targetKey === draggedKey) return;\n if ($isElementNode(draggedNode) && draggedNode.getChildren().some(\n (child) => child.getKey() === targetKey,\n )) return;\n\n const targetDOM = editor.getElementByKey(targetKey);\n if (!targetDOM) return;\n\n const { top: targetTop, height } = targetDOM.getBoundingClientRect();\n if (event.clientY < targetTop + height / 2) {\n targetNode.insertBefore(draggedNode);\n } else {\n targetNode.insertAfter(draggedNode);\n }\n });\n\n // Lexical의 drop 이벤트 처리(DOM selection 읽기)가 완료된 뒤에 onDropped 호출\n // requestAnimationFrame은 Lexical의 microtask 큐 처리 후에 실행됨\n if (willMove && onDropped) {\n requestAnimationFrame(() => onDropped(draggedKey));\n }\n return true;\n },\n COMMAND_PRIORITY_HIGH,\n );\n\n // 드래그가 에디터 밖으로 나가면 가이드 라인 제거\n function onDragEnd() { setDropLine(null); }\n document.addEventListener(\"dragend\", onDragEnd);\n\n return () => {\n unregDragover();\n unregDrop();\n document.removeEventListener(\"dragend\", onDragEnd);\n };\n }, [editor, showDragHandle, container, onDropped]);\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n if (!nodeKey) return;\n e.dataTransfer.setData(DRAG_DATA_FORMAT, nodeKey);\n e.dataTransfer.effectAllowed = \"move\";\n const blockEl = editor.getElementByKey(nodeKey);\n if (blockEl) e.dataTransfer.setDragImage(blockEl, 0, 0);\n onDragStarted?.(nodeKey);\n }, [nodeKey, editor, onDragStarted]);\n\n // ── + 버튼 클릭 ────────────────────────────────────────────────────────────\n\n const handleAddClick = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n setPanelOpen((p) => !p);\n }, []);\n\n // ── Render ─────────────────────────────────────────────────────────────────\n\n if (!container) return null;\n\n const dragBtnStyle: CSSProperties = {\n ...btnBase,\n width: BTN,\n height: BTN,\n cursor: \"grab\",\n ...(dragHovered ? btnHover : {}),\n };\n\n const addBtnStyle: CSSProperties = {\n ...btnBase,\n width: BTN,\n height: BTN,\n ...(panelOpen || addHovered ? btnHover : {}),\n };\n\n return (\n <>\n {/* 커서 인디케이터 — 현재 포커스 블록 왼쪽에 세로 라인 */}\n {createPortal(\n <AnimatePresence>\n {focusedTop !== null && (\n <motion.div\n key={focusedNodeKey ?? \"cursor-indicator\"}\n style={{\n position: \"absolute\",\n top: focusedTop,\n left: 0,\n width: 2,\n height: focusedHeight,\n background: \"#818cf8\", // indigo-400\n borderRadius: 1,\n zIndex: 2,\n pointerEvents: \"none\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.1 }}\n />\n )}\n </AnimatePresence>,\n container!,\n )}\n\n {/* 버튼 그룹 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {isVisible && top !== null && (\n <motion.div\n key={nodeKey ?? \"toolbar\"}\n data-block-toolbar\n style={{\n position: \"absolute\",\n top,\n left: GUTTER_LEFT,\n display: \"flex\",\n alignItems: \"center\",\n gap: GAP,\n width: numBtns * BTN + Math.max(0, numBtns - 1) * GAP,\n zIndex: 2,\n userSelect: \"none\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.08, ease: \"easeOut\" }}\n >\n {showDragHandle && (\n <div\n draggable\n data-drag-handle\n onDragStart={handleDragStart}\n aria-label=\"Drag to reorder\"\n style={dragBtnStyle}\n onMouseEnter={() => setDragHovered(true)}\n onMouseLeave={() => setDragHovered(false)}\n >\n <GripVertical size={14} strokeWidth={2} />\n </div>\n )}\n {showAddButton && (\n <button\n ref={addBtnRef}\n type=\"button\"\n aria-label=\"Add block\"\n aria-expanded={panelOpen}\n style={addBtnStyle}\n onMouseDown={handleAddClick}\n onMouseEnter={() => setAddHovered(true)}\n onMouseLeave={() => setAddHovered(false)}\n >\n <Plus size={13} strokeWidth={2.5} />\n </button>\n )}\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n\n {/* 드래그 가이드 라인 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {dropLine !== null && (\n <motion.div\n key=\"drop-line\"\n style={{\n position: \"absolute\",\n top: dropLine.top,\n // 텍스트 시작점(padding-left)에 맞춰 왼쪽 여백\n left: 0,\n right: 0,\n height: 2,\n zIndex: 3,\n pointerEvents: \"none\",\n // 가이드 라인: 파란 선 + 양쪽 원형 핸들\n display: \"flex\",\n alignItems: \"center\",\n // translateY로 선을 블록 경계에 정확히 정렬\n transform: \"translateY(-1px)\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.06 }}\n >\n {/* 왼쪽 원 */}\n <div style={{\n width: 6,\n height: 6,\n borderRadius: \"50%\",\n background: \"#818cf8\", // indigo-400\n flexShrink: 0,\n marginLeft: 4,\n }} />\n {/* 가로선 */}\n <div style={{\n flex: 1,\n height: 2,\n background: \"#818cf8\",\n marginRight: 8,\n }} />\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n\n {/* 패널 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {isVisible && panelOpen && top !== null && (\n <motion.div\n ref={panelRef}\n data-block-toolbar\n style={{\n position: \"absolute\",\n top: top + BTN + GAP,\n left: GUTTER_LEFT,\n zIndex: 50,\n width: 240,\n }}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n role=\"dialog\"\n aria-label=\"Insert block\"\n className=\"rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5\"\n >\n <div role=\"listbox\" aria-label=\"Block type\" className=\"w-full px-1.5 flex flex-col\">\n {items.map((item, index) => {\n const icon = ICON_MAP[item.id] ?? item.icon;\n const isActive = index === activeIndex;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n onMouseDown={(e) => {\n e.preventDefault();\n editor.focus(() => { item.onSelect(editor); setPanelOpen(false); });\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={[\n \"w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75\",\n \"outline-none\",\n isActive\n ? \"bg-zinc-700/60 text-zinc-100\"\n : \"text-zinc-400 hover:bg-zinc-700/40 hover:text-zinc-200\",\n ].join(\" \")}\n >\n <span className=\"flex items-center justify-center shrink-0 w-4 text-current\">\n {icon}\n </span>\n <span className=\"text-sm font-normal leading-none\">{item.label}</span>\n </button>\n );\n })}\n </div>\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n </>\n );\n}\n","\"use client\";\n\nimport type { LexicalEditor } from \"lexical\";\nimport type { SlashMenuItem } from \"@jikjo/core\";\nimport { Heading1, Heading2, Heading3, Quote, Text } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type { ReactNode } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface SlashMenuProps {\n isVisible: boolean;\n query: string;\n items: SlashMenuItem[];\n editor: LexicalEditor;\n onClose: () => void;\n}\n\n// ─── Icon map ─────────────────────────────────────────────────────────────────\n\nconst ICON_MAP: Record<string, ReactNode> = {\n paragraph: <Text size={14} strokeWidth={1.75} />,\n heading1: <Heading1 size={14} strokeWidth={1.75} />,\n heading2: <Heading2 size={14} strokeWidth={1.75} />,\n heading3: <Heading3 size={14} strokeWidth={1.75} />,\n quote: <Quote size={14} strokeWidth={1.75} />,\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\ninterface AbsolutePos {\n top: number;\n left: number;\n}\n\n/**\n * 캐럿(collapsed range)의 bottom 위치를 컨테이너 기준 absolute 좌표로 변환.\n */\nfunction getCaretAbsPos(container: HTMLElement): AbsolutePos | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n\n const range = sel.getRangeAt(0).cloneRange();\n range.collapse(true);\n const caretRect = range.getBoundingClientRect();\n\n // getBoundingClientRect()가 (0,0,0,0)을 반환하는 경우 방어\n if (caretRect.width === 0 && caretRect.height === 0 && caretRect.top === 0) return null;\n\n const cr = container.getBoundingClientRect();\n const GAP = 6;\n\n return {\n top: caretRect.bottom - cr.top + container.scrollTop + GAP,\n left: caretRect.left - cr.left + container.scrollLeft,\n };\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function SlashMenu({\n isVisible,\n query,\n items,\n editor,\n onClose,\n}: SlashMenuProps) {\n const [activeIndex, setActiveIndex] = useState(0);\n const containerRef = useRef<HTMLDivElement>(null);\n const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);\n const [pos, setPos] = useState<AbsolutePos | null>(null);\n\n // editor.getRootElement()가 준비된 후 portalContainer를 설정\n useEffect(() => {\n function attachContainer() {\n const rootEl = editor.getRootElement();\n if (!rootEl) return;\n const parent = rootEl.parentElement;\n if (parent) setPortalContainer(parent);\n }\n attachContainer();\n return editor.registerRootListener(attachContainer);\n }, [editor]);\n\n const filtered = items.filter(\n (item) =>\n !query ||\n item.label.toLowerCase().includes(query.toLowerCase()) ||\n item.description.toLowerCase().includes(query.toLowerCase()),\n );\n\n // isVisible 또는 query 변경 시 좌표 재계산 (캐럿이 이동/입력될 때마다)\n useEffect(() => {\n if (!isVisible || !portalContainer) {\n setPos(null);\n return;\n }\n // registerUpdateListener가 발동된 직후 호출되므로 DOM이 확정된 시점\n setPos(getCaretAbsPos(portalContainer));\n }, [isVisible, query, portalContainer]);\n\n useEffect(() => {\n setActiveIndex(0);\n }, [query]);\n\n useEffect(() => {\n if (!isVisible) return;\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setActiveIndex((prev) => Math.min(prev + 1, filtered.length - 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((prev) => Math.max(prev - 1, 0));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const item = filtered[activeIndex];\n if (!item) return;\n editor.focus(() => {\n item.onSelect(editor);\n onClose();\n });\n } else if (event.key === \"Escape\") {\n onClose();\n }\n }\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isVisible, filtered, activeIndex, editor, onClose]);\n\n useEffect(() => {\n const el = containerRef.current?.querySelector<HTMLElement>(\n '[data-active=\"true\"]',\n );\n el?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex]);\n\n if (!portalContainer) return null;\n\n const menuStyle = pos\n ? { position: \"absolute\" as const, top: pos.top, left: pos.left, zIndex: 50, width: 240 }\n : { position: \"absolute\" as const, top: -9999, left: -9999, zIndex: 50, width: 240 };\n\n return createPortal(\n <AnimatePresence>\n {isVisible && (\n <motion.div\n style={menuStyle}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n role=\"dialog\"\n aria-label=\"Insert block\"\n className=\"rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5 overflow-hidden\"\n >\n {filtered.length === 0 ? (\n <p className=\"px-4 py-2 text-xs text-zinc-500\">\n No results for “{query}”\n </p>\n ) : (\n <div\n ref={containerRef}\n role=\"listbox\"\n aria-label=\"Block type\"\n className=\"w-full max-h-72 overflow-y-auto px-1.5 flex flex-col\"\n >\n {filtered.map((item, index) => {\n const icon = ICON_MAP[item.id] ?? item.icon;\n const isActive = index === activeIndex;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-active={isActive}\n onMouseDown={(e) => {\n e.preventDefault();\n editor.focus(() => {\n item.onSelect(editor);\n onClose();\n });\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={[\n \"w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75\",\n \"outline-none focus-visible:outline-none\",\n isActive\n ? \"bg-zinc-700/80 text-zinc-100\"\n : \"text-zinc-400 hover:bg-zinc-700/60 hover:text-zinc-200 focus-visible:bg-zinc-700/80 focus-visible:text-zinc-100\",\n ].join(\" \")}\n >\n <span className=\"flex items-center justify-center shrink-0 w-4 text-current\">\n {icon}\n </span>\n <span className=\"text-sm font-normal leading-none\">\n {item.label}\n </span>\n </button>\n );\n })}\n </div>\n )}\n </motion.div>\n )}\n </AnimatePresence>,\n portalContainer,\n );\n}\n","\"use client\";\n\nimport {\n Editor,\n historyExtension,\n richTextExtension,\n useBlockHoverPlugin,\n useSelectionPlugin,\n useSlashCommandPlugin,\n} from \"@jikjo/core\";\nimport type { Extension, SlashMenuItem } from \"@jikjo/core\";\nimport {\n $createHeadingNode,\n $isHeadingNode,\n type HeadingTagType,\n} from \"@lexical/rich-text\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n $createParagraphNode,\n $getNearestNodeFromDOMNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\nimport {\n Bold,\n Code,\n Heading1,\n Heading2,\n Heading3,\n Italic,\n Strikethrough,\n Underline,\n} from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { MouseEvent } from \"react\";\nimport { BubbleMenu } from \"./components/bubble-menu\";\nimport { BlockToolbar } from \"./components/block-toolbar\";\nimport { SlashMenu } from \"./components/slash-menu\";\n\n// ─── Styles ───────────────────────────────────────────────────────────────────\n\nconst tbtnBase: CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 32,\n height: 32,\n borderRadius: 6,\n border: \"none\",\n background: \"transparent\",\n color: \"#71717a\",\n cursor: \"pointer\",\n padding: 0,\n transition: \"background 100ms, color 100ms\",\n flexShrink: 0,\n};\n\nconst tbtnHover: CSSProperties = {\n background: \"rgba(39,39,42,0.9)\",\n color: \"#e4e4e7\",\n};\n\nconst tbtnActive: CSSProperties = {\n background: \"rgba(63,63,70,0.8)\",\n color: \"#f4f4f5\",\n};\n\n// ─── ToolbarButton ─────────────────────────────────────────────────────────────\n\ninterface ToolbarButtonProps {\n label: string;\n isActive?: boolean;\n onMouseDown: (e: MouseEvent) => void;\n children: ReactNode;\n}\n\nfunction ToolbarButton({ label, isActive, onMouseDown, children }: ToolbarButtonProps) {\n const [hovered, setHovered] = useState(false);\n\n const style: CSSProperties = {\n ...tbtnBase,\n ...(hovered ? tbtnHover : {}),\n ...(isActive ? tbtnActive : {}),\n };\n\n return (\n <button\n type=\"button\"\n aria-label={label}\n onMouseDown={onMouseDown}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={style}\n >\n {children}\n </button>\n );\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface EditorUIProps {\n extensions?: Extension[];\n namespace?: string;\n /**\n * undefined → default toolbar\n * false → no toolbar rendered at all\n * ReactNode → custom toolbar content\n */\n toolbarContent?: ReactNode | false;\n className?: string;\n /**\n * 활성화할 UI 기능 목록. 기본값: 모두 활성화.\n * \"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\"\n */\n features?: Array<\"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\">;\n}\n\n// ─── Default extensions ───────────────────────────────────────────────────────\n\nconst defaultExtensions: Extension[] = [richTextExtension, historyExtension];\n\n// ─── Inner component ──────────────────────────────────────────────────────────\n// Must live inside LexicalComposer so all hooks have editor context.\n\nconst ALL_FEATURES = [\"blockHandle\", \"inlineAdd\", \"slashCommand\", \"bubbleMenu\"] as const;\n\ninterface EditorInnerProps {\n extensions: Extension[];\n toolbarContent: ReactNode | false | undefined;\n features: Array<\"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\">;\n}\n\nfunction EditorInner({\n extensions,\n toolbarContent,\n features,\n}: EditorInnerProps) {\n const [editor] = useLexicalComposerContext();\n\n const selection = useSelectionPlugin();\n const slashCommand = useSlashCommandPlugin();\n const blockHover = useBlockHoverPlugin();\n\n // heading 태그는 Lexical selection으로 추적 (toolbar 표시용)\n const [currentHeadingTag, setCurrentHeadingTag] = useState<HeadingTagType | null>(null);\n\n // focusedNodeKey: Lexical selection에서 완전히 독립.\n // - 에디터 click → DOM에서 직접 nodeKey 읽기\n // - dragstart → onDragStarted 콜백으로 nodeKey 전달\n // - drop 성공 → onDropped 콜백으로 이동된 nodeKey 전달\n // - dragend(취소) → null\n // - 에디터 바깥 클릭 → null\n const [focusedNodeKey, setFocusedNodeKey] = useState<string | null>(null);\n\n // drop 성공 플래그 (dragend 시 취소인지 성공인지 구분용)\n const dropSucceededRef = useRef(false);\n\n // ── heading tag 추적 (selection 기반, toolbar 전용) ──────────────────────\n\n useEffect(() => {\n return editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel)) {\n setCurrentHeadingTag(null);\n return;\n }\n const topNode = sel.anchor.getNode().getTopLevelElement();\n setCurrentHeadingTag($isHeadingNode(topNode) ? topNode.getTag() : null);\n });\n });\n }, [editor]);\n\n // ── focusedNodeKey: 에디터 click 이벤트에서 DOM → nodeKey 직접 읽기 ──────\n\n useEffect(() => {\n return editor.registerRootListener((rootElement) => {\n if (!rootElement) return;\n const root = rootElement;\n\n function onClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n let key: string | null = null;\n editor.read(() => {\n const node = $getNearestNodeFromDOMNode(target);\n if (!node) return;\n const top = node.isInline() ? node.getTopLevelElement() : node;\n if (top) key = top.getKey();\n });\n if (key) setFocusedNodeKey(key);\n }\n\n // 에디터/툴바 바깥 클릭 시 커서 인디케이터 제거\n function onDocClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (root.contains(target)) return;\n if (target?.closest(\"[data-block-toolbar]\") || target?.closest(\"[data-drag-handle]\")) return;\n setFocusedNodeKey(null);\n }\n\n root.addEventListener(\"click\", onClick);\n document.addEventListener(\"click\", onDocClick, true);\n\n return () => {\n root.removeEventListener(\"click\", onClick);\n document.removeEventListener(\"click\", onDocClick, true);\n };\n });\n }, [editor]);\n\n // ── drag/drop 이벤트에서 focusedNodeKey 관리 ────────────────────────────\n // Firefox: drag handle mousedown → blur → dragstart 순서로 발생.\n // blur 시 Lexical이 $setSelection(null)을 호출하지만,\n // focusedNodeKey는 Lexical selection과 독립이므로 영향받지 않음.\n // drop 성공/취소는 onDropped / dragend 이벤트로 구분.\n\n const handleDragStarted = useCallback((key: string) => {\n dropSucceededRef.current = false;\n setFocusedNodeKey(key);\n // Firefox blur로 에디터 포커스가 빠진 경우 복구\n editor.focus();\n }, [editor]);\n\n const handleDropped = useCallback((key: string) => {\n dropSucceededRef.current = true;\n setFocusedNodeKey(key);\n // drop 후 에디터 포커스 복구\n editor.focus();\n }, [editor]);\n\n useEffect(() => {\n function onDragEnd() {\n if (!dropSucceededRef.current) {\n // drag 취소: 커서 인디케이터 제거\n setFocusedNodeKey(null);\n }\n dropSucceededRef.current = false;\n }\n document.addEventListener(\"dragend\", onDragEnd);\n return () => document.removeEventListener(\"dragend\", onDragEnd);\n }, []);\n\n // Collect slash menu items from all registered extensions\n const slashMenuItems = useMemo<SlashMenuItem[]>(\n () => extensions.flatMap((ext) => ext.slashMenuItems ?? []),\n [extensions],\n );\n\n // Toggle heading: if already at this level, revert to paragraph.\n // newNode.select() preserves selection after replace to avoid Lexical error.\n const toggleHeading = useCallback(\n (tag: HeadingTagType) => {\n editor.update(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel)) return;\n const topNode = sel.anchor.getNode().getTopLevelElementOrThrow();\n const newNode = ($isHeadingNode(topNode) && topNode.getTag() === tag)\n ? $createParagraphNode()\n : $createHeadingNode(tag);\n topNode.replace(newNode);\n newNode.select();\n });\n },\n [editor],\n );\n\n return (\n <>\n {/* ── Toolbar ──────────────────────────────────────────────────── */}\n {toolbarContent === false ? null : toolbarContent !== undefined ? (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2, padding: \"8px 16px\", borderBottom: \"1px solid rgba(39,39,42,0.5)\" }}>\n {toolbarContent}\n </div>\n ) : (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2, padding: \"8px 16px\", borderBottom: \"1px solid rgba(39,39,42,0.5)\" }}>\n {/* Format group */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <ToolbarButton\n label=\"Bold\"\n isActive={selection.format.bold}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"bold\"); }}\n >\n <Bold size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Italic\"\n isActive={selection.format.italic}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"italic\"); }}\n >\n <Italic size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Underline\"\n isActive={selection.format.underline}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"underline\"); }}\n >\n <Underline size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Strikethrough\"\n isActive={selection.format.strikethrough}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"strikethrough\"); }}\n >\n <Strikethrough size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Code\"\n isActive={selection.format.code}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"code\"); }}\n >\n <Code size={14} strokeWidth={2.5} />\n </ToolbarButton>\n </div>\n\n <div style={{ width: 1, height: 16, background: \"#27272a\", margin: \"0 4px\" }} />\n\n {/* Heading group */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <ToolbarButton\n label=\"Heading 1\"\n isActive={currentHeadingTag === \"h1\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h1\"); }}\n >\n <Heading1 size={15} strokeWidth={2} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Heading 2\"\n isActive={currentHeadingTag === \"h2\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h2\"); }}\n >\n <Heading2 size={15} strokeWidth={2} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Heading 3\"\n isActive={currentHeadingTag === \"h3\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h3\"); }}\n >\n <Heading3 size={15} strokeWidth={2} />\n </ToolbarButton>\n </div>\n </div>\n )}\n\n {/* ── Floating overlays ────────────────────────────────────────── */}\n {features.includes(\"bubbleMenu\") && (\n <BubbleMenu\n isVisible={selection.isActive}\n format={selection.format}\n onToggleFormat={selection.toggleFormat}\n editor={editor}\n />\n )}\n\n {features.includes(\"slashCommand\") && (\n <SlashMenu\n isVisible={slashCommand.isActive}\n query={slashCommand.query}\n items={slashMenuItems}\n editor={editor}\n onClose={slashCommand.close}\n />\n )}\n\n {/* drag handle + + 버튼 + 커서 인디케이터 */}\n <BlockToolbar\n isVisible={blockHover.isActive && (features.includes(\"blockHandle\") || features.includes(\"inlineAdd\"))}\n nodeKey={blockHover.nodeKey}\n focusedNodeKey={focusedNodeKey}\n items={slashMenuItems}\n editor={editor}\n showDragHandle={features.includes(\"blockHandle\")}\n showAddButton={features.includes(\"inlineAdd\")}\n onDragStarted={handleDragStarted}\n onDropped={handleDropped}\n />\n </>\n );\n}\n\n// ─── Public component ─────────────────────────────────────────────────────────\n\nexport function EditorUI({\n extensions = defaultExtensions,\n namespace = \"jikjo\",\n toolbarContent,\n className,\n features = [...ALL_FEATURES],\n}: EditorUIProps) {\n const hasBlockToolbar = features.includes(\"blockHandle\") || features.includes(\"inlineAdd\");\n return (\n <div className={className} {...(hasBlockToolbar ? { \"data-has-block-toolbar\": \"\" } : {})}>\n <Editor extensions={extensions} namespace={namespace}>\n <EditorInner\n extensions={extensions}\n toolbarContent={toolbarContent}\n features={features}\n />\n </Editor>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["btnBase: CSSProperties","btnHover: CSSProperties","ICON_MAP: Record<string, React.ReactNode>","targetKey: string | null","top","dragBtnStyle: CSSProperties","addBtnStyle: CSSProperties","ICON_MAP","ICON_MAP: Record<string, ReactNode>","tbtnBase: CSSProperties","tbtnHover: CSSProperties","tbtnActive: CSSProperties","style: CSSProperties","defaultExtensions: Extension[]","key: string | null"],"sources":["../src/components/bubble-menu.tsx","../src/components/block-toolbar.tsx","../src/components/slash-menu.tsx","../src/editor-ui.tsx"],"sourcesContent":["\"use client\";\n\nimport type { SelectionFormatState } from \"@jikjo/core\";\nimport type { LexicalEditor } from \"lexical\";\nimport { Bold, Code, Italic, Strikethrough, Underline } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface BubbleMenuProps {\n isVisible: boolean;\n format: SelectionFormatState;\n onToggleFormat: (format: keyof SelectionFormatState) => void;\n editor: LexicalEditor;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\ninterface AbsolutePos {\n top: number;\n left: number;\n}\n\n/**\n * selection rect를 컨테이너 기준 absolute 좌표로 변환.\n * 컨테이너가 스크롤되어도 absolute 요소는 함께 움직인다.\n */\nfunction getSelectionAbsPos(container: HTMLElement): AbsolutePos | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const range = sel.getRangeAt(0);\n if (range.collapsed) return null;\n\n const selRect = range.getBoundingClientRect();\n if (selRect.width === 0) return null;\n\n const cr = container.getBoundingClientRect();\n const MENU_HEIGHT = 36;\n const GAP = 6;\n\n return {\n top: selRect.top - cr.top + container.scrollTop - MENU_HEIGHT - GAP,\n left: selRect.left - cr.left + container.scrollLeft + selRect.width / 2,\n };\n}\n\n// ─── Button ───────────────────────────────────────────────────────────────────\n\nfunction Btn({\n active,\n label,\n onMouseDown,\n children,\n}: {\n active: boolean;\n label: string;\n onMouseDown: (e: React.MouseEvent) => void;\n children: React.ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n onMouseDown={onMouseDown}\n className={[\n \"flex items-center justify-center w-8 h-8 rounded-md\",\n \"transition-colors duration-75\",\n active\n ? \"bg-white/15 text-white\"\n : \"text-zinc-400 hover:bg-white/10 hover:text-zinc-200\",\n ].join(\" \")}\n >\n {children}\n </button>\n );\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function BubbleMenu({\n isVisible,\n format,\n onToggleFormat,\n editor,\n}: BubbleMenuProps) {\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const [pos, setPos] = useState<AbsolutePos | null>(null);\n\n // editor.getRootElement()가 준비된 후 container를 설정\n useEffect(() => {\n function attachContainer() {\n const rootEl = editor.getRootElement();\n if (!rootEl) return;\n const parent = rootEl.parentElement;\n if (parent) setContainer(parent);\n }\n attachContainer();\n return editor.registerRootListener(attachContainer);\n }, [editor]);\n\n // selection 변화 또는 isVisible 변화 시 좌표 재계산.\n // isVisible=false 될 때만 pos를 null로 초기화 (format 적용 중에는 이전 pos 유지).\n useEffect(() => {\n if (!isVisible || !container) {\n setPos(null);\n return;\n }\n const newPos = getSelectionAbsPos(container);\n // newPos가 null이면 이전 pos 유지 (format 적용 직후 DOM rect가 잠시 없을 때 대비)\n if (newPos !== null) {\n setPos(newPos);\n }\n }, [isVisible, container]);\n\n if (!container) return null;\n\n const menuStyle = pos\n ? { position: \"absolute\" as const, top: pos.top, left: pos.left, x: \"-50%\", zIndex: 50 }\n : { position: \"absolute\" as const, top: -9999, left: -9999, zIndex: 50 };\n\n return createPortal(\n <AnimatePresence>\n {isVisible && (\n <motion.div\n style={menuStyle}\n initial={{ opacity: 0, y: 6 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 6 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n className=\"flex items-center gap-0.5 px-1.5 py-1.5 rounded-lg bg-zinc-800 shadow-xl shadow-black/50\"\n >\n <Btn\n active={format.bold}\n label=\"Bold\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"bold\");\n }}\n >\n <Bold size={13} strokeWidth={2.5} />\n </Btn>\n <Btn\n active={format.italic}\n label=\"Italic\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"italic\");\n }}\n >\n <Italic size={13} strokeWidth={2} />\n </Btn>\n <Btn\n active={format.underline}\n label=\"Underline\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"underline\");\n }}\n >\n <Underline size={13} strokeWidth={2} />\n </Btn>\n\n <div className=\"w-px h-4 bg-zinc-600/50 mx-0.5 shrink-0\" />\n\n <Btn\n active={format.strikethrough}\n label=\"Strikethrough\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"strikethrough\");\n }}\n >\n <Strikethrough size={13} strokeWidth={2} />\n </Btn>\n <Btn\n active={format.code}\n label=\"Code\"\n onMouseDown={(e) => {\n e.preventDefault();\n onToggleFormat(\"code\");\n }}\n >\n <Code size={13} strokeWidth={2} />\n </Btn>\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n );\n}\n","\"use client\";\n\nimport type { SlashMenuItem } from \"@jikjo/core\";\nimport type { LexicalEditor } from \"lexical\";\nimport {\n $getNearestNodeFromDOMNode,\n $getNodeByKey,\n $isElementNode,\n COMMAND_PRIORITY_HIGH,\n COMMAND_PRIORITY_LOW,\n DRAGOVER_COMMAND,\n DROP_COMMAND,\n} from \"lexical\";\nimport {\n GripVertical,\n Heading1,\n Heading2,\n Heading3,\n List,\n ListOrdered,\n ListTodo,\n Plus,\n Quote,\n Text,\n} from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { useCallback, useEffect, useRef, useState, type CSSProperties } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface BlockToolbarProps {\n isVisible: boolean;\n nodeKey: string | null;\n /** 현재 커서(포커스)가 있는 블록의 nodeKey */\n focusedNodeKey: string | null;\n items: SlashMenuItem[];\n editor: LexicalEditor;\n showDragHandle?: boolean;\n showAddButton?: boolean;\n /** drag 시작 시 드래그 중인 블록의 nodeKey를 부모에 전달 */\n onDragStarted?: (nodeKey: string) => void;\n /** drag & drop 완료 후 이동된 블록의 nodeKey를 부모에 전달 */\n onDropped?: (nodeKey: string) => void;\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\nconst DRAG_DATA_FORMAT = \"application/x-lexical-drag-block\";\nconst BTN = 24;\nconst GAP = 4;\nconst GUTTER_LEFT = 6;\n// 버튼 그룹 전체 너비 (drag + add 기준): GUTTER_LEFT + 2*BTN + GAP\nconst TOOLBAR_WIDTH = GUTTER_LEFT + 2 * BTN + GAP; // 58px\n\n// ─── Inline styles ────────────────────────────────────────────────────────────\n\nconst btnBase: CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: 4,\n border: \"none\",\n background: \"transparent\",\n color: \"#a1a1aa\",\n cursor: \"pointer\",\n padding: 0,\n transition: \"background 80ms, color 80ms\",\n};\n\nconst btnHover: CSSProperties = {\n background: \"rgba(63,63,70,0.7)\",\n color: \"#e4e4e7\",\n};\n\n// ─── Icon map ─────────────────────────────────────────────────────────────────\n\nconst ICON_MAP: Record<string, React.ReactNode> = {\n paragraph: <Text size={13} strokeWidth={1.75} />,\n heading1: <Heading1 size={13} strokeWidth={1.75} />,\n heading2: <Heading2 size={13} strokeWidth={1.75} />,\n heading3: <Heading3 size={13} strokeWidth={1.75} />,\n bulletList: <List size={13} strokeWidth={1.75} />,\n orderedList: <ListOrdered size={13} strokeWidth={1.75} />,\n taskList: <ListTodo size={13} strokeWidth={1.75} />,\n quote: <Quote size={13} strokeWidth={1.75} />,\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/** blockEl의 top을 container 기준 절대 좌표로 계산 (getBoundingClientRect 기반) */\nfunction getOffsetTopToContainer(blockEl: HTMLElement, container: HTMLElement): number {\n const blockRect = blockEl.getBoundingClientRect();\n const containerRect = container.getBoundingClientRect();\n // container의 scrollTop도 반영 (container가 스크롤 가능한 경우)\n return blockRect.top - containerRect.top + container.scrollTop;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function BlockToolbar({\n isVisible,\n nodeKey,\n focusedNodeKey,\n items,\n editor,\n showDragHandle = true,\n showAddButton = true,\n onDragStarted,\n onDropped,\n}: BlockToolbarProps) {\n const [panelOpen, setPanelOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n const [container, setContainer] = useState<HTMLElement | null>(null);\n const [top, setTop] = useState<number | null>(null);\n const [dragHovered, setDragHovered] = useState(false);\n const [addHovered, setAddHovered] = useState(false);\n\n // 드래그 가이드 라인: container 기준 absolute top + 위치 (before/after)\n const [dropLine, setDropLine] = useState<{ top: number; position: \"before\" | \"after\" } | null>(null);\n // 커서 인디케이터: focusedNodeKey 블록의 위치\n const [focusedTop, setFocusedTop] = useState<number | null>(null);\n const [focusedHeight, setFocusedHeight] = useState<number>(0);\n\n const panelRef = useRef<HTMLDivElement>(null);\n const addBtnRef = useRef<HTMLButtonElement>(null);\n\n const numBtns = (showDragHandle ? 1 : 0) + (showAddButton ? 1 : 0);\n\n // ── 컨테이너 취득 ──────────────────────────────────────────────────────────\n\n useEffect(() => {\n return editor.registerRootListener((rootElement) => {\n setContainer(rootElement?.parentElement ?? null);\n });\n }, [editor]);\n\n // ── 수직 위치 계산 ─────────────────────────────────────────────────────────\n\n useEffect(() => {\n function calc() {\n if (!isVisible || !nodeKey || !container) { setTop(null); return; }\n const blockEl = editor.getElementByKey(nodeKey);\n if (!blockEl) { setTop(null); return; }\n const offset = getOffsetTopToContainer(blockEl, container);\n setTop(offset + (blockEl.offsetHeight - BTN) / 2);\n }\n calc();\n return editor.registerUpdateListener(() => calc());\n }, [isVisible, nodeKey, editor, container]);\n\n // ── 커서 인디케이터 위치 계산 ──────────────────────────────────────────────\n\n useEffect(() => {\n function calc() {\n if (!focusedNodeKey || !container) { setFocusedTop(null); return; }\n const blockEl = editor.getElementByKey(focusedNodeKey);\n if (!blockEl) { setFocusedTop(null); return; }\n setFocusedTop(getOffsetTopToContainer(blockEl, container));\n setFocusedHeight(blockEl.offsetHeight);\n }\n calc();\n const unregister = editor.registerUpdateListener(() => calc());\n return unregister;\n }, [focusedNodeKey, editor, container]);\n\n // ── 패널 닫기 ──────────────────────────────────────────────────────────────\n\n useEffect(() => { if (!panelOpen) setActiveIndex(0); }, [panelOpen]);\n useEffect(() => { if (!isVisible) setPanelOpen(false); }, [isVisible]);\n\n\n // 스크롤 시 패널 닫기\n useEffect(() => {\n if (!panelOpen) return;\n function onScroll() { setPanelOpen(false); }\n window.addEventListener(\"scroll\", onScroll, { passive: true, capture: true });\n return () => window.removeEventListener(\"scroll\", onScroll, { capture: true });\n }, [panelOpen]);\n\n useEffect(() => {\n if (!panelOpen) return;\n function onPointerDown(e: PointerEvent) {\n if (panelRef.current && !panelRef.current.contains(e.target as Node)) {\n setPanelOpen(false);\n }\n }\n window.addEventListener(\"pointerdown\", onPointerDown);\n return () => window.removeEventListener(\"pointerdown\", onPointerDown);\n }, [panelOpen]);\n\n useEffect(() => {\n if (!panelOpen) return;\n function onKeyDown(e: KeyboardEvent) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((p) => Math.min(p + 1, items.length - 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((p) => Math.max(p - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const item = items[activeIndex];\n if (item) { editor.focus(() => { item.onSelect(editor); setPanelOpen(false); }); }\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n setPanelOpen(false);\n }\n }\n window.addEventListener(\"keydown\", onKeyDown, { capture: true });\n return () => window.removeEventListener(\"keydown\", onKeyDown, { capture: true });\n }, [panelOpen, items, activeIndex, editor]);\n\n // ── Drag & Drop (가이드 라인 포함) ─────────────────────────────────────────\n\n useEffect(() => {\n if (!showDragHandle || !container) return;\n\n const unregDragover = editor.registerCommand(\n DRAGOVER_COMMAND,\n (event) => {\n if (!event.dataTransfer?.types.includes(DRAG_DATA_FORMAT)) return false;\n event.preventDefault();\n\n const draggedKey = event.dataTransfer.getData(DRAG_DATA_FORMAT);\n const target = event.target as HTMLElement | null;\n if (!target) return true;\n\n // 드래그 중인 element가 draggedNode의 DOM 안에 있으면 가이드라인 숨김\n if (draggedKey) {\n const draggedDOM = editor.getElementByKey(draggedKey);\n if (draggedDOM?.contains(target)) { setDropLine(null); return true; }\n }\n\n let targetKey: string | null = null;\n editor.read(() => {\n const node = $getNearestNodeFromDOMNode(target);\n if (!node) return;\n // 최상위 블록으로 올라감\n const top = node.isInline() ? node.getTopLevelElement() : node;\n if (top) targetKey = top.getKey();\n });\n if (!targetKey || targetKey === draggedKey) { setDropLine(null); return true; }\n\n const targetDOM = editor.getElementByKey(targetKey);\n if (!targetDOM) { setDropLine(null); return true; }\n\n const { top: domTop, height } = targetDOM.getBoundingClientRect();\n const isBefore = event.clientY < domTop + height / 2;\n const offsetTop = getOffsetTopToContainer(targetDOM, container);\n\n setDropLine({\n top: isBefore ? offsetTop : offsetTop + targetDOM.offsetHeight,\n position: isBefore ? \"before\" : \"after\",\n });\n\n return true;\n },\n COMMAND_PRIORITY_LOW,\n );\n\n const unregDrop = editor.registerCommand(\n DROP_COMMAND,\n (event) => {\n const draggedKey = event.dataTransfer?.getData(DRAG_DATA_FORMAT);\n if (!draggedKey) return false;\n event.preventDefault();\n setDropLine(null);\n\n const target = event.target as HTMLElement | null;\n if (!target) return true;\n\n // 실제로 노드가 이동되었는지 확인하기 위해 현재 에디터 상태에서 사전 검증\n let willMove = false;\n editor.read(() => {\n const draggedNode = $getNodeByKey(draggedKey);\n if (!draggedNode) return;\n const nearestNode = $getNearestNodeFromDOMNode(target);\n if (!nearestNode) return;\n const targetNode = nearestNode.isInline() ? nearestNode.getTopLevelElement() : nearestNode;\n if (!targetNode) return;\n const targetKey = targetNode.getKey();\n if (targetKey === draggedKey) return;\n if ($isElementNode(draggedNode) && draggedNode.getChildren().some(\n (child) => child.getKey() === targetKey,\n )) return;\n willMove = true;\n });\n\n editor.update(() => {\n const draggedNode = $getNodeByKey(draggedKey);\n if (!draggedNode) return;\n\n const nearestNode = $getNearestNodeFromDOMNode(target);\n if (!nearestNode) return;\n\n // 최상위 블록으로 정규화\n const targetNode = nearestNode.isInline()\n ? nearestNode.getTopLevelElement()\n : nearestNode;\n if (!targetNode) return;\n\n const targetKey = targetNode.getKey();\n // 자기 자신 또는 자기 자손으로 drop 방지\n if (targetKey === draggedKey) return;\n if ($isElementNode(draggedNode) && draggedNode.getChildren().some(\n (child) => child.getKey() === targetKey,\n )) return;\n\n const targetDOM = editor.getElementByKey(targetKey);\n if (!targetDOM) return;\n\n const { top: targetTop, height } = targetDOM.getBoundingClientRect();\n if (event.clientY < targetTop + height / 2) {\n targetNode.insertBefore(draggedNode);\n } else {\n targetNode.insertAfter(draggedNode);\n }\n });\n\n // Lexical의 drop 이벤트 처리(DOM selection 읽기)가 완료된 뒤에 onDropped 호출\n // requestAnimationFrame은 Lexical의 microtask 큐 처리 후에 실행됨\n if (willMove && onDropped) {\n requestAnimationFrame(() => onDropped(draggedKey));\n }\n return true;\n },\n COMMAND_PRIORITY_HIGH,\n );\n\n // 드래그가 에디터 밖으로 나가면 가이드 라인 제거\n function onDragEnd() { setDropLine(null); }\n document.addEventListener(\"dragend\", onDragEnd);\n\n return () => {\n unregDragover();\n unregDrop();\n document.removeEventListener(\"dragend\", onDragEnd);\n };\n }, [editor, showDragHandle, container, onDropped]);\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n if (!nodeKey) return;\n e.dataTransfer.setData(DRAG_DATA_FORMAT, nodeKey);\n e.dataTransfer.effectAllowed = \"move\";\n const blockEl = editor.getElementByKey(nodeKey);\n if (blockEl) e.dataTransfer.setDragImage(blockEl, 0, 0);\n onDragStarted?.(nodeKey);\n }, [nodeKey, editor, onDragStarted]);\n\n // ── + 버튼 클릭 ────────────────────────────────────────────────────────────\n\n const handleAddClick = useCallback((e: React.MouseEvent) => {\n e.preventDefault();\n setPanelOpen((p) => !p);\n }, []);\n\n // ── Render ─────────────────────────────────────────────────────────────────\n\n if (!container) return null;\n\n const dragBtnStyle: CSSProperties = {\n ...btnBase,\n width: BTN,\n height: BTN,\n cursor: \"grab\",\n ...(dragHovered ? btnHover : {}),\n };\n\n const addBtnStyle: CSSProperties = {\n ...btnBase,\n width: BTN,\n height: BTN,\n ...(panelOpen || addHovered ? btnHover : {}),\n };\n\n return (\n <>\n {/* 커서 인디케이터 — 현재 포커스 블록 왼쪽에 세로 라인 */}\n {createPortal(\n <AnimatePresence>\n {focusedTop !== null && (\n <motion.div\n key={focusedNodeKey ?? \"cursor-indicator\"}\n style={{\n position: \"absolute\",\n top: focusedTop,\n left: 0,\n width: 2,\n height: focusedHeight,\n background: \"#818cf8\", // indigo-400\n borderRadius: 1,\n zIndex: 2,\n pointerEvents: \"none\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.1 }}\n />\n )}\n </AnimatePresence>,\n container!,\n )}\n\n {/* 버튼 그룹 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {isVisible && top !== null && (\n <motion.div\n key={nodeKey ?? \"toolbar\"}\n data-block-toolbar\n style={{\n position: \"absolute\",\n top,\n left: GUTTER_LEFT,\n display: \"flex\",\n alignItems: \"center\",\n gap: GAP,\n width: numBtns * BTN + Math.max(0, numBtns - 1) * GAP,\n zIndex: 2,\n userSelect: \"none\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.08, ease: \"easeOut\" }}\n >\n {showDragHandle && (\n <div\n draggable\n data-drag-handle\n onDragStart={handleDragStart}\n aria-label=\"Drag to reorder\"\n style={dragBtnStyle}\n onMouseEnter={() => setDragHovered(true)}\n onMouseLeave={() => setDragHovered(false)}\n >\n <GripVertical size={14} strokeWidth={2} />\n </div>\n )}\n {showAddButton && (\n <button\n ref={addBtnRef}\n type=\"button\"\n aria-label=\"Add block\"\n aria-expanded={panelOpen}\n style={addBtnStyle}\n onMouseDown={handleAddClick}\n onMouseEnter={() => setAddHovered(true)}\n onMouseLeave={() => setAddHovered(false)}\n >\n <Plus size={13} strokeWidth={2.5} />\n </button>\n )}\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n\n {/* 드래그 가이드 라인 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {dropLine !== null && (\n <motion.div\n key=\"drop-line\"\n style={{\n position: \"absolute\",\n top: dropLine.top,\n // 텍스트 시작점(padding-left)에 맞춰 왼쪽 여백\n left: 0,\n right: 0,\n height: 2,\n zIndex: 3,\n pointerEvents: \"none\",\n // 가이드 라인: 파란 선 + 양쪽 원형 핸들\n display: \"flex\",\n alignItems: \"center\",\n // translateY로 선을 블록 경계에 정확히 정렬\n transform: \"translateY(-1px)\",\n }}\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.06 }}\n >\n {/* 왼쪽 원 */}\n <div style={{\n width: 6,\n height: 6,\n borderRadius: \"50%\",\n background: \"#818cf8\", // indigo-400\n flexShrink: 0,\n marginLeft: 4,\n }} />\n {/* 가로선 */}\n <div style={{\n flex: 1,\n height: 2,\n background: \"#818cf8\",\n marginRight: 8,\n }} />\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n\n {/* 패널 — container 내부, absolute */}\n {createPortal(\n <AnimatePresence>\n {isVisible && panelOpen && top !== null && (\n <motion.div\n ref={panelRef}\n data-block-toolbar\n style={{\n position: \"absolute\",\n top: top + BTN + GAP,\n left: GUTTER_LEFT,\n zIndex: 50,\n width: 240,\n }}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n role=\"dialog\"\n aria-label=\"Insert block\"\n className=\"rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5\"\n >\n <div role=\"listbox\" aria-label=\"Block type\" className=\"w-full px-1.5 flex flex-col\">\n {items.map((item, index) => {\n const icon = ICON_MAP[item.id] ?? item.icon;\n const isActive = index === activeIndex;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n onMouseDown={(e) => {\n e.preventDefault();\n editor.focus(() => { item.onSelect(editor); setPanelOpen(false); });\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={[\n \"w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75\",\n \"outline-none\",\n isActive\n ? \"bg-zinc-700/60 text-zinc-100\"\n : \"text-zinc-400 hover:bg-zinc-700/40 hover:text-zinc-200\",\n ].join(\" \")}\n >\n <span className=\"flex items-center justify-center shrink-0 w-4 text-current\">\n {icon}\n </span>\n <span className=\"text-sm font-normal leading-none\">{item.label}</span>\n </button>\n );\n })}\n </div>\n </motion.div>\n )}\n </AnimatePresence>,\n container,\n )}\n </>\n );\n}\n","\"use client\";\n\nimport type { LexicalEditor } from \"lexical\";\nimport type { SlashMenuItem } from \"@jikjo/core\";\nimport { Heading1, Heading2, Heading3, Quote, Text } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport type { ReactNode } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface SlashMenuProps {\n isVisible: boolean;\n query: string;\n items: SlashMenuItem[];\n editor: LexicalEditor;\n onClose: () => void;\n}\n\n// ─── Icon map ─────────────────────────────────────────────────────────────────\n\nconst ICON_MAP: Record<string, ReactNode> = {\n paragraph: <Text size={14} strokeWidth={1.75} />,\n heading1: <Heading1 size={14} strokeWidth={1.75} />,\n heading2: <Heading2 size={14} strokeWidth={1.75} />,\n heading3: <Heading3 size={14} strokeWidth={1.75} />,\n quote: <Quote size={14} strokeWidth={1.75} />,\n};\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\ninterface AbsolutePos {\n top: number;\n left: number;\n}\n\n/**\n * 캐럿(collapsed range)의 bottom 위치를 컨테이너 기준 absolute 좌표로 변환.\n */\nfunction getCaretAbsPos(container: HTMLElement): AbsolutePos | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n\n const range = sel.getRangeAt(0).cloneRange();\n range.collapse(true);\n const caretRect = range.getBoundingClientRect();\n\n // getBoundingClientRect()가 (0,0,0,0)을 반환하는 경우 방어\n if (caretRect.width === 0 && caretRect.height === 0 && caretRect.top === 0) return null;\n\n const cr = container.getBoundingClientRect();\n const GAP = 6;\n\n return {\n top: caretRect.bottom - cr.top + container.scrollTop + GAP,\n left: caretRect.left - cr.left + container.scrollLeft,\n };\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport function SlashMenu({\n isVisible,\n query,\n items,\n editor,\n onClose,\n}: SlashMenuProps) {\n const [activeIndex, setActiveIndex] = useState(0);\n const containerRef = useRef<HTMLDivElement>(null);\n const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);\n const [pos, setPos] = useState<AbsolutePos | null>(null);\n\n // editor.getRootElement()가 준비된 후 portalContainer를 설정\n useEffect(() => {\n function attachContainer() {\n const rootEl = editor.getRootElement();\n if (!rootEl) return;\n const parent = rootEl.parentElement;\n if (parent) setPortalContainer(parent);\n }\n attachContainer();\n return editor.registerRootListener(attachContainer);\n }, [editor]);\n\n const filtered = items.filter(\n (item) =>\n !query ||\n item.label.toLowerCase().includes(query.toLowerCase()) ||\n item.description.toLowerCase().includes(query.toLowerCase()),\n );\n\n // isVisible 또는 query 변경 시 좌표 재계산 (캐럿이 이동/입력될 때마다)\n useEffect(() => {\n if (!isVisible || !portalContainer) {\n setPos(null);\n return;\n }\n // registerUpdateListener가 발동된 직후 호출되므로 DOM이 확정된 시점\n setPos(getCaretAbsPos(portalContainer));\n }, [isVisible, query, portalContainer]);\n\n useEffect(() => {\n setActiveIndex(0);\n }, [query]);\n\n useEffect(() => {\n if (!isVisible) return;\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setActiveIndex((prev) => Math.min(prev + 1, filtered.length - 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setActiveIndex((prev) => Math.max(prev - 1, 0));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const item = filtered[activeIndex];\n if (!item) return;\n editor.focus(() => {\n item.onSelect(editor);\n onClose();\n });\n } else if (event.key === \"Escape\") {\n onClose();\n }\n }\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isVisible, filtered, activeIndex, editor, onClose]);\n\n useEffect(() => {\n const el = containerRef.current?.querySelector<HTMLElement>(\n '[data-active=\"true\"]',\n );\n el?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex]);\n\n if (!portalContainer) return null;\n\n const menuStyle = pos\n ? { position: \"absolute\" as const, top: pos.top, left: pos.left, zIndex: 50, width: 240 }\n : { position: \"absolute\" as const, top: -9999, left: -9999, zIndex: 50, width: 240 };\n\n return createPortal(\n <AnimatePresence>\n {isVisible && (\n <motion.div\n style={menuStyle}\n initial={{ opacity: 0, y: -4 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -4 }}\n transition={{ duration: 0.1, ease: \"easeOut\" }}\n role=\"dialog\"\n aria-label=\"Insert block\"\n className=\"rounded-lg bg-zinc-800 shadow-xl shadow-black/50 py-1.5 overflow-hidden\"\n >\n {filtered.length === 0 ? (\n <p className=\"px-4 py-2 text-xs text-zinc-500\">\n No results for “{query}”\n </p>\n ) : (\n <div\n ref={containerRef}\n role=\"listbox\"\n aria-label=\"Block type\"\n className=\"w-full max-h-72 overflow-y-auto px-1.5 flex flex-col\"\n >\n {filtered.map((item, index) => {\n const icon = ICON_MAP[item.id] ?? item.icon;\n const isActive = index === activeIndex;\n return (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={isActive}\n data-active={isActive}\n onMouseDown={(e) => {\n e.preventDefault();\n editor.focus(() => {\n item.onSelect(editor);\n onClose();\n });\n }}\n onMouseEnter={() => setActiveIndex(index)}\n className={[\n \"w-full flex items-center gap-3 px-3 py-2 rounded-md text-left transition-colors duration-75\",\n \"outline-none focus-visible:outline-none\",\n isActive\n ? \"bg-zinc-700/80 text-zinc-100\"\n : \"text-zinc-400 hover:bg-zinc-700/60 hover:text-zinc-200 focus-visible:bg-zinc-700/80 focus-visible:text-zinc-100\",\n ].join(\" \")}\n >\n <span className=\"flex items-center justify-center shrink-0 w-4 text-current\">\n {icon}\n </span>\n <span className=\"text-sm font-normal leading-none\">\n {item.label}\n </span>\n </button>\n );\n })}\n </div>\n )}\n </motion.div>\n )}\n </AnimatePresence>,\n portalContainer,\n );\n}\n","\"use client\";\n\nimport {\n Editor,\n historyExtension,\n richTextExtension,\n useBlockHoverPlugin,\n useSelectionPlugin,\n useSlashCommandPlugin,\n} from \"@jikjo/core\";\nimport type { Extension, SlashMenuItem } from \"@jikjo/core\";\nimport {\n $createHeadingNode,\n $isHeadingNode,\n type HeadingTagType,\n} from \"@lexical/rich-text\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n $createParagraphNode,\n $getNearestNodeFromDOMNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\nimport {\n Bold,\n Code,\n Heading1,\n Heading2,\n Heading3,\n Italic,\n Strikethrough,\n Underline,\n} from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { MouseEvent } from \"react\";\nimport { BubbleMenu } from \"./components/bubble-menu\";\nimport { BlockToolbar } from \"./components/block-toolbar\";\nimport { SlashMenu } from \"./components/slash-menu\";\n\n// ─── Styles ───────────────────────────────────────────────────────────────────\n\nconst tbtnBase: CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n width: 32,\n height: 32,\n borderRadius: 6,\n border: \"none\",\n background: \"transparent\",\n color: \"#71717a\",\n cursor: \"pointer\",\n padding: 0,\n transition: \"background 100ms, color 100ms\",\n flexShrink: 0,\n};\n\nconst tbtnHover: CSSProperties = {\n background: \"rgba(39,39,42,0.9)\",\n color: \"#e4e4e7\",\n};\n\nconst tbtnActive: CSSProperties = {\n background: \"rgba(63,63,70,0.8)\",\n color: \"#f4f4f5\",\n};\n\n// ─── ToolbarButton ─────────────────────────────────────────────────────────────\n\ninterface ToolbarButtonProps {\n label: string;\n isActive?: boolean;\n onMouseDown: (e: MouseEvent) => void;\n children: ReactNode;\n}\n\nfunction ToolbarButton({ label, isActive, onMouseDown, children }: ToolbarButtonProps) {\n const [hovered, setHovered] = useState(false);\n\n const style: CSSProperties = {\n ...tbtnBase,\n ...(hovered ? tbtnHover : {}),\n ...(isActive ? tbtnActive : {}),\n };\n\n return (\n <button\n type=\"button\"\n aria-label={label}\n onMouseDown={onMouseDown}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n style={style}\n >\n {children}\n </button>\n );\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface EditorUIProps {\n extensions?: Extension[];\n namespace?: string;\n /**\n * undefined → default toolbar\n * false → no toolbar rendered at all\n * ReactNode → custom toolbar content\n */\n toolbarContent?: ReactNode | false;\n className?: string;\n /**\n * 활성화할 UI 기능 목록. 기본값: 모두 활성화.\n * \"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\"\n */\n features?: Array<\"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\">;\n}\n\n// ─── Default extensions ───────────────────────────────────────────────────────\n\nconst defaultExtensions: Extension[] = [richTextExtension, historyExtension];\n\n// ─── Inner component ──────────────────────────────────────────────────────────\n// Must live inside LexicalComposer so all hooks have editor context.\n\nconst ALL_FEATURES = [\"blockHandle\", \"inlineAdd\", \"slashCommand\", \"bubbleMenu\"] as const;\n\ninterface EditorInnerProps {\n extensions: Extension[];\n toolbarContent: ReactNode | false | undefined;\n features: Array<\"blockHandle\" | \"inlineAdd\" | \"slashCommand\" | \"bubbleMenu\">;\n}\n\nfunction EditorInner({\n extensions,\n toolbarContent,\n features,\n}: EditorInnerProps) {\n const [editor] = useLexicalComposerContext();\n\n const selection = useSelectionPlugin();\n const slashCommand = useSlashCommandPlugin();\n const blockHover = useBlockHoverPlugin();\n\n // heading 태그는 Lexical selection으로 추적 (toolbar 표시용)\n const [currentHeadingTag, setCurrentHeadingTag] = useState<HeadingTagType | null>(null);\n\n // focusedNodeKey: Lexical selection에서 완전히 독립.\n // - 에디터 click → DOM에서 직접 nodeKey 읽기\n // - dragstart → onDragStarted 콜백으로 nodeKey 전달\n // - drop 성공 → onDropped 콜백으로 이동된 nodeKey 전달\n // - dragend(취소) → null\n // - 에디터 바깥 클릭 → null\n const [focusedNodeKey, setFocusedNodeKey] = useState<string | null>(null);\n\n // drop 성공 플래그 (dragend 시 취소인지 성공인지 구분용)\n const dropSucceededRef = useRef(false);\n\n // ── heading tag 추적 (selection 기반, toolbar 전용) ──────────────────────\n\n useEffect(() => {\n return editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel)) {\n setCurrentHeadingTag(null);\n return;\n }\n const topNode = sel.anchor.getNode().getTopLevelElement();\n setCurrentHeadingTag($isHeadingNode(topNode) ? topNode.getTag() : null);\n });\n });\n }, [editor]);\n\n // ── focusedNodeKey: 에디터 click 이벤트에서 DOM → nodeKey 직접 읽기 ──────\n\n useEffect(() => {\n return editor.registerRootListener((rootElement) => {\n if (!rootElement) return;\n const root = rootElement;\n\n function onClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n let key: string | null = null;\n editor.read(() => {\n const node = $getNearestNodeFromDOMNode(target);\n if (!node) return;\n const top = node.isInline() ? node.getTopLevelElement() : node;\n if (top) key = top.getKey();\n });\n if (key) setFocusedNodeKey(key);\n }\n\n // 에디터/툴바 바깥 클릭 시 커서 인디케이터 제거\n function onDocClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (root.contains(target)) return;\n if (target?.closest(\"[data-block-toolbar]\") || target?.closest(\"[data-drag-handle]\")) return;\n setFocusedNodeKey(null);\n }\n\n root.addEventListener(\"click\", onClick);\n document.addEventListener(\"click\", onDocClick, true);\n\n return () => {\n root.removeEventListener(\"click\", onClick);\n document.removeEventListener(\"click\", onDocClick, true);\n };\n });\n }, [editor]);\n\n // ── drag/drop 이벤트에서 focusedNodeKey 관리 ────────────────────────────\n // Firefox: drag handle mousedown → blur → dragstart 순서로 발생.\n // blur 시 Lexical이 $setSelection(null)을 호출하지만,\n // focusedNodeKey는 Lexical selection과 독립이므로 영향받지 않음.\n // drop 성공/취소는 onDropped / dragend 이벤트로 구분.\n\n const handleDragStarted = useCallback((key: string) => {\n dropSucceededRef.current = false;\n setFocusedNodeKey(key);\n // Firefox blur로 에디터 포커스가 빠진 경우 복구\n editor.focus();\n }, [editor]);\n\n const handleDropped = useCallback((key: string) => {\n dropSucceededRef.current = true;\n setFocusedNodeKey(key);\n // drop 후 에디터 포커스 복구\n editor.focus();\n }, [editor]);\n\n useEffect(() => {\n function onDragEnd() {\n if (!dropSucceededRef.current) {\n // drag 취소: 커서 인디케이터 제거\n setFocusedNodeKey(null);\n }\n dropSucceededRef.current = false;\n }\n document.addEventListener(\"dragend\", onDragEnd);\n return () => document.removeEventListener(\"dragend\", onDragEnd);\n }, []);\n\n // Collect slash menu items from all registered extensions\n const slashMenuItems = useMemo<SlashMenuItem[]>(\n () => extensions.flatMap((ext) => ext.slashMenuItems ?? []),\n [extensions],\n );\n\n // Toggle heading: if already at this level, revert to paragraph.\n // newNode.select() preserves selection after replace to avoid Lexical error.\n const toggleHeading = useCallback(\n (tag: HeadingTagType) => {\n editor.update(() => {\n const sel = $getSelection();\n if (!$isRangeSelection(sel)) return;\n const topNode = sel.anchor.getNode().getTopLevelElementOrThrow();\n const newNode = ($isHeadingNode(topNode) && topNode.getTag() === tag)\n ? $createParagraphNode()\n : $createHeadingNode(tag);\n topNode.replace(newNode);\n newNode.select();\n });\n },\n [editor],\n );\n\n return (\n <>\n {/* ── Toolbar ──────────────────────────────────────────────────── */}\n {toolbarContent === false ? null : toolbarContent !== undefined ? (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2, padding: \"8px 16px\", borderBottom: \"1px solid rgba(39,39,42,0.5)\" }}>\n {toolbarContent}\n </div>\n ) : (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2, padding: \"8px 16px\", borderBottom: \"1px solid rgba(39,39,42,0.5)\" }}>\n {/* Format group */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <ToolbarButton\n label=\"Bold\"\n isActive={selection.format.bold}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"bold\"); }}\n >\n <Bold size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Italic\"\n isActive={selection.format.italic}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"italic\"); }}\n >\n <Italic size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Underline\"\n isActive={selection.format.underline}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"underline\"); }}\n >\n <Underline size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Strikethrough\"\n isActive={selection.format.strikethrough}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"strikethrough\"); }}\n >\n <Strikethrough size={14} strokeWidth={2.5} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Code\"\n isActive={selection.format.code}\n onMouseDown={(e) => { e.preventDefault(); selection.toggleFormat(\"code\"); }}\n >\n <Code size={14} strokeWidth={2.5} />\n </ToolbarButton>\n </div>\n\n <div style={{ width: 1, height: 16, background: \"#27272a\", margin: \"0 4px\" }} />\n\n {/* Heading group */}\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 2 }}>\n <ToolbarButton\n label=\"Heading 1\"\n isActive={currentHeadingTag === \"h1\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h1\"); }}\n >\n <Heading1 size={15} strokeWidth={2} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Heading 2\"\n isActive={currentHeadingTag === \"h2\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h2\"); }}\n >\n <Heading2 size={15} strokeWidth={2} />\n </ToolbarButton>\n <ToolbarButton\n label=\"Heading 3\"\n isActive={currentHeadingTag === \"h3\"}\n onMouseDown={(e) => { e.preventDefault(); toggleHeading(\"h3\"); }}\n >\n <Heading3 size={15} strokeWidth={2} />\n </ToolbarButton>\n </div>\n </div>\n )}\n\n {/* ── Floating overlays ────────────────────────────────────────── */}\n {features.includes(\"bubbleMenu\") && (\n <BubbleMenu\n isVisible={selection.isActive}\n format={selection.format}\n onToggleFormat={selection.toggleFormat}\n editor={editor}\n />\n )}\n\n {features.includes(\"slashCommand\") && (\n <SlashMenu\n isVisible={slashCommand.isActive}\n query={slashCommand.query}\n items={slashMenuItems}\n editor={editor}\n onClose={slashCommand.close}\n />\n )}\n\n {/* drag handle + + 버튼 + 커서 인디케이터 */}\n <BlockToolbar\n isVisible={blockHover.isActive && (features.includes(\"blockHandle\") || features.includes(\"inlineAdd\"))}\n nodeKey={blockHover.nodeKey}\n focusedNodeKey={focusedNodeKey}\n items={slashMenuItems}\n editor={editor}\n showDragHandle={features.includes(\"blockHandle\")}\n showAddButton={features.includes(\"inlineAdd\")}\n onDragStarted={handleDragStarted}\n onDropped={handleDropped}\n />\n </>\n );\n}\n\n// ─── Public component ─────────────────────────────────────────────────────────\n\nexport function EditorUI({\n extensions = defaultExtensions,\n namespace = \"jikjo\",\n toolbarContent,\n className,\n features = [...ALL_FEATURES],\n}: EditorUIProps) {\n const hasBlockToolbar = features.includes(\"blockHandle\") || features.includes(\"inlineAdd\");\n return (\n <div\n className={className}\n {...(hasBlockToolbar ? { \"data-has-block-toolbar\": \"\" } : {})}\n // block toolbar가 있을 때만 좌측 74px padding을 주입\n // (GUTTER_LEFT 6 + 2×BTN 48 + GAP 4 = 58px toolbar + 16px text padding)\n style={hasBlockToolbar ? ({\n \"--jikjo-content-pl\": \"74px\",\n \"--jikjo-placeholder-pl\": \"74px\",\n } as React.CSSProperties) : ({\n \"--jikjo-content-pl\": \"16px\",\n \"--jikjo-placeholder-pl\": \"16px\",\n } as React.CSSProperties)}\n >\n <Editor extensions={extensions} namespace={namespace}>\n <EditorInner\n extensions={extensions}\n toolbarContent={toolbarContent}\n features={features}\n />\n </Editor>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-normal:400;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.fixed{position:fixed}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-0\.5{margin-inline:calc(var(--spacing) * .5)}.block{display:block}.flex{display:flex}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-8{height:calc(var(--spacing) * 8)}.max-h-72{max-height:calc(var(--spacing) * 72)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-8{width:calc(var(--spacing) * 8)}.w-full{width:100%}.w-px{width:1px}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-grab{cursor:grab}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-3{gap:calc(var(--spacing) * 3)}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.bg-white\/15{background-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.bg-white\/15{background-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}.bg-zinc-600\/50{background-color:#52525c80}@supports (color:color-mix(in lab, red, red)){.bg-zinc-600\/50{background-color:color-mix(in oklab, var(--color-zinc-600) 50%, transparent)}}.bg-zinc-700\/60{background-color:#3f3f4699}@supports (color:color-mix(in lab, red, red)){.bg-zinc-700\/60{background-color:color-mix(in oklab, var(--color-zinc-700) 60%, transparent)}}.bg-zinc-700\/80{background-color:#3f3f46cc}@supports (color:color-mix(in lab, red, red)){.bg-zinc-700\/80{background-color:color-mix(in oklab, var(--color-zinc-700) 80%, transparent)}}.bg-zinc-800{background-color:var(--color-zinc-800)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.text-left{text-align:left}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.text-current{color:currentColor}.text-white{color:var(--color-white)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.italic{font-style:italic}.underline{text-decoration-line:underline}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-black\/50{--tw-shadow-color:#00000080}@supports (color:color-mix(in lab, red, red)){.shadow-black\/50{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-black) 50%, transparent) var(--tw-shadow-alpha), transparent)}}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-100{--tw-duration:.1s;transition-duration:.1s}.outline-none{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.hover\:bg-zinc-700\/40:hover{background-color:#3f3f4666}@supports (color:color-mix(in lab, red, red)){.hover\:bg-zinc-700\/40:hover{background-color:color-mix(in oklab, var(--color-zinc-700) 40%, transparent)}}.hover\:bg-zinc-700\/60:hover{background-color:#3f3f4699}@supports (color:color-mix(in lab, red, red)){.hover\:bg-zinc-700\/60:hover{background-color:color-mix(in oklab, var(--color-zinc-700) 60%, transparent)}}.hover\:bg-zinc-700\/80:hover{background-color:#3f3f46cc}@supports (color:color-mix(in lab, red, red)){.hover\:bg-zinc-700\/80:hover{background-color:color-mix(in oklab, var(--color-zinc-700) 80%, transparent)}}.hover\:text-zinc-200:hover{color:var(--color-zinc-200)}.hover\:text-zinc-300:hover{color:var(--color-zinc-300)}}.focus-visible\:bg-zinc-700\/80:focus-visible{background-color:#3f3f46cc}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-zinc-700\/80:focus-visible{background-color:color-mix(in oklab, var(--color-zinc-700) 80%, transparent)}}.focus-visible\:text-zinc-100:focus-visible{color:var(--color-zinc-100)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:cursor-grabbing:active{cursor:grabbing}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jikjo/ui-kit",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Styled editor UI components for jikjo",
|
|
6
6
|
"keywords": [
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"types": "./dist/index.d.cts",
|
|
25
25
|
"default": "./dist/index.cjs"
|
|
26
26
|
}
|
|
27
|
-
}
|
|
27
|
+
},
|
|
28
|
+
"./styles.css": "./dist/styles.css"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"dist"
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"lucide-react": "^0.575.0",
|
|
34
35
|
"tailwind-merge": "^3.5.0",
|
|
35
36
|
"tailwind-variants": "^3.2.2",
|
|
36
|
-
"@jikjo/core": "0.
|
|
37
|
+
"@jikjo/core": "0.2.0"
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
40
|
"@base-ui/react": "^1.0.0",
|
|
@@ -48,18 +49,20 @@
|
|
|
48
49
|
"@base-ui/react": "^1.2.0",
|
|
49
50
|
"@lexical/react": "^0.38.2",
|
|
50
51
|
"@lexical/rich-text": "^0.38.2",
|
|
52
|
+
"@tailwindcss/cli": "^4.2.1",
|
|
51
53
|
"@types/react": "^19.1.8",
|
|
52
54
|
"@types/react-dom": "^19.1.6",
|
|
53
55
|
"lexical": "^0.38.2",
|
|
54
56
|
"motion": "^11.18.2",
|
|
55
57
|
"react": "^19.2.0",
|
|
56
58
|
"react-dom": "^19.2.0",
|
|
59
|
+
"tailwindcss": "^4.2.0",
|
|
57
60
|
"tsdown": "^0.12.7",
|
|
58
61
|
"typescript": "5.9.2",
|
|
59
62
|
"@jikjo/typescript-config": "0.0.0"
|
|
60
63
|
},
|
|
61
64
|
"scripts": {
|
|
62
|
-
"build": "tsdown",
|
|
65
|
+
"build": "tsdown && node_modules/.bin/tailwindcss -i ./src/styles.css -o ./dist/styles.css --minify",
|
|
63
66
|
"dev": "tsdown --watch",
|
|
64
67
|
"check-types": "tsc --noEmit"
|
|
65
68
|
}
|