notra-editor 0.8.2 → 0.8.3

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.
Files changed (49) hide show
  1. package/dist/components/slash-dropdown-menu/filter-slash-items.cjs +46 -0
  2. package/dist/components/slash-dropdown-menu/filter-slash-items.cjs.map +1 -0
  3. package/dist/components/slash-dropdown-menu/filter-slash-items.d.cts +15 -0
  4. package/dist/components/slash-dropdown-menu/filter-slash-items.d.ts +15 -0
  5. package/dist/components/slash-dropdown-menu/filter-slash-items.mjs +21 -0
  6. package/dist/components/slash-dropdown-menu/filter-slash-items.mjs.map +1 -0
  7. package/dist/components/slash-dropdown-menu/slash-dropdown-menu.cjs +228 -85
  8. package/dist/components/slash-dropdown-menu/slash-dropdown-menu.cjs.map +1 -1
  9. package/dist/components/slash-dropdown-menu/slash-dropdown-menu.mjs +249 -84
  10. package/dist/components/slash-dropdown-menu/slash-dropdown-menu.mjs.map +1 -1
  11. package/dist/components/{suggestion-menu/suggestion-menu-types.cjs → slash-dropdown-menu/types.cjs} +4 -4
  12. package/dist/components/slash-dropdown-menu/types.cjs.map +1 -0
  13. package/dist/components/{suggestion-menu/suggestion-menu-types.d.cts → slash-dropdown-menu/types.d.cts} +2 -2
  14. package/dist/components/{suggestion-menu/suggestion-menu-types.d.ts → slash-dropdown-menu/types.d.ts} +2 -2
  15. package/dist/components/slash-dropdown-menu/types.mjs +1 -0
  16. package/dist/components/slash-dropdown-menu/use-slash-items.cjs.map +1 -1
  17. package/dist/components/slash-dropdown-menu/use-slash-items.d.cts +2 -2
  18. package/dist/components/slash-dropdown-menu/use-slash-items.d.ts +2 -2
  19. package/dist/components/slash-dropdown-menu/use-slash-items.mjs.map +1 -1
  20. package/dist/components/ui/command.cjs +1 -1
  21. package/dist/components/ui/command.cjs.map +1 -1
  22. package/dist/components/ui/command.mjs +1 -1
  23. package/dist/components/ui/command.mjs.map +1 -1
  24. package/dist/components/ui/input-group.d.cts +1 -1
  25. package/dist/components/ui/input-group.d.ts +1 -1
  26. package/dist/styles/globals.css +1 -2
  27. package/dist/themes/default/editor.css +18 -6
  28. package/package.json +2 -1
  29. package/dist/components/suggestion-menu/filter-suggestion-items.cjs +0 -57
  30. package/dist/components/suggestion-menu/filter-suggestion-items.cjs.map +0 -1
  31. package/dist/components/suggestion-menu/filter-suggestion-items.d.cts +0 -6
  32. package/dist/components/suggestion-menu/filter-suggestion-items.d.ts +0 -6
  33. package/dist/components/suggestion-menu/filter-suggestion-items.mjs +0 -32
  34. package/dist/components/suggestion-menu/filter-suggestion-items.mjs.map +0 -1
  35. package/dist/components/suggestion-menu/suggestion-menu-types.cjs.map +0 -1
  36. package/dist/components/suggestion-menu/suggestion-menu-types.mjs +0 -1
  37. package/dist/components/suggestion-menu/suggestion-menu.cjs +0 -205
  38. package/dist/components/suggestion-menu/suggestion-menu.cjs.map +0 -1
  39. package/dist/components/suggestion-menu/suggestion-menu.d.cts +0 -27
  40. package/dist/components/suggestion-menu/suggestion-menu.d.ts +0 -27
  41. package/dist/components/suggestion-menu/suggestion-menu.mjs +0 -181
  42. package/dist/components/suggestion-menu/suggestion-menu.mjs.map +0 -1
  43. package/dist/hooks/use-floating-element.cjs +0 -55
  44. package/dist/hooks/use-floating-element.cjs.map +0 -1
  45. package/dist/hooks/use-floating-element.d.cts +0 -21
  46. package/dist/hooks/use-floating-element.d.ts +0 -21
  47. package/dist/hooks/use-floating-element.mjs +0 -35
  48. package/dist/hooks/use-floating-element.mjs.map +0 -1
  49. /package/dist/components/{suggestion-menu/suggestion-menu-types.mjs.map → slash-dropdown-menu/types.mjs.map} +0 -0
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/components/slash-dropdown-menu/filter-slash-items.ts
21
+ var filter_slash_items_exports = {};
22
+ __export(filter_slash_items_exports, {
23
+ filterSlashItems: () => filterSlashItems
24
+ });
25
+ module.exports = __toCommonJS(filter_slash_items_exports);
26
+ function filterSlashItems(items, query) {
27
+ const needle = query.trim().toLowerCase();
28
+ if (needle === "") return items;
29
+ const matches = items.filter((item) => {
30
+ if (item.title.toLowerCase().includes(needle)) return true;
31
+ if (item.subtext?.toLowerCase().includes(needle)) return true;
32
+ return item.keywords?.some((kw) => kw.toLowerCase().includes(needle)) ?? false;
33
+ });
34
+ const rankOf = (title) => {
35
+ const lower = title.toLowerCase();
36
+ if (lower === needle) return 0;
37
+ if (lower.startsWith(needle)) return 1;
38
+ return 2;
39
+ };
40
+ return matches.map((item, index) => ({ item, index, rank: rankOf(item.title) })).sort((a, b) => a.rank - b.rank || a.index - b.index).map(({ item }) => item);
41
+ }
42
+ // Annotate the CommonJS export names for ESM import in node:
43
+ 0 && (module.exports = {
44
+ filterSlashItems
45
+ });
46
+ //# sourceMappingURL=filter-slash-items.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/slash-dropdown-menu/filter-slash-items.ts"],"sourcesContent":["import type { SlashItem } from './types.js';\n\n/**\n * Filter and rank slash-menu items against a user-typed query.\n *\n * Match rule: an item matches when the lowercased query is a substring of the\n * item's lowercased title, subtext, or any keyword.\n *\n * Ranking (stable): exact title match > title startsWith > everything else in\n * original order.\n */\nexport function filterSlashItems(\n\titems: SlashItem[],\n\tquery: string\n): SlashItem[] {\n\tconst needle = query.trim().toLowerCase();\n\n\tif (needle === '') return items;\n\n\tconst matches = items.filter((item) => {\n\t\tif (item.title.toLowerCase().includes(needle)) return true;\n\n\t\tif (item.subtext?.toLowerCase().includes(needle)) return true;\n\n\t\treturn (\n\t\t\titem.keywords?.some((kw) => kw.toLowerCase().includes(needle)) ?? false\n\t\t);\n\t});\n\n\tconst rankOf = (title: string) => {\n\t\tconst lower = title.toLowerCase();\n\n\t\tif (lower === needle) return 0;\n\n\t\tif (lower.startsWith(needle)) return 1;\n\n\t\treturn 2;\n\t};\n\n\treturn matches\n\t\t.map((item, index) => ({ item, index, rank: rankOf(item.title) }))\n\t\t.sort((a, b) => a.rank - b.rank || a.index - b.index)\n\t\t.map(({ item }) => item);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAWO,SAAS,iBACf,OACA,OACc;AACd,QAAM,SAAS,MAAM,KAAK,EAAE,YAAY;AAExC,MAAI,WAAW,GAAI,QAAO;AAE1B,QAAM,UAAU,MAAM,OAAO,CAAC,SAAS;AACtC,QAAI,KAAK,MAAM,YAAY,EAAE,SAAS,MAAM,EAAG,QAAO;AAEtD,QAAI,KAAK,SAAS,YAAY,EAAE,SAAS,MAAM,EAAG,QAAO;AAEzD,WACC,KAAK,UAAU,KAAK,CAAC,OAAO,GAAG,YAAY,EAAE,SAAS,MAAM,CAAC,KAAK;AAAA,EAEpE,CAAC;AAED,QAAM,SAAS,CAAC,UAAkB;AACjC,UAAM,QAAQ,MAAM,YAAY;AAEhC,QAAI,UAAU,OAAQ,QAAO;AAE7B,QAAI,MAAM,WAAW,MAAM,EAAG,QAAO;AAErC,WAAO;AAAA,EACR;AAEA,SAAO,QACL,IAAI,CAAC,MAAM,WAAW,EAAE,MAAM,OAAO,MAAM,OAAO,KAAK,KAAK,EAAE,EAAE,EAChE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EACnD,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI;AACzB;","names":[]}
@@ -0,0 +1,15 @@
1
+ import { SlashItem } from './types.cjs';
2
+ import '@tiptap/react';
3
+
4
+ /**
5
+ * Filter and rank slash-menu items against a user-typed query.
6
+ *
7
+ * Match rule: an item matches when the lowercased query is a substring of the
8
+ * item's lowercased title, subtext, or any keyword.
9
+ *
10
+ * Ranking (stable): exact title match > title startsWith > everything else in
11
+ * original order.
12
+ */
13
+ declare function filterSlashItems(items: SlashItem[], query: string): SlashItem[];
14
+
15
+ export { filterSlashItems };
@@ -0,0 +1,15 @@
1
+ import { SlashItem } from './types.js';
2
+ import '@tiptap/react';
3
+
4
+ /**
5
+ * Filter and rank slash-menu items against a user-typed query.
6
+ *
7
+ * Match rule: an item matches when the lowercased query is a substring of the
8
+ * item's lowercased title, subtext, or any keyword.
9
+ *
10
+ * Ranking (stable): exact title match > title startsWith > everything else in
11
+ * original order.
12
+ */
13
+ declare function filterSlashItems(items: SlashItem[], query: string): SlashItem[];
14
+
15
+ export { filterSlashItems };
@@ -0,0 +1,21 @@
1
+ // src/components/slash-dropdown-menu/filter-slash-items.ts
2
+ function filterSlashItems(items, query) {
3
+ const needle = query.trim().toLowerCase();
4
+ if (needle === "") return items;
5
+ const matches = items.filter((item) => {
6
+ if (item.title.toLowerCase().includes(needle)) return true;
7
+ if (item.subtext?.toLowerCase().includes(needle)) return true;
8
+ return item.keywords?.some((kw) => kw.toLowerCase().includes(needle)) ?? false;
9
+ });
10
+ const rankOf = (title) => {
11
+ const lower = title.toLowerCase();
12
+ if (lower === needle) return 0;
13
+ if (lower.startsWith(needle)) return 1;
14
+ return 2;
15
+ };
16
+ return matches.map((item, index) => ({ item, index, rank: rankOf(item.title) })).sort((a, b) => a.rank - b.rank || a.index - b.index).map(({ item }) => item);
17
+ }
18
+ export {
19
+ filterSlashItems
20
+ };
21
+ //# sourceMappingURL=filter-slash-items.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/slash-dropdown-menu/filter-slash-items.ts"],"sourcesContent":["import type { SlashItem } from './types.js';\n\n/**\n * Filter and rank slash-menu items against a user-typed query.\n *\n * Match rule: an item matches when the lowercased query is a substring of the\n * item's lowercased title, subtext, or any keyword.\n *\n * Ranking (stable): exact title match > title startsWith > everything else in\n * original order.\n */\nexport function filterSlashItems(\n\titems: SlashItem[],\n\tquery: string\n): SlashItem[] {\n\tconst needle = query.trim().toLowerCase();\n\n\tif (needle === '') return items;\n\n\tconst matches = items.filter((item) => {\n\t\tif (item.title.toLowerCase().includes(needle)) return true;\n\n\t\tif (item.subtext?.toLowerCase().includes(needle)) return true;\n\n\t\treturn (\n\t\t\titem.keywords?.some((kw) => kw.toLowerCase().includes(needle)) ?? false\n\t\t);\n\t});\n\n\tconst rankOf = (title: string) => {\n\t\tconst lower = title.toLowerCase();\n\n\t\tif (lower === needle) return 0;\n\n\t\tif (lower.startsWith(needle)) return 1;\n\n\t\treturn 2;\n\t};\n\n\treturn matches\n\t\t.map((item, index) => ({ item, index, rank: rankOf(item.title) }))\n\t\t.sort((a, b) => a.rank - b.rank || a.index - b.index)\n\t\t.map(({ item }) => item);\n}\n"],"mappings":";AAWO,SAAS,iBACf,OACA,OACc;AACd,QAAM,SAAS,MAAM,KAAK,EAAE,YAAY;AAExC,MAAI,WAAW,GAAI,QAAO;AAE1B,QAAM,UAAU,MAAM,OAAO,CAAC,SAAS;AACtC,QAAI,KAAK,MAAM,YAAY,EAAE,SAAS,MAAM,EAAG,QAAO;AAEtD,QAAI,KAAK,SAAS,YAAY,EAAE,SAAS,MAAM,EAAG,QAAO;AAEzD,WACC,KAAK,UAAU,KAAK,CAAC,OAAO,GAAG,YAAY,EAAE,SAAS,MAAM,CAAC,KAAK;AAAA,EAEpE,CAAC;AAED,QAAM,SAAS,CAAC,UAAkB;AACjC,UAAM,QAAQ,MAAM,YAAY;AAEhC,QAAI,UAAU,OAAQ,QAAO;AAE7B,QAAI,MAAM,WAAW,MAAM,EAAG,QAAO;AAErC,WAAO;AAAA,EACR;AAEA,SAAO,QACL,IAAI,CAAC,MAAM,WAAW,EAAE,MAAM,OAAO,MAAM,OAAO,KAAK,KAAK,EAAE,EAAE,EAChE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EACnD,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI;AACzB;","names":[]}
@@ -24,17 +24,20 @@ __export(slash_dropdown_menu_exports, {
24
24
  SlashDropdownMenu: () => SlashDropdownMenu
25
25
  });
26
26
  module.exports = __toCommonJS(slash_dropdown_menu_exports);
27
- var import_react = require("react");
27
+ var import_react = require("@floating-ui/react");
28
+ var import_state = require("@tiptap/pm/state");
29
+ var import_suggestion = require("@tiptap/suggestion");
30
+ var import_react2 = require("react");
31
+ var import_filter_slash_items = require("./filter-slash-items.cjs");
28
32
  var import_slash_image_popover = require("./slash-image-popover.cjs");
29
33
  var import_use_slash_items = require("./use-slash-items.cjs");
30
- var import_filter_suggestion_items = require("../suggestion-menu/filter-suggestion-items.cjs");
31
- var import_suggestion_menu = require("../suggestion-menu/suggestion-menu.cjs");
32
- var import_button = require("../ui/button.cjs");
33
- var import_separator = require("../ui/separator.cjs");
34
+ var import_command = require("../ui/command.cjs");
34
35
  var import_jsx_runtime = require("react/jsx-runtime");
36
+ var FLOATING_Z_INDEX = 1e3;
37
+ var MAX_HEIGHT = 384;
35
38
  function SlashDropdownMenu({ editor }) {
36
- const [imageAnchor, setImageAnchor] = (0, import_react.useState)(null);
37
- const handleImageRequest = (0, import_react.useCallback)(() => {
39
+ const [imageAnchor, setImageAnchor] = (0, import_react2.useState)(null);
40
+ const handleImageRequest = (0, import_react2.useCallback)(() => {
38
41
  if (!editor) return;
39
42
  const { from } = editor.state.selection;
40
43
  const rect = editor.view.coordsAtPos(from);
@@ -46,28 +49,217 @@ function SlashDropdownMenu({ editor }) {
46
49
  );
47
50
  setImageAnchor(domRect);
48
51
  }, [editor]);
49
- const getItems = (0, import_use_slash_items.useSlashItems)({ onImageRequest: handleImageRequest });
50
- const itemsCallback = (0, import_react.useCallback)(
51
- ({ query, editor: editor2 }) => (0, import_filter_suggestion_items.filterSuggestionItems)(getItems(editor2), query),
52
- [getItems]
52
+ const getSlashItems = (0, import_use_slash_items.useSlashItems)({ onImageRequest: handleImageRequest });
53
+ const itemsCallback = (0, import_react2.useCallback)(
54
+ ({ query, editor: editor2 }) => (0, import_filter_slash_items.filterSlashItems)(getSlashItems(editor2), query),
55
+ [getSlashItems]
53
56
  );
57
+ const [open, setOpen] = (0, import_react2.useState)(false);
58
+ const [decorationNode, setDecorationNode] = (0, import_react2.useState)(
59
+ null
60
+ );
61
+ const [filteredItems, setFilteredItems] = (0, import_react2.useState)([]);
62
+ const [selectedValue, setSelectedValue] = (0, import_react2.useState)("");
63
+ const close = (0, import_react2.useCallback)(() => {
64
+ setOpen(false);
65
+ }, []);
66
+ const middleware = (0, import_react2.useMemo)(
67
+ () => [
68
+ (0, import_react.offset)(8),
69
+ (0, import_react.flip)({ mainAxis: true, crossAxis: false }),
70
+ (0, import_react.shift)({ padding: 8 }),
71
+ (0, import_react.size)({
72
+ apply({ availableHeight, elements }) {
73
+ const next = Math.min(availableHeight, MAX_HEIGHT);
74
+ elements.floating.style.setProperty(
75
+ "--suggestion-menu-max-height",
76
+ `${next}px`
77
+ );
78
+ }
79
+ })
80
+ ],
81
+ []
82
+ );
83
+ const { refs, floatingStyles, context } = (0, import_react.useFloating)({
84
+ open,
85
+ strategy: "absolute",
86
+ placement: "bottom-start",
87
+ whileElementsMounted: import_react.autoUpdate,
88
+ middleware,
89
+ onOpenChange: (next) => {
90
+ if (!next) close();
91
+ }
92
+ });
93
+ (0, import_react2.useEffect)(() => {
94
+ refs.setReference(decorationNode);
95
+ }, [refs, decorationNode]);
96
+ const dismiss = (0, import_react.useDismiss)(context);
97
+ const { getFloatingProps } = (0, import_react.useInteractions)([dismiss]);
98
+ const itemsCallbackRef = (0, import_react2.useRef)(itemsCallback);
99
+ const filteredItemsRef = (0, import_react2.useRef)([]);
100
+ const selectedValueRef = (0, import_react2.useRef)("");
101
+ const commandRef = (0, import_react2.useRef)(null);
102
+ (0, import_react2.useEffect)(() => {
103
+ itemsCallbackRef.current = itemsCallback;
104
+ }, [itemsCallback]);
105
+ (0, import_react2.useEffect)(() => {
106
+ filteredItemsRef.current = filteredItems;
107
+ }, [filteredItems]);
108
+ (0, import_react2.useEffect)(() => {
109
+ selectedValueRef.current = selectedValue;
110
+ }, [selectedValue]);
111
+ const floatingDivRef = (0, import_react2.useRef)(null);
112
+ (0, import_react2.useEffect)(() => {
113
+ if (!selectedValue) return;
114
+ const root = floatingDivRef.current;
115
+ if (!root) return;
116
+ const target = root.querySelector(
117
+ '[data-selected="true"]'
118
+ );
119
+ target?.scrollIntoView({ block: "nearest" });
120
+ }, [selectedValue]);
121
+ const setFloatingNode = (0, import_react2.useCallback)(
122
+ (node) => {
123
+ floatingDivRef.current = node;
124
+ refs.setFloating(node);
125
+ },
126
+ [refs]
127
+ );
128
+ (0, import_react2.useEffect)(() => {
129
+ if (!editor || editor.isDestroyed) return;
130
+ const pluginKey = new import_state.PluginKey("slashDropdownMenu");
131
+ const plugin = (0, import_suggestion.Suggestion)({
132
+ editor,
133
+ char: "/",
134
+ pluginKey,
135
+ decorationClass: "notra-slash-decoration",
136
+ decorationContent: "Filter...",
137
+ items: ({ query, editor: editor2 }) => itemsCallbackRef.current({ query, editor: editor2 }),
138
+ allow: ({ state, range }) => {
139
+ const $from = state.doc.resolve(range.from);
140
+ for (let depth = $from.depth; depth > 0; depth--) {
141
+ const name = $from.node(depth).type.name;
142
+ if (name === "image" || name === "codeBlock") return false;
143
+ }
144
+ return true;
145
+ },
146
+ command: ({ editor: editor2, range, props }) => {
147
+ editor2.chain().focus().deleteRange(range).run();
148
+ props.onSelect({ editor: editor2, range });
149
+ },
150
+ render: () => ({
151
+ onStart: (props) => {
152
+ setDecorationNode(props.decorationNode ?? null);
153
+ setFilteredItems(props.items);
154
+ setSelectedValue(props.items[0]?.title ?? "");
155
+ commandRef.current = (item) => props.command(item);
156
+ setOpen(true);
157
+ },
158
+ onUpdate: (props) => {
159
+ setDecorationNode(props.decorationNode ?? null);
160
+ setFilteredItems(props.items);
161
+ setSelectedValue(
162
+ (prev) => props.items.some((i) => i.title === prev) ? prev : props.items[0]?.title ?? ""
163
+ );
164
+ commandRef.current = (item) => props.command(item);
165
+ },
166
+ onKeyDown: ({ event }) => {
167
+ const list = filteredItemsRef.current;
168
+ if (list.length === 0 && event.key !== "Escape") return false;
169
+ if (event.key === "ArrowDown") {
170
+ const currentIndex = list.findIndex(
171
+ (i) => i.title === selectedValueRef.current
172
+ );
173
+ const nextIndex = (currentIndex + 1) % list.length;
174
+ setSelectedValue(list[nextIndex].title);
175
+ return true;
176
+ }
177
+ if (event.key === "ArrowUp") {
178
+ const currentIndex = list.findIndex(
179
+ (i) => i.title === selectedValueRef.current
180
+ );
181
+ const prevIndex = (currentIndex - 1 + list.length) % list.length;
182
+ setSelectedValue(list[prevIndex].title);
183
+ return true;
184
+ }
185
+ if (event.key === "Enter") {
186
+ const item = list.find((i) => i.title === selectedValueRef.current);
187
+ if (item && commandRef.current) {
188
+ commandRef.current(item);
189
+ }
190
+ return true;
191
+ }
192
+ if (event.key === "Escape") {
193
+ close();
194
+ return true;
195
+ }
196
+ return false;
197
+ },
198
+ onExit: () => {
199
+ setDecorationNode(null);
200
+ setFilteredItems([]);
201
+ setSelectedValue("");
202
+ commandRef.current = null;
203
+ setOpen(false);
204
+ }
205
+ })
206
+ });
207
+ editor.registerPlugin(plugin, (newPlugin, currentPlugins) => [
208
+ newPlugin,
209
+ ...currentPlugins
210
+ ]);
211
+ return () => {
212
+ if (!editor.isDestroyed) {
213
+ editor.unregisterPlugin(pluginKey);
214
+ }
215
+ };
216
+ }, [editor, close]);
217
+ const grouped = (0, import_react2.useMemo)(() => groupByLabel(filteredItems), [filteredItems]);
218
+ const isMounted = open && decorationNode !== null;
54
219
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
55
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
56
- import_suggestion_menu.SuggestionMenu,
220
+ isMounted && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
221
+ "div",
57
222
  {
58
- char: "/",
59
- decorationClass: "notra-slash-decoration",
60
- decorationContent: "Filter...",
61
- editor,
62
- items: itemsCallback,
63
- pluginKey: "slashDropdownMenu",
64
- selector: "notra-slash-dropdown-menu",
65
- children: ({ items, selectedIndex, onSelect }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
66
- GroupedItemList,
223
+ ref: setFloatingNode,
224
+ className: "nt:rounded-xl nt:bg-popover nt:text-popover-foreground nt:shadow-md nt:ring-1 nt:ring-foreground/10 nt:outline-hidden",
225
+ "data-selector": "notra-slash-dropdown-menu",
226
+ style: { ...floatingStyles, zIndex: FLOATING_Z_INDEX },
227
+ ...getFloatingProps(),
228
+ onPointerDown: (e) => e.preventDefault(),
229
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
230
+ import_command.Command,
67
231
  {
68
- items,
69
- selectedIndex,
70
- onSelect
232
+ disablePointerSelection: true,
233
+ label: "Slash command menu",
234
+ shouldFilter: false,
235
+ value: selectedValue,
236
+ onValueChange: setSelectedValue,
237
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
238
+ import_command.CommandList,
239
+ {
240
+ style: {
241
+ maxHeight: "var(--suggestion-menu-max-height)"
242
+ },
243
+ children: grouped.map((group, groupIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react2.Fragment, { children: [
244
+ groupIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_command.CommandSeparator, {}),
245
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_command.CommandGroup, { heading: group.label || void 0, children: group.items.map((item) => {
246
+ const Badge = item.badge;
247
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
248
+ import_command.CommandItem,
249
+ {
250
+ value: item.title,
251
+ onSelect: () => commandRef.current?.(item),
252
+ children: [
253
+ Badge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { className: "nt:size-4" }),
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "nt:flex-1 nt:text-left", children: item.title })
255
+ ]
256
+ },
257
+ item.title
258
+ );
259
+ }) })
260
+ ] }, `${group.label}-${groupIndex}`))
261
+ }
262
+ )
71
263
  }
72
264
  )
73
265
  }
@@ -82,67 +274,18 @@ function SlashDropdownMenu({ editor }) {
82
274
  )
83
275
  ] });
84
276
  }
85
- function GroupedItemList({
86
- items,
87
- selectedIndex,
88
- onSelect
89
- }) {
90
- const groups = (0, import_react.useMemo)(() => {
91
- const result = [];
92
- items.forEach((item, index) => {
93
- const label = item.group ?? "";
94
- const last = result[result.length - 1];
95
- if (last && last.label === label) {
96
- last.items.push(item);
97
- last.offsets.push(index);
98
- } else {
99
- result.push({ label, items: [item], offsets: [index] });
100
- }
101
- });
102
- return result;
103
- }, [items]);
104
- if (items.length === 0) return null;
105
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "notra-slash-card", children: groups.map((group, groupIndex) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
106
- groupIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_separator.Separator, { className: "nt:my-1", orientation: "horizontal" }),
107
- group.label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "notra-slash-group-label", children: group.label }),
108
- group.items.map((item, itemIndex) => {
109
- const absoluteIndex = group.offsets[itemIndex];
110
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
111
- SlashItemButton,
112
- {
113
- isSelected: absoluteIndex === selectedIndex,
114
- item,
115
- onSelect: () => onSelect(item)
116
- },
117
- `${item.title}-${absoluteIndex}`
118
- );
119
- })
120
- ] }, `${group.label}-${groupIndex}`)) });
121
- }
122
- function SlashItemButton({ item, isSelected, onSelect }) {
123
- const ref = (0, import_react.useRef)(null);
124
- (0, import_react.useEffect)(() => {
125
- if (!isSelected || !ref.current) return;
126
- ref.current.scrollIntoView({ block: "nearest" });
127
- }, [isSelected]);
128
- const Badge = item.badge;
129
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
130
- import_button.Button,
131
- {
132
- ref,
133
- className: "nt:w-full nt:justify-start nt:gap-2",
134
- "data-active-state": isSelected ? "on" : "off",
135
- size: "default",
136
- tabIndex: -1,
137
- type: "button",
138
- variant: "ghost",
139
- onClick: onSelect,
140
- children: [
141
- Badge && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, { className: "nt:size-4" }),
142
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "nt:flex-1 nt:text-left", children: item.title })
143
- ]
277
+ function groupByLabel(items) {
278
+ const result = [];
279
+ for (const item of items) {
280
+ const label = item.group ?? "";
281
+ const last = result[result.length - 1];
282
+ if (last && last.label === label) {
283
+ last.items.push(item);
284
+ } else {
285
+ result.push({ label, items: [item] });
144
286
  }
145
- );
287
+ }
288
+ return result;
146
289
  }
147
290
  // Annotate the CommonJS export names for ESM import in node:
148
291
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/slash-dropdown-menu/slash-dropdown-menu.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { SlashImagePopover } from './slash-image-popover.js';\nimport { useSlashItems } from './use-slash-items.js';\nimport { filterSuggestionItems } from '../suggestion-menu/filter-suggestion-items.js';\nimport { SuggestionMenu } from '../suggestion-menu/suggestion-menu.js';\nimport { Button } from '../ui/button.js';\nimport { Separator } from '../ui/separator.js';\n\nimport type { SuggestionItem } from '../suggestion-menu/suggestion-menu-types.js';\nimport type { Editor } from '@tiptap/core';\n\nexport interface SlashDropdownMenuProps {\n\teditor: Editor | null;\n}\n\nexport function SlashDropdownMenu({ editor }: SlashDropdownMenuProps) {\n\tconst [imageAnchor, setImageAnchor] = useState<DOMRect | null>(null);\n\n\tconst handleImageRequest = useCallback(() => {\n\t\tif (!editor) return;\n\n\t\tconst { from } = editor.state.selection;\n\t\tconst rect = editor.view.coordsAtPos(from);\n\t\tconst domRect = new DOMRect(\n\t\t\trect.left,\n\t\t\trect.top,\n\t\t\trect.right - rect.left,\n\t\t\trect.bottom - rect.top\n\t\t);\n\n\t\tsetImageAnchor(domRect);\n\t}, [editor]);\n\n\tconst getItems = useSlashItems({ onImageRequest: handleImageRequest });\n\n\tconst itemsCallback = useCallback(\n\t\t({ query, editor }: { query: string; editor: Editor }) =>\n\t\t\tfilterSuggestionItems(getItems(editor), query),\n\t\t[getItems]\n\t);\n\n\treturn (\n\t\t<>\n\t\t\t<SuggestionMenu\n\t\t\t\tchar=\"/\"\n\t\t\t\tdecorationClass=\"notra-slash-decoration\"\n\t\t\t\tdecorationContent=\"Filter...\"\n\t\t\t\teditor={editor}\n\t\t\t\titems={itemsCallback}\n\t\t\t\tpluginKey=\"slashDropdownMenu\"\n\t\t\t\tselector=\"notra-slash-dropdown-menu\"\n\t\t\t>\n\t\t\t\t{({ items, selectedIndex, onSelect }) => (\n\t\t\t\t\t<GroupedItemList\n\t\t\t\t\t\titems={items}\n\t\t\t\t\t\tselectedIndex={selectedIndex}\n\t\t\t\t\t\tonSelect={onSelect}\n\t\t\t\t\t/>\n\t\t\t\t)}\n\t\t\t</SuggestionMenu>\n\t\t\t{imageAnchor && editor && (\n\t\t\t\t<SlashImagePopover\n\t\t\t\t\tanchorRect={imageAnchor}\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tonClose={() => setImageAnchor(null)}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</>\n\t);\n}\n\ninterface GroupedItemListProps {\n\titems: SuggestionItem[];\n\tselectedIndex: number;\n\tonSelect: (item: SuggestionItem) => void;\n}\n\nfunction GroupedItemList({\n\titems,\n\tselectedIndex,\n\tonSelect\n}: GroupedItemListProps) {\n\tconst groups = useMemo(() => {\n\t\tconst result: {\n\t\t\tlabel: string;\n\t\t\titems: SuggestionItem[];\n\t\t\toffsets: number[];\n\t\t}[] = [];\n\n\t\titems.forEach((item, index) => {\n\t\t\tconst label = item.group ?? '';\n\t\t\tconst last = result[result.length - 1];\n\n\t\t\tif (last && last.label === label) {\n\t\t\t\tlast.items.push(item);\n\t\t\t\tlast.offsets.push(index);\n\t\t\t} else {\n\t\t\t\tresult.push({ label, items: [item], offsets: [index] });\n\t\t\t}\n\t\t});\n\n\t\treturn result;\n\t}, [items]);\n\n\tif (items.length === 0) return null;\n\n\treturn (\n\t\t<div className=\"notra-slash-card\">\n\t\t\t{groups.map((group, groupIndex) => (\n\t\t\t\t<div key={`${group.label}-${groupIndex}`}>\n\t\t\t\t\t{groupIndex > 0 && (\n\t\t\t\t\t\t<Separator className=\"nt:my-1\" orientation=\"horizontal\" />\n\t\t\t\t\t)}\n\t\t\t\t\t{group.label && (\n\t\t\t\t\t\t<div className=\"notra-slash-group-label\">{group.label}</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{group.items.map((item, itemIndex) => {\n\t\t\t\t\t\tconst absoluteIndex = group.offsets[itemIndex];\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<SlashItemButton\n\t\t\t\t\t\t\t\tkey={`${item.title}-${absoluteIndex}`}\n\t\t\t\t\t\t\t\tisSelected={absoluteIndex === selectedIndex}\n\t\t\t\t\t\t\t\titem={item}\n\t\t\t\t\t\t\t\tonSelect={() => onSelect(item)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</div>\n\t\t\t))}\n\t\t</div>\n\t);\n}\n\ninterface SlashItemButtonProps {\n\titem: SuggestionItem;\n\tisSelected: boolean;\n\tonSelect: () => void;\n}\n\nfunction SlashItemButton({ item, isSelected, onSelect }: SlashItemButtonProps) {\n\tconst ref = useRef<HTMLButtonElement>(null);\n\n\tuseEffect(() => {\n\t\tif (!isSelected || !ref.current) return;\n\n\t\tref.current.scrollIntoView({ block: 'nearest' });\n\t}, [isSelected]);\n\n\tconst Badge = item.badge;\n\n\treturn (\n\t\t<Button\n\t\t\tref={ref}\n\t\t\tclassName=\"nt:w-full nt:justify-start nt:gap-2\"\n\t\t\tdata-active-state={isSelected ? 'on' : 'off'}\n\t\t\tsize=\"default\"\n\t\t\ttabIndex={-1}\n\t\t\ttype=\"button\"\n\t\t\tvariant=\"ghost\"\n\t\t\tonClick={onSelect}\n\t\t>\n\t\t\t{Badge && <Badge className=\"nt:size-4\" />}\n\t\t\t<span className=\"nt:flex-1 nt:text-left\">{item.title}</span>\n\t\t</Button>\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAAkE;AAElE,iCAAkC;AAClC,6BAA8B;AAC9B,qCAAsC;AACtC,6BAA+B;AAC/B,oBAAuB;AACvB,uBAA0B;AAoCxB;AA3BK,SAAS,kBAAkB,EAAE,OAAO,GAA2B;AACrE,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAyB,IAAI;AAEnE,QAAM,yBAAqB,0BAAY,MAAM;AAC5C,QAAI,CAAC,OAAQ;AAEb,UAAM,EAAE,KAAK,IAAI,OAAO,MAAM;AAC9B,UAAM,OAAO,OAAO,KAAK,YAAY,IAAI;AACzC,UAAM,UAAU,IAAI;AAAA,MACnB,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK,QAAQ,KAAK;AAAA,MAClB,KAAK,SAAS,KAAK;AAAA,IACpB;AAEA,mBAAe,OAAO;AAAA,EACvB,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,eAAW,sCAAc,EAAE,gBAAgB,mBAAmB,CAAC;AAErE,QAAM,oBAAgB;AAAA,IACrB,CAAC,EAAE,OAAO,QAAAA,QAAO,UAChB,sDAAsB,SAASA,OAAM,GAAG,KAAK;AAAA,IAC9C,CAAC,QAAQ;AAAA,EACV;AAEA,SACC,4EACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACA,MAAK;AAAA,QACL,iBAAgB;AAAA,QAChB,mBAAkB;AAAA,QAClB;AAAA,QACA,OAAO;AAAA,QACP,WAAU;AAAA,QACV,UAAS;AAAA,QAER,WAAC,EAAE,OAAO,eAAe,SAAS,MAClC;AAAA,UAAC;AAAA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA;AAAA,QACD;AAAA;AAAA,IAEF;AAAA,IACC,eAAe,UACf;AAAA,MAAC;AAAA;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA,SAAS,MAAM,eAAe,IAAI;AAAA;AAAA,IACnC;AAAA,KAEF;AAEF;AAQA,SAAS,gBAAgB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACD,GAAyB;AACxB,QAAM,aAAS,sBAAQ,MAAM;AAC5B,UAAM,SAIA,CAAC;AAEP,UAAM,QAAQ,CAAC,MAAM,UAAU;AAC9B,YAAM,QAAQ,KAAK,SAAS;AAC5B,YAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AAErC,UAAI,QAAQ,KAAK,UAAU,OAAO;AACjC,aAAK,MAAM,KAAK,IAAI;AACpB,aAAK,QAAQ,KAAK,KAAK;AAAA,MACxB,OAAO;AACN,eAAO,KAAK,EAAE,OAAO,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC;AAAA,MACvD;AAAA,IACD,CAAC;AAED,WAAO;AAAA,EACR,GAAG,CAAC,KAAK,CAAC;AAEV,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SACC,4CAAC,SAAI,WAAU,oBACb,iBAAO,IAAI,CAAC,OAAO,eACnB,6CAAC,SACC;AAAA,iBAAa,KACb,4CAAC,8BAAU,WAAU,WAAU,aAAY,cAAa;AAAA,IAExD,MAAM,SACN,4CAAC,SAAI,WAAU,2BAA2B,gBAAM,OAAM;AAAA,IAEtD,MAAM,MAAM,IAAI,CAAC,MAAM,cAAc;AACrC,YAAM,gBAAgB,MAAM,QAAQ,SAAS;AAE7C,aACC;AAAA,QAAC;AAAA;AAAA,UAEA,YAAY,kBAAkB;AAAA,UAC9B;AAAA,UACA,UAAU,MAAM,SAAS,IAAI;AAAA;AAAA,QAHxB,GAAG,KAAK,KAAK,IAAI,aAAa;AAAA,MAIpC;AAAA,IAEF,CAAC;AAAA,OAlBQ,GAAG,MAAM,KAAK,IAAI,UAAU,EAmBtC,CACA,GACF;AAEF;AAQA,SAAS,gBAAgB,EAAE,MAAM,YAAY,SAAS,GAAyB;AAC9E,QAAM,UAAM,qBAA0B,IAAI;AAE1C,8BAAU,MAAM;AACf,QAAI,CAAC,cAAc,CAAC,IAAI,QAAS;AAEjC,QAAI,QAAQ,eAAe,EAAE,OAAO,UAAU,CAAC;AAAA,EAChD,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,QAAQ,KAAK;AAEnB,SACC;AAAA,IAAC;AAAA;AAAA,MACA;AAAA,MACA,WAAU;AAAA,MACV,qBAAmB,aAAa,OAAO;AAAA,MACvC,MAAK;AAAA,MACL,UAAU;AAAA,MACV,MAAK;AAAA,MACL,SAAQ;AAAA,MACR,SAAS;AAAA,MAER;AAAA,iBAAS,4CAAC,SAAM,WAAU,aAAY;AAAA,QACvC,4CAAC,UAAK,WAAU,0BAA0B,eAAK,OAAM;AAAA;AAAA;AAAA,EACtD;AAEF;","names":["editor"]}
1
+ {"version":3,"sources":["../../../src/components/slash-dropdown-menu/slash-dropdown-menu.tsx"],"sourcesContent":["'use client';\n\nimport {\n\tautoUpdate,\n\tflip,\n\toffset,\n\tshift,\n\tsize,\n\tuseDismiss,\n\tuseFloating,\n\tuseInteractions\n} from '@floating-ui/react';\nimport { PluginKey } from '@tiptap/pm/state';\nimport { Suggestion } from '@tiptap/suggestion';\nimport {\n\tFragment,\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState\n} from 'react';\n\nimport { filterSlashItems } from './filter-slash-items.js';\nimport { SlashImagePopover } from './slash-image-popover.js';\nimport { useSlashItems } from './use-slash-items.js';\nimport {\n\tCommand,\n\tCommandGroup,\n\tCommandItem,\n\tCommandList,\n\tCommandSeparator\n} from '../ui/command.js';\n\nimport type { SlashItem } from './types.js';\nimport type { Editor } from '@tiptap/core';\nimport type {\n\tSuggestionKeyDownProps,\n\tSuggestionProps\n} from '@tiptap/suggestion';\n\nexport interface SlashDropdownMenuProps {\n\teditor: Editor | null;\n}\n\nconst FLOATING_Z_INDEX = 1000;\nconst MAX_HEIGHT = 384;\n\nexport function SlashDropdownMenu({ editor }: SlashDropdownMenuProps) {\n\tconst [imageAnchor, setImageAnchor] = useState<DOMRect | null>(null);\n\n\tconst handleImageRequest = useCallback(() => {\n\t\tif (!editor) return;\n\n\t\tconst { from } = editor.state.selection;\n\t\tconst rect = editor.view.coordsAtPos(from);\n\t\tconst domRect = new DOMRect(\n\t\t\trect.left,\n\t\t\trect.top,\n\t\t\trect.right - rect.left,\n\t\t\trect.bottom - rect.top\n\t\t);\n\n\t\tsetImageAnchor(domRect);\n\t}, [editor]);\n\n\tconst getSlashItems = useSlashItems({ onImageRequest: handleImageRequest });\n\n\tconst itemsCallback = useCallback(\n\t\t({ query, editor }: { query: string; editor: Editor }) =>\n\t\t\tfilterSlashItems(getSlashItems(editor), query),\n\t\t[getSlashItems]\n\t);\n\n\tconst [open, setOpen] = useState(false);\n\tconst [decorationNode, setDecorationNode] = useState<HTMLElement | null>(\n\t\tnull\n\t);\n\tconst [filteredItems, setFilteredItems] = useState<SlashItem[]>([]);\n\tconst [selectedValue, setSelectedValue] = useState<string>('');\n\n\tconst close = useCallback(() => {\n\t\tsetOpen(false);\n\t}, []);\n\n\tconst middleware = useMemo(\n\t\t() => [\n\t\t\toffset(8),\n\t\t\tflip({ mainAxis: true, crossAxis: false }),\n\t\t\tshift({ padding: 8 }),\n\t\t\tsize({\n\t\t\t\tapply({ availableHeight, elements }) {\n\t\t\t\t\tconst next = Math.min(availableHeight, MAX_HEIGHT);\n\n\t\t\t\t\telements.floating.style.setProperty(\n\t\t\t\t\t\t'--suggestion-menu-max-height',\n\t\t\t\t\t\t`${next}px`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t})\n\t\t],\n\t\t[]\n\t);\n\n\tconst { refs, floatingStyles, context } = useFloating({\n\t\topen,\n\t\tstrategy: 'absolute',\n\t\tplacement: 'bottom-start',\n\t\twhileElementsMounted: autoUpdate,\n\t\tmiddleware,\n\t\tonOpenChange: (next) => {\n\t\t\tif (!next) close();\n\t\t}\n\t});\n\n\tuseEffect(() => {\n\t\trefs.setReference(decorationNode);\n\t}, [refs, decorationNode]);\n\n\tconst dismiss = useDismiss(context);\n\tconst { getFloatingProps } = useInteractions([dismiss]);\n\n\t// Stable refs for the plugin, which is registered once per editor instance.\n\tconst itemsCallbackRef = useRef(itemsCallback);\n\tconst filteredItemsRef = useRef<SlashItem[]>([]);\n\tconst selectedValueRef = useRef('');\n\tconst commandRef = useRef<((item: SlashItem) => void) | null>(null);\n\n\tuseEffect(() => {\n\t\titemsCallbackRef.current = itemsCallback;\n\t}, [itemsCallback]);\n\n\tuseEffect(() => {\n\t\tfilteredItemsRef.current = filteredItems;\n\t}, [filteredItems]);\n\n\tuseEffect(() => {\n\t\tselectedValueRef.current = selectedValue;\n\t}, [selectedValue]);\n\n\t// Keep highlighted item visible when arrow keys move past the viewport.\n\tconst floatingDivRef = useRef<HTMLDivElement | null>(null);\n\n\tuseEffect(() => {\n\t\tif (!selectedValue) return;\n\n\t\tconst root = floatingDivRef.current;\n\n\t\tif (!root) return;\n\n\t\tconst target = root.querySelector(\n\t\t\t'[data-selected=\"true\"]'\n\t\t) as HTMLElement | null;\n\n\t\ttarget?.scrollIntoView({ block: 'nearest' });\n\t}, [selectedValue]);\n\n\tconst setFloatingNode = useCallback(\n\t\t(node: HTMLDivElement | null) => {\n\t\t\tfloatingDivRef.current = node;\n\t\t\trefs.setFloating(node);\n\t\t},\n\t\t[refs]\n\t);\n\n\tuseEffect(() => {\n\t\tif (!editor || editor.isDestroyed) return;\n\n\t\tconst pluginKey = new PluginKey('slashDropdownMenu');\n\n\t\tconst plugin = Suggestion<SlashItem>({\n\t\t\teditor,\n\t\t\tchar: '/',\n\t\t\tpluginKey,\n\t\t\tdecorationClass: 'notra-slash-decoration',\n\t\t\tdecorationContent: 'Filter...',\n\n\t\t\titems: ({ query, editor }) => itemsCallbackRef.current({ query, editor }),\n\n\t\t\tallow: ({ state, range }) => {\n\t\t\t\tconst $from = state.doc.resolve(range.from);\n\n\t\t\t\tfor (let depth = $from.depth; depth > 0; depth--) {\n\t\t\t\t\tconst name = $from.node(depth).type.name;\n\n\t\t\t\t\tif (name === 'image' || name === 'codeBlock') return false;\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t},\n\n\t\t\tcommand: ({ editor, range, props }) => {\n\t\t\t\teditor.chain().focus().deleteRange(range).run();\n\t\t\t\tprops.onSelect({ editor, range });\n\t\t\t},\n\n\t\t\trender: () => ({\n\t\t\t\tonStart: (props: SuggestionProps<SlashItem>) => {\n\t\t\t\t\tsetDecorationNode((props.decorationNode as HTMLElement) ?? null);\n\t\t\t\t\tsetFilteredItems(props.items);\n\t\t\t\t\tsetSelectedValue(props.items[0]?.title ?? '');\n\t\t\t\t\tcommandRef.current = (item) => props.command(item);\n\t\t\t\t\tsetOpen(true);\n\t\t\t\t},\n\t\t\t\tonUpdate: (props: SuggestionProps<SlashItem>) => {\n\t\t\t\t\tsetDecorationNode((props.decorationNode as HTMLElement) ?? null);\n\t\t\t\t\tsetFilteredItems(props.items);\n\t\t\t\t\tsetSelectedValue((prev) =>\n\t\t\t\t\t\tprops.items.some((i) => i.title === prev)\n\t\t\t\t\t\t\t? prev\n\t\t\t\t\t\t\t: (props.items[0]?.title ?? '')\n\t\t\t\t\t);\n\t\t\t\t\tcommandRef.current = (item) => props.command(item);\n\t\t\t\t},\n\t\t\t\tonKeyDown: ({ event }: SuggestionKeyDownProps) => {\n\t\t\t\t\tconst list = filteredItemsRef.current;\n\n\t\t\t\t\tif (list.length === 0 && event.key !== 'Escape') return false;\n\n\t\t\t\t\tif (event.key === 'ArrowDown') {\n\t\t\t\t\t\tconst currentIndex = list.findIndex(\n\t\t\t\t\t\t\t(i) => i.title === selectedValueRef.current\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst nextIndex = (currentIndex + 1) % list.length;\n\n\t\t\t\t\t\tsetSelectedValue(list[nextIndex].title);\n\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (event.key === 'ArrowUp') {\n\t\t\t\t\t\tconst currentIndex = list.findIndex(\n\t\t\t\t\t\t\t(i) => i.title === selectedValueRef.current\n\t\t\t\t\t\t);\n\t\t\t\t\t\tconst prevIndex = (currentIndex - 1 + list.length) % list.length;\n\n\t\t\t\t\t\tsetSelectedValue(list[prevIndex].title);\n\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (event.key === 'Enter') {\n\t\t\t\t\t\tconst item = list.find((i) => i.title === selectedValueRef.current);\n\n\t\t\t\t\t\tif (item && commandRef.current) {\n\t\t\t\t\t\t\tcommandRef.current(item);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (event.key === 'Escape') {\n\t\t\t\t\t\tclose();\n\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t\tonExit: () => {\n\t\t\t\t\tsetDecorationNode(null);\n\t\t\t\t\tsetFilteredItems([]);\n\t\t\t\t\tsetSelectedValue('');\n\t\t\t\t\tcommandRef.current = null;\n\t\t\t\t\tsetOpen(false);\n\t\t\t\t}\n\t\t\t})\n\t\t});\n\n\t\t// Prepend so our handleKeyDown runs before the base keymap; otherwise\n\t\t// Enter would be consumed by the paragraph-split handler before our\n\t\t// suggestion onKeyDown could see it.\n\t\teditor.registerPlugin(plugin, (newPlugin, currentPlugins) => [\n\t\t\tnewPlugin,\n\t\t\t...currentPlugins\n\t\t]);\n\n\t\treturn () => {\n\t\t\tif (!editor.isDestroyed) {\n\t\t\t\teditor.unregisterPlugin(pluginKey);\n\t\t\t}\n\t\t};\n\t}, [editor, close]);\n\n\tconst grouped = useMemo(() => groupByLabel(filteredItems), [filteredItems]);\n\n\tconst isMounted = open && decorationNode !== null;\n\n\treturn (\n\t\t<>\n\t\t\t{isMounted && (\n\t\t\t\t<div\n\t\t\t\t\tref={setFloatingNode}\n\t\t\t\t\tclassName=\"nt:rounded-xl nt:bg-popover nt:text-popover-foreground nt:shadow-md nt:ring-1 nt:ring-foreground/10 nt:outline-hidden\"\n\t\t\t\t\tdata-selector=\"notra-slash-dropdown-menu\"\n\t\t\t\t\tstyle={{ ...floatingStyles, zIndex: FLOATING_Z_INDEX }}\n\t\t\t\t\t{...getFloatingProps()}\n\t\t\t\t\tonPointerDown={(e) => e.preventDefault()}\n\t\t\t\t>\n\t\t\t\t\t<Command\n\t\t\t\t\t\tdisablePointerSelection\n\t\t\t\t\t\tlabel=\"Slash command menu\"\n\t\t\t\t\t\tshouldFilter={false}\n\t\t\t\t\t\tvalue={selectedValue}\n\t\t\t\t\t\tonValueChange={setSelectedValue}\n\t\t\t\t\t>\n\t\t\t\t\t\t<CommandList\n\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\tmaxHeight: 'var(--suggestion-menu-max-height)'\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{grouped.map((group, groupIndex) => (\n\t\t\t\t\t\t\t\t<Fragment key={`${group.label}-${groupIndex}`}>\n\t\t\t\t\t\t\t\t\t{groupIndex > 0 && <CommandSeparator />}\n\t\t\t\t\t\t\t\t\t<CommandGroup heading={group.label || undefined}>\n\t\t\t\t\t\t\t\t\t\t{group.items.map((item) => {\n\t\t\t\t\t\t\t\t\t\t\tconst Badge = item.badge;\n\n\t\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t\t<CommandItem\n\t\t\t\t\t\t\t\t\t\t\t\t\tkey={item.title}\n\t\t\t\t\t\t\t\t\t\t\t\t\tvalue={item.title}\n\t\t\t\t\t\t\t\t\t\t\t\t\tonSelect={() => commandRef.current?.(item)}\n\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{Badge && <Badge className=\"nt:size-4\" />}\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"nt:flex-1 nt:text-left\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{item.title}\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t</CommandItem>\n\t\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\t\t</CommandGroup>\n\t\t\t\t\t\t\t\t</Fragment>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</CommandList>\n\t\t\t\t\t</Command>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t\t{imageAnchor && editor && (\n\t\t\t\t<SlashImagePopover\n\t\t\t\t\tanchorRect={imageAnchor}\n\t\t\t\t\teditor={editor}\n\t\t\t\t\tonClose={() => setImageAnchor(null)}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</>\n\t);\n}\n\ninterface GroupedSlashItems {\n\tlabel: string;\n\titems: SlashItem[];\n}\n\nfunction groupByLabel(items: SlashItem[]): GroupedSlashItems[] {\n\tconst result: GroupedSlashItems[] = [];\n\n\tfor (const item of items) {\n\t\tconst label = item.group ?? '';\n\t\tconst last = result[result.length - 1];\n\n\t\tif (last && last.label === label) {\n\t\t\tlast.items.push(item);\n\t\t} else {\n\t\t\tresult.push({ label, items: [item] });\n\t\t}\n\t}\n\n\treturn result;\n}\n"],"mappings":"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","names":["import_react","editor"]}