@domternal/react 0.15.0 → 1.0.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/README.md CHANGED
@@ -16,10 +16,10 @@ and React 19.
16
16
  ## Install
17
17
 
18
18
  ```bash
19
- pnpm add @domternal/react @domternal/core react react-dom
19
+ pnpm add @domternal/react @domternal/core @domternal/theme react react-dom
20
20
  ```
21
21
 
22
- `react` (>=18), `react-dom` (>=18), and `@domternal/core` (>=0.15.0) are peer dependencies. Import the theme
22
+ `react` (>=18), `react-dom` (>=18), and `@domternal/core` (>=1.0.0) are peer dependencies. Import the theme
23
23
  ([`@domternal/theme`](https://www.npmjs.com/package/@domternal/theme)) in your entry CSS for default styling:
24
24
 
25
25
  ```css
@@ -73,13 +73,35 @@ export default function Editor() {
73
73
  }
74
74
  ```
75
75
 
76
+ ## Options
77
+
78
+ `useEditor(options, deps?)` and `<Domternal>` take the same options:
79
+
80
+ | Option | Type | Default | Description |
81
+ |---|---|---|---|
82
+ | `extensions` | `AnyExtension[]` | `[]` | Extensions to load on top of `DEFAULT_EXTENSIONS`. |
83
+ | `history` | `boolean` | `true` | Whether the built-in History extension is loaded. Turn it off when an extension brings its own undo. |
84
+ | `content` | `Content` | `''` | Initial content, HTML string or JSON. Changing it syncs the editor. |
85
+ | `editable` | `boolean` | `true` | Whether the editor is editable. |
86
+ | `preset` | `'classic' \| 'notion'` | `'classic'` | `'notion'` paints `dm-notion-mode` on the `.dm-editor` host and switches preset-aware extensions to their Notion behavior. Create-time only. |
87
+ | `autofocus` | `FocusPosition` | `false` | Where to place the caret on mount. |
88
+ | `outputFormat` | `'html' \| 'json'` | `'html'` | Format used when comparing the `content` prop against the document. |
89
+ | `immediatelyRender` | `boolean` | `false` | Create the editor during the first render, so `editor` is never null. Throws in SSR. |
90
+
91
+ `onCreate`, `onUpdate`, `onSelectionChange`, `onFocus`, `onBlur`, and `onDestroy` callbacks are
92
+ accepted alongside them. `<Domternal>` additionally takes `deps`, the dependency array
93
+ `useEditor` reads as its second argument: change a value in it and the editor is rebuilt.
94
+
76
95
  ## Exports
77
96
 
78
- - **Hooks**: `useEditor`, `useEditorState` (full state or a granular selector), `useCurrentEditor`
97
+ - **Hooks**: `useEditor`, `useEditorState` (full state or a granular selector), `useCurrentEditor`,
98
+ `useNotionColorPicker`
79
99
  - **Constants**: `DEFAULT_EXTENSIONS`
80
100
  - **Composable component**: `Domternal` with `Domternal.Toolbar`, `Domternal.Content`, `Domternal.BubbleMenu`,
81
101
  `Domternal.FloatingMenu`, `Domternal.EmojiPicker`, `Domternal.Loading`
82
102
  - **Components**: `DomternalEditor`, `EditorContent`, `DomternalToolbar`, `DomternalBubbleMenu`,
83
103
  `DomternalFloatingMenu`, `DomternalEmojiPicker`, `DomternalNotionColorPicker`, `EditorProvider`
84
104
  - **Node views**: `ReactNodeViewRenderer`, `NodeViewWrapper`, `NodeViewContent`, `useReactNodeView`
105
+ - **Core re-exports**: the `Editor` class plus the `Content`, `AnyExtension`, `FocusPosition`,
106
+ and `JSONContent` types
85
107
  - **SSR helpers** (re-exported from core, work without an editor instance): `generateHTML`, `generateJSON`, `generateText`
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { DependencyList, ReactNode, HTMLAttributes, Ref, RefObject, KeyboardEvent, ElementType, RefCallback } from 'react';
3
- import { AnyExtension, Content, EditorPreset, FocusPosition, Editor, JSONContent, IconSet, ToolbarLayoutEntry, BubbleMenuOptions, FloatingMenuOptions, FloatingMenuItemsOverride, FloatingMenuKeymap } from '@domternal/core';
3
+ import { AnyExtension, Content, EditorPreset, FocusPosition, Editor, JSONContent, IconSet, ToolbarLayoutEntry, BubbleMenuOptions, BubbleContexts, FloatingMenuOptions, FloatingMenuItemsOverride, FloatingMenuKeymap } from '@domternal/core';
4
4
  export { AnyExtension, Content, Editor, FocusPosition, GenerateHTMLOptions, GenerateJSONOptions, GenerateTextOptions, JSONContent, generateHTML, generateJSON, generateText } from '@domternal/core';
5
5
 
6
6
  declare const DEFAULT_EXTENSIONS: AnyExtension[];
@@ -177,7 +177,7 @@ interface DomternalBubbleMenuProps {
177
177
  /** Fixed item names, e.g. ['bold', 'italic', 'code']. */
178
178
  items?: string[];
179
179
  /** Context-aware: map context names to item arrays, true for all, or null to disable. */
180
- contexts?: Record<string, string[] | true | null>;
180
+ contexts?: BubbleContexts;
181
181
  /** Custom icon overrides. Falls back to default Phosphor icons for unmapped keys. */
182
182
  icons?: IconSet;
183
183
  /** Additional content rendered after buttons. */
@@ -230,7 +230,7 @@ interface DomternalEmojiPickerProps {
230
230
  /** Array of emoji items with emoji, name, and group. */
231
231
  emojis: EmojiPickerItem[];
232
232
  }
233
- declare function DomternalEmojiPicker({ editor: editorProp, emojis }: DomternalEmojiPickerProps): ReactNode;
233
+ declare function DomternalEmojiPicker({ editor: editorProp, emojis, }: DomternalEmojiPickerProps): ReactNode;
234
234
 
235
235
  interface DomternalProps extends UseEditorOptions {
236
236
  /** Optional dependency array for forced editor recreation. */
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, forwardRef, useImperativeHandle, useRef, useEffect, useState, useMemo, useCallback, useSyncExternalStore, useContext, Fragment, useLayoutEffect, createElement } from 'react';
2
- import { Document, Paragraph, Text, BaseKeymap, History, Editor, refocusEditorAfterCommand, positionFloatingOnce, PluginKey, createFloatingMenuPlugin, FloatingMenuController, defaultIcons, positionFloating, ToolbarController, defaultBubbleContexts, createBubbleMenuPlugin } from '@domternal/core';
2
+ import { Document, Paragraph, Text, BaseKeymap, History, Editor, refocusEditorAfterCommand, positionFloatingOnce, PluginKey, createFloatingMenuPlugin, FloatingMenuController, defaultIcons, positionFloating, ToolbarController, defaultBubbleContexts, buildBubbleItemMaps, createBubbleShouldShow, createBubbleMenuPlugin, resolveBubbleNames, resolveBubbleMenuItems } from '@domternal/core';
3
3
  export { Editor, generateHTML, generateJSON, generateText } from '@domternal/core';
4
4
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
5
5
  import { createPortal } from 'react-dom';
@@ -947,20 +947,6 @@ var INITIAL_TRAILING_STATE = {
947
947
  currentBgColorVar: null,
948
948
  hasAnyColor: false
949
949
  };
950
- function isInsideTableCell($pos) {
951
- for (let d = $pos.depth; d > 0; d--) {
952
- const name = $pos.node(d).type.name;
953
- if (name === "tableCell" || name === "tableHeader") return true;
954
- }
955
- return false;
956
- }
957
- function findCellNode(pos) {
958
- for (let d = pos.depth; d > 0; d--) {
959
- const node = pos.node(d);
960
- if (node.type.name === "tableCell" || node.type.name === "tableHeader") return node;
961
- }
962
- return null;
963
- }
964
950
  function useBubbleMenu(options) {
965
951
  const { editor, shouldShow, placement = "top", offset = 8, updateDelay = 0, items, contexts: explicitContexts, icons } = options;
966
952
  const contexts = explicitContexts ?? (items ? void 0 : editor ? defaultBubbleContexts(editor) : void 0);
@@ -971,8 +957,6 @@ function useBubbleMenu(options) {
971
957
  const [trailing, setTrailing] = useState(INITIAL_TRAILING_STATE);
972
958
  const activeMapRef = useRef(/* @__PURE__ */ new Map());
973
959
  const disabledMapRef = useRef(/* @__PURE__ */ new Map());
974
- const itemMapRef = useRef(/* @__PURE__ */ new Map());
975
- const bubbleDefaultsRef = useRef(/* @__PURE__ */ new Map());
976
960
  const resolvedItemsRef = useRef([]);
977
961
  const editorRef = useRef(editor);
978
962
  editorRef.current = editor;
@@ -981,127 +965,8 @@ function useBubbleMenu(options) {
981
965
  const exts = editor.extensionManager.extensions;
982
966
  const hasNotionColorPicker = exts.some((e) => e.name === "notionColorPicker");
983
967
  const hasBlockContextMenu = exts.some((e) => e.name === "blockContextMenu");
984
- const itemMap = /* @__PURE__ */ new Map();
985
- const dropdownMap = /* @__PURE__ */ new Map();
986
- for (const item of editor.toolbarItems) {
987
- if (item.type === "button") {
988
- itemMap.set(item.name, item);
989
- } else if (item.type === "dropdown") {
990
- dropdownMap.set(item.name, item);
991
- for (const sub of item.items) {
992
- itemMap.set(sub.name, sub);
993
- }
994
- }
995
- }
996
- itemMapRef.current = itemMap;
997
- const bubbleDefaults = /* @__PURE__ */ new Map();
998
- const byCtx = /* @__PURE__ */ new Map();
999
- const addItem = (btn) => {
1000
- const ctx = btn["bubbleMenu"];
1001
- if (!ctx) return;
1002
- let arr = byCtx.get(ctx);
1003
- if (!arr) {
1004
- arr = [];
1005
- byCtx.set(ctx, arr);
1006
- }
1007
- arr.push(btn);
1008
- };
1009
- for (const item of editor.toolbarItems) {
1010
- if (item.type === "button") addItem(item);
1011
- else if (item.type === "dropdown") {
1012
- for (const sub of item.items) addItem(sub);
1013
- }
1014
- }
1015
- for (const [ctx, ctxItems] of byCtx) {
1016
- ctxItems.sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
1017
- const result = [];
1018
- let lastGroup;
1019
- let sepIdx = 0;
1020
- for (const item of ctxItems) {
1021
- if (lastGroup !== void 0 && item.group !== lastGroup) {
1022
- result.push({ type: "separator", name: `bsep-${String(sepIdx++)}` });
1023
- }
1024
- result.push(item);
1025
- lastGroup = item.group;
1026
- }
1027
- bubbleDefaults.set(ctx, result);
1028
- }
1029
- bubbleDefaultsRef.current = bubbleDefaults;
1030
- const resolveNames = (names) => {
1031
- const result = [];
1032
- let sepIdx = 0;
1033
- for (const name of names) {
1034
- if (name === "|") {
1035
- result.push({ type: "separator", name: `sep-${String(sepIdx++)}` });
1036
- } else {
1037
- const dropdown = dropdownMap.get(name);
1038
- if (dropdown) {
1039
- result.push(dropdown);
1040
- continue;
1041
- }
1042
- const item = itemMap.get(name);
1043
- if (item) result.push(item);
1044
- }
1045
- }
1046
- return result;
1047
- };
1048
- const getFormatItems = () => {
1049
- return Array.from(itemMap.values()).filter((item) => item.group === "format").sort((a, b) => (b.priority ?? 100) - (a.priority ?? 100));
1050
- };
1051
- const detectContext = (selection, ctxs) => {
1052
- if ("$anchorCell" in selection) return null;
1053
- if (selection.node) return selection.node.type.name;
1054
- if (selection.empty) return null;
1055
- const fromCell = findCellNode(selection.$from);
1056
- if (fromCell) {
1057
- const toCell = findCellNode(selection.$to);
1058
- if (toCell && fromCell !== toCell) return null;
1059
- return "table";
1060
- }
1061
- const fromName = selection.$from.parent.type.name;
1062
- if (fromName in ctxs) return fromName;
1063
- if ("text" in ctxs && selection.$from.parent.type.spec.marks !== "") return "text";
1064
- const toName = selection.$to.parent.type.name;
1065
- if (toName in ctxs) return toName;
1066
- if ("text" in ctxs && selection.$to.parent.type.spec.marks !== "") return "text";
1067
- return null;
1068
- };
1069
- const filterBySchema = (contextName, schemaItems) => {
1070
- if (contextName === "text" || contextName === "table") return schemaItems;
1071
- const schema = editor.state.schema;
1072
- if (!schema) return schemaItems;
1073
- const nodeType = schema.nodes[contextName];
1074
- if (!nodeType) return schemaItems;
1075
- return schemaItems.filter((item) => {
1076
- const markName = typeof item.isActive === "string" ? item.isActive : null;
1077
- if (!markName) return true;
1078
- const markType = schema.marks[markName];
1079
- if (!markType) return true;
1080
- return nodeType.allowsMarkType(markType);
1081
- });
1082
- };
1083
- let shouldShowFn = shouldShow;
1084
- if (!shouldShowFn) {
1085
- if (contexts) {
1086
- shouldShowFn = ({ state }) => {
1087
- const context = detectContext(state.selection, contexts);
1088
- if (!context) return false;
1089
- if (context in contexts) {
1090
- const val = contexts[context];
1091
- if (val === null) return false;
1092
- return val === true || Array.isArray(val) && val.length > 0;
1093
- }
1094
- return bubbleDefaults.has(context);
1095
- };
1096
- } else {
1097
- shouldShowFn = ({ state }) => {
1098
- if (state.selection.empty) return false;
1099
- if (state.selection.node) return bubbleDefaults.has(state.selection.node.type.name);
1100
- if (isInsideTableCell(state.selection.$from)) return false;
1101
- return state.selection.$from.parent.type.spec.marks !== "" || state.selection.$to.parent.type.spec.marks !== "";
1102
- };
1103
- }
1104
- }
968
+ const maps = buildBubbleItemMaps(editor);
969
+ const shouldShowFn = shouldShow ?? createBubbleShouldShow(maps, contexts);
1105
970
  const pluginKey = pluginKeyRef.current;
1106
971
  const plugin = createBubbleMenuPlugin({
1107
972
  pluginKey,
@@ -1117,13 +982,15 @@ function useBubbleMenu(options) {
1117
982
  resolvedItemsRef.current = newItems;
1118
983
  setResolvedItems(newItems);
1119
984
  };
1120
- if (contexts) {
1121
- updateContextItems(editor, contexts, detectContext, resolveNames, getFormatItems, filterBySchema, bubbleDefaults, setItems);
1122
- } else if (items) {
1123
- setItems(resolveNames(items));
1124
- } else {
1125
- setItems(resolveNames(["bold", "italic", "underline"]));
1126
- }
985
+ const fallbackItems = resolveBubbleNames(
986
+ items ?? ["bold", "italic", "underline"],
987
+ maps.itemMap,
988
+ maps.dropdownMap
989
+ );
990
+ const refreshItems = (ed) => {
991
+ setItems(resolveBubbleMenuItems({ editor: ed, maps, contexts, fallbackItems }));
992
+ };
993
+ refreshItems(editor);
1127
994
  const updateStates = (ed) => {
1128
995
  let canProxy = null;
1129
996
  try {
@@ -1148,7 +1015,6 @@ function useBubbleMenu(options) {
1148
1015
  trackButton(item);
1149
1016
  }
1150
1017
  };
1151
- const defaultItems = items ? resolveNames(items) : resolveNames(["bold", "italic", "underline"]);
1152
1018
  const syncTrailingState = (ed) => {
1153
1019
  const sel = ed.state.selection;
1154
1020
  const isNode = !!sel.node;
@@ -1185,16 +1051,7 @@ function useBubbleMenu(options) {
1185
1051
  });
1186
1052
  };
1187
1053
  const transactionHandler = () => {
1188
- if (contexts) {
1189
- updateContextItems(editor, contexts, detectContext, resolveNames, getFormatItems, filterBySchema, bubbleDefaults, setItems);
1190
- } else {
1191
- const sel = editor.state.selection;
1192
- if (sel.node && bubbleDefaults.has(sel.node.type.name)) {
1193
- setItems(bubbleDefaults.get(sel.node.type.name) ?? []);
1194
- } else {
1195
- setItems(defaultItems);
1196
- }
1197
- }
1054
+ refreshItems(editor);
1198
1055
  updateStates(editor);
1199
1056
  syncTrailingState(editor);
1200
1057
  setActiveVersion((v) => v + 1);
@@ -1209,30 +1066,6 @@ function useBubbleMenu(options) {
1209
1066
  }
1210
1067
  };
1211
1068
  }, [editor]);
1212
- function updateContextItems(ed, ctxs, detectContext, resolveNames, getFormatItems, filterBySchema, defaults, setItems) {
1213
- const ctx = detectContext(ed.state.selection, ctxs);
1214
- if (!ctx) {
1215
- setItems([]);
1216
- return;
1217
- }
1218
- if (ctx in ctxs) {
1219
- const val = ctxs[ctx];
1220
- if (val === null || Array.isArray(val) && val.length === 0) {
1221
- setItems([]);
1222
- return;
1223
- }
1224
- if (val === true) {
1225
- setItems(filterBySchema(ctx, getFormatItems()));
1226
- } else if (Array.isArray(val)) {
1227
- const resolved = resolveNames(val);
1228
- const buttons = resolved.filter((i) => i.type !== "separator");
1229
- const filtered = new Set(filterBySchema(ctx, buttons).map((b) => b.name));
1230
- setItems(resolved.filter((i) => i.type === "separator" || filtered.has(i.name)));
1231
- }
1232
- } else {
1233
- setItems(defaults.get(ctx) ?? []);
1234
- }
1235
- }
1236
1069
  const isItemActive = (item) => {
1237
1070
  return activeMapRef.current.get(item.name) ?? false;
1238
1071
  };
@@ -1334,6 +1167,8 @@ function DomternalBubbleMenu({
1334
1167
  const colorBtnRef = useRef(null);
1335
1168
  const blockMenuBtnRef = useRef(null);
1336
1169
  const innerHtml = useInnerHtml();
1170
+ const showColor = trailing.showColorPickerButton && !trailing.isNodeSelection;
1171
+ const showBlock = trailing.showBlockMenuButton && !trailing.isNodeSelection;
1337
1172
  return /* @__PURE__ */ jsxs("div", { ref: menuRef, className: "dm-bubble-menu", role: "toolbar", "aria-label": "Text formatting", children: [
1338
1173
  resolvedItems.map((item) => {
1339
1174
  if (item.type === "separator") {
@@ -1383,8 +1218,8 @@ function DomternalBubbleMenu({
1383
1218
  btn.name
1384
1219
  );
1385
1220
  }),
1386
- trailing.showColorPickerButton && !trailing.isNodeSelection && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1387
- /* @__PURE__ */ jsx("span", { className: "dm-toolbar-separator", role: "separator" }),
1221
+ showColor && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1222
+ resolvedItems.length > 0 && /* @__PURE__ */ jsx("span", { className: "dm-toolbar-separator", role: "separator" }),
1388
1223
  /* @__PURE__ */ jsxs(
1389
1224
  "button",
1390
1225
  {
@@ -1420,8 +1255,8 @@ function DomternalBubbleMenu({
1420
1255
  }
1421
1256
  )
1422
1257
  ] }),
1423
- trailing.showBlockMenuButton && !trailing.isNodeSelection && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1424
- /* @__PURE__ */ jsx("span", { className: "dm-toolbar-separator", role: "separator" }),
1258
+ showBlock && /* @__PURE__ */ jsxs(Fragment$1, { children: [
1259
+ (resolvedItems.length > 0 || showColor) && /* @__PURE__ */ jsx("span", { className: "dm-toolbar-separator", role: "separator" }),
1425
1260
  /* @__PURE__ */ jsx(
1426
1261
  "button",
1427
1262
  {
@@ -1957,10 +1792,10 @@ var CATEGORY_ICONS = {
1957
1792
  "Animals & Nature": "\u{1F431}",
1958
1793
  "Food & Drink": "\u{1F355}",
1959
1794
  "Travel & Places": "\u{1F697}",
1960
- "Activities": "\u26BD",
1961
- "Objects": "\u{1F4A1}",
1962
- "Symbols": "\u{1F523}",
1963
- "Flags": "\u{1F3C1}"
1795
+ Activities: "\u26BD",
1796
+ Objects: "\u{1F4A1}",
1797
+ Symbols: "\u{1F523}",
1798
+ Flags: "\u{1F3C1}"
1964
1799
  };
1965
1800
  function categoryIcon(cat) {
1966
1801
  return CATEGORY_ICONS[cat] ?? cat.charAt(0);
@@ -1968,7 +1803,10 @@ function categoryIcon(cat) {
1968
1803
  function formatName(name) {
1969
1804
  return name.replace(/_/g, " ");
1970
1805
  }
1971
- function DomternalEmojiPicker({ editor: editorProp, emojis }) {
1806
+ function DomternalEmojiPicker({
1807
+ editor: editorProp,
1808
+ emojis
1809
+ }) {
1972
1810
  const { editor: contextEditor } = useCurrentEditor();
1973
1811
  const editor = editorProp ?? contextEditor;
1974
1812
  const {
@@ -2000,7 +1838,7 @@ function DomternalEmojiPicker({ editor: editorProp, emojis }) {
2000
1838
  return;
2001
1839
  }
2002
1840
  const cols = 8;
2003
- let next = idx;
1841
+ let next;
2004
1842
  switch (event.key) {
2005
1843
  case "ArrowRight":
2006
1844
  event.preventDefault();
@@ -2261,6 +2099,14 @@ function EditorContent({ editor, innerRef, ...htmlProps }) {
2261
2099
  }
2262
2100
  );
2263
2101
  }
2102
+ function paletteFromExtensionOptions(options) {
2103
+ if (typeof options !== "object" || options === null || !("palette" in options)) return [];
2104
+ const palette = options.palette;
2105
+ if (!Array.isArray(palette) || !palette.every((token) => typeof token === "string")) {
2106
+ return [];
2107
+ }
2108
+ return [...palette];
2109
+ }
2264
2110
  var TOKEN_LABELS = {
2265
2111
  gray: "Gray",
2266
2112
  brown: "Brown",
@@ -2318,22 +2164,24 @@ function useNotionColorPicker(options) {
2318
2164
  setCurrentTextToken(attrs.colorToken ?? null);
2319
2165
  setCurrentBgToken(attrs.backgroundColorToken ?? null);
2320
2166
  }, []);
2321
- const close = useCallback((opts = {}) => {
2322
- if (!isOpenRef.current) return;
2323
- setIsOpen(false);
2324
- setStorageOpen2(false);
2325
- if (opts.refocus) {
2326
- editorRef.current?.view.focus();
2327
- }
2328
- setAnchorEl(null);
2329
- }, [setStorageOpen2]);
2167
+ const close = useCallback(
2168
+ (opts = {}) => {
2169
+ if (!isOpenRef.current) return;
2170
+ setIsOpen(false);
2171
+ setStorageOpen2(false);
2172
+ if (opts.refocus) {
2173
+ editorRef.current?.view.focus();
2174
+ }
2175
+ setAnchorEl(null);
2176
+ },
2177
+ [setStorageOpen2]
2178
+ );
2330
2179
  useEffect(() => {
2331
2180
  if (!editor || editor.isDestroyed) return;
2332
2181
  const host = editor.view.dom.closest(".dm-editor") ?? null;
2333
2182
  setHostEl(host);
2334
2183
  const ext = editor.extensionManager.extensions.find((e) => e.name === "notionColorPicker");
2335
- const extOptions = ext?.options ?? null;
2336
- setPalette(extOptions?.palette ? [...extOptions.palette] : []);
2184
+ setPalette(paletteFromExtensionOptions(ext?.options));
2337
2185
  const onOpen = (...args) => {
2338
2186
  const detail = args[0];
2339
2187
  const incomingAnchor = detail?.anchorElement;
@@ -2371,35 +2219,49 @@ function useNotionColorPicker(options) {
2371
2219
  if (!isOpen) return;
2372
2220
  const controller = new AbortController();
2373
2221
  const { signal } = controller;
2374
- document.addEventListener("mousedown", (e) => {
2375
- const target = e.target;
2376
- if (!target) return;
2377
- if (panelRef.current?.contains(target)) return;
2378
- if (anchorRef.current?.contains(target)) return;
2379
- close({ refocus: false });
2380
- }, { signal });
2381
- document.addEventListener("keydown", (e) => {
2382
- if (e.key === "Escape" && isOpenRef.current) {
2383
- e.preventDefault();
2384
- close({ refocus: true });
2385
- }
2386
- }, { signal });
2222
+ document.addEventListener(
2223
+ "mousedown",
2224
+ (e) => {
2225
+ const target = e.target;
2226
+ if (!target) return;
2227
+ if (panelRef.current?.contains(target)) return;
2228
+ if (anchorRef.current?.contains(target)) return;
2229
+ close({ refocus: false });
2230
+ },
2231
+ { signal }
2232
+ );
2233
+ document.addEventListener(
2234
+ "keydown",
2235
+ (e) => {
2236
+ if (e.key === "Escape" && isOpenRef.current) {
2237
+ e.preventDefault();
2238
+ close({ refocus: true });
2239
+ }
2240
+ },
2241
+ { signal }
2242
+ );
2387
2243
  return () => {
2388
2244
  controller.abort();
2389
2245
  };
2390
2246
  }, [isOpen, close]);
2391
- const applyText = useCallback((token) => {
2392
- const ed = editorRef.current;
2393
- if (!ed) return;
2394
- ed.commands.setTextColorToken(token);
2395
- syncFromSelection();
2396
- }, [syncFromSelection]);
2397
- const applyBg = useCallback((token) => {
2398
- const ed = editorRef.current;
2399
- if (!ed) return;
2400
- ed.commands.setBackgroundColorToken(token);
2401
- syncFromSelection();
2402
- }, [syncFromSelection]);
2247
+ const applyText = useCallback(
2248
+ (token) => {
2249
+ const ed = editorRef.current;
2250
+ if (!ed) return;
2251
+ ed.commands.setTextColorToken(token);
2252
+ syncFromSelection();
2253
+ },
2254
+ [syncFromSelection]
2255
+ );
2256
+ const applyBg = useCallback(
2257
+ (token) => {
2258
+ const ed = editorRef.current;
2259
+ if (!ed) return;
2260
+ ed.commands.setBackgroundColorToken(token);
2261
+ syncFromSelection();
2262
+ },
2263
+ [syncFromSelection]
2264
+ );
2403
2265
  const tokenLabel = useCallback((token) => {
2404
2266
  return TOKEN_LABELS[token] ?? token.charAt(0).toUpperCase() + token.slice(1);
2405
2267
  }, []);
@@ -2407,14 +2269,12 @@ function useNotionColorPicker(options) {
2407
2269
  const cols = 5;
2408
2270
  const root = panelRef.current;
2409
2271
  if (!root) return;
2410
- const swatches = Array.from(
2411
- root.querySelectorAll(".dm-ncp-swatch")
2412
- );
2272
+ const swatches = Array.from(root.querySelectorAll(".dm-ncp-swatch"));
2413
2273
  if (!swatches.length) return;
2414
2274
  const active = document.activeElement;
2415
2275
  const idx = active ? swatches.indexOf(active) : -1;
2416
2276
  if (idx === -1) return;
2417
- let next = idx;
2277
+ let next;
2418
2278
  switch (event.key) {
2419
2279
  case "ArrowRight":
2420
2280
  event.preventDefault();