@pixerate/editor-react 0.2.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 CHANGED
@@ -86,6 +86,9 @@ function usePromptEditor({
86
86
  onMilestoneRef.current = onMilestone;
87
87
  const containerIdRef = (0, import_react.useRef)(containerId);
88
88
  containerIdRef.current = containerId;
89
+ const templateKey = (0, import_react.useMemo)(() => {
90
+ return (templates || []).map((t) => `${t.id || t.name}:${t.latestVersion || ""}`).sort().join(",");
91
+ }, [templates]);
89
92
  const resolvedColorMap = (0, import_react.useMemo)(() => {
90
93
  if (userColorMap) return userColorMap;
91
94
  const map = /* @__PURE__ */ new Map();
@@ -93,7 +96,9 @@ function usePromptEditor({
93
96
  map.set(t.name, { from: "#f43f5e", to: "#8b5cf6" });
94
97
  });
95
98
  return map.size > 0 ? map : DEFAULT_COLOR_MAP;
96
- }, [userColorMap, templates]);
99
+ }, [userColorMap, templateKey]);
100
+ const StarterKitExt = import_starter_kit.default?.configure ? import_starter_kit.default : import_starter_kit.default?.default?.configure ? import_starter_kit.default?.default : import_starter_kit.default;
101
+ const PlaceholderExt = import_extension_placeholder.default?.configure ? import_extension_placeholder.default : import_extension_placeholder.default?.default?.configure ? import_extension_placeholder.default?.default : import_extension_placeholder.default;
97
102
  const editor = (0, import_react2.useEditor)(
98
103
  {
99
104
  immediatelyRender: false,
@@ -101,11 +106,11 @@ function usePromptEditor({
101
106
  enableInputRules: false,
102
107
  editable: isEditing,
103
108
  extensions: [
104
- import_starter_kit.default.configure({
109
+ StarterKitExt.configure({
105
110
  heading: false,
106
111
  codeBlock: false
107
112
  }),
108
- import_extension_placeholder.default.configure({ placeholder }),
113
+ PlaceholderExt.configure({ placeholder }),
109
114
  import_editor.GradientText.configure({
110
115
  templateColorMap: resolvedColorMap,
111
116
  speed,
@@ -163,18 +168,56 @@ function usePromptEditor({
163
168
  },
164
169
  editorProps: {
165
170
  attributes: {
166
- class: className || "w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words"
171
+ class: className || "w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words",
172
+ role: "textbox",
173
+ "aria-label": "Prompt editor"
167
174
  },
168
- handleKeyDown: (view, event) => {
175
+ handleKeyDown(view, event) {
176
+ if (slashCommandActiveRef.current) {
177
+ if (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter") {
178
+ event.preventDefault();
179
+ return true;
180
+ }
181
+ if (event.key === "Escape") {
182
+ clearSlashCommandRef.current?.();
183
+ event.preventDefault();
184
+ event.stopPropagation();
185
+ return true;
186
+ }
187
+ }
188
+ if (onKeyDownRef.current && onKeyDownRef.current(event)) {
189
+ return true;
190
+ }
169
191
  if (event.key === "Escape" && onEscapeRef.current) {
170
192
  onEscapeRef.current();
171
193
  return true;
172
194
  }
173
- if (onKeyDownRef.current) {
174
- return onKeyDownRef.current(event);
175
- }
176
195
  return false;
177
196
  },
197
+ handlePaste: (view, event) => {
198
+ const text = event.clipboardData?.getData("text/plain");
199
+ if (!text) return false;
200
+ event.preventDefault();
201
+ const schema = view.state.schema;
202
+ if (!schema) return false;
203
+ const blocks = text.replace(/\r\n?/g, "\n").split("\n");
204
+ const nodes = [];
205
+ blocks.forEach((line) => {
206
+ const nodeJson = { type: "paragraph" };
207
+ if (line.length > 0) {
208
+ nodeJson.content = [{ type: "text", text: line }];
209
+ }
210
+ try {
211
+ const node = import_model.Node.fromJSON(schema, nodeJson);
212
+ nodes.push(node);
213
+ } catch {
214
+ }
215
+ });
216
+ const parsedSlice = import_model.Slice.maxOpen(import_model.Fragment.fromArray(nodes));
217
+ const tr = view.state.tr.replaceSelection(parsedSlice);
218
+ view.dispatch(tr);
219
+ return true;
220
+ },
178
221
  clipboardTextParser: (text, context, _plain, view) => {
179
222
  const schema = context?.doc?.type?.schema || context?.schema || view?.state?.schema;
180
223
  if (!schema) return import_model.Slice.empty;
@@ -195,8 +238,13 @@ function usePromptEditor({
195
238
  }
196
239
  }
197
240
  },
198
- [templates, resolvedColorMap, isEditing, placeholder, speed]
241
+ [resolvedColorMap, templateKey, isEditing, placeholder, speed]
199
242
  );
243
+ (0, import_react.useEffect)(() => {
244
+ if (editor && !editor.isDestroyed) {
245
+ editor.setEditable(isEditing);
246
+ }
247
+ }, [editor, isEditing]);
200
248
  return editor;
201
249
  }
202
250
 
@@ -225,29 +273,57 @@ var EditorContent = ({
225
273
  };
226
274
 
227
275
  // src/components/BubbleMenu.tsx
228
- var import_react5 = require("@tiptap/react");
229
- var import_jsx_runtime2 = require("react/jsx-runtime");
276
+ var import_react5 = require("react");
277
+ var import_react_dom = require("react-dom");
278
+ var import_extension_bubble_menu = require("@tiptap/extension-bubble-menu");
230
279
  var BubbleMenu = ({
231
280
  editor,
232
281
  className = "flex items-center gap-1 bg-popover text-popover-foreground border shadow-md rounded-md p-1",
233
- children
282
+ children,
283
+ tippyOptions = { duration: 100 },
284
+ shouldShow = null,
285
+ pluginKey = "bubbleMenu",
286
+ updateDelay
234
287
  }) => {
235
- if (!editor) return null;
236
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
237
- import_react5.BubbleMenu,
238
- {
239
- editor,
240
- tippyOptions: { duration: 100 },
241
- className,
242
- children
288
+ const menuEl = (0, import_react5.useRef)(null);
289
+ const [mounted, setMounted] = (0, import_react5.useState)(false);
290
+ if (!menuEl.current && typeof document !== "undefined") {
291
+ menuEl.current = document.createElement("div");
292
+ }
293
+ (0, import_react5.useEffect)(() => {
294
+ if (!editor || !menuEl.current || editor.isDestroyed) {
295
+ return;
243
296
  }
244
- );
297
+ const element = menuEl.current;
298
+ element.className = className;
299
+ const plugin = (0, import_extension_bubble_menu.BubbleMenuPlugin)({
300
+ editor,
301
+ element,
302
+ pluginKey,
303
+ tippyOptions,
304
+ shouldShow,
305
+ updateDelay
306
+ });
307
+ editor.registerPlugin(plugin);
308
+ setMounted(true);
309
+ return () => {
310
+ setMounted(false);
311
+ editor.unregisterPlugin(pluginKey);
312
+ if (element.parentNode) {
313
+ element.parentNode.removeChild(element);
314
+ }
315
+ };
316
+ }, [editor, className, pluginKey]);
317
+ if (!editor || !menuEl.current || !mounted) {
318
+ return null;
319
+ }
320
+ return (0, import_react_dom.createPortal)(children, menuEl.current);
245
321
  };
246
322
 
247
323
  // src/components/TemplateRenderer.tsx
248
324
  var import_react6 = require("react");
249
325
  var import_editor3 = require("@pixerate/editor");
250
- var import_jsx_runtime3 = require("react/jsx-runtime");
326
+ var import_jsx_runtime2 = require("react/jsx-runtime");
251
327
  var TemplateRenderer = ({
252
328
  content,
253
329
  templates = [],
@@ -264,7 +340,7 @@ var TemplateRenderer = ({
264
340
  const tokens = (0, import_react6.useMemo)(() => {
265
341
  return (0, import_editor3.tokenizePrompt)(displayText);
266
342
  }, [displayText]);
267
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, children: tokens.map((token, index) => {
343
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, children: tokens.map((token, index) => {
268
344
  if (token.type === "template") {
269
345
  const colors = templateColorMap?.get(token.value) || {
270
346
  from: "#f43f5e",
@@ -274,7 +350,7 @@ var TemplateRenderer = ({
274
350
  "--color-from": colors.from,
275
351
  "--color-to": colors.to
276
352
  };
277
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
353
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
278
354
  "span",
279
355
  {
280
356
  style,
@@ -287,7 +363,7 @@ var TemplateRenderer = ({
287
363
  );
288
364
  }
289
365
  if (token.type === "variable") {
290
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
366
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
291
367
  "span",
292
368
  {
293
369
  onClick: () => onVariableClick?.(token.value),
@@ -299,7 +375,7 @@ var TemplateRenderer = ({
299
375
  );
300
376
  }
301
377
  if (token.type === "instruction") {
302
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
378
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
303
379
  "span",
304
380
  {
305
381
  className: "inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs italic font-medium rounded-md border border-rose-400/40 bg-rose-400/15 text-rose-600 dark:text-rose-300",
@@ -309,7 +385,7 @@ var TemplateRenderer = ({
309
385
  index
310
386
  );
311
387
  }
312
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: token.value }, index);
388
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: token.value }, index);
313
389
  }) });
314
390
  };
315
391
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/hooks/usePromptEditor.ts","../src/hooks/useEditor.ts","../src/components/EditorContent.tsx","../src/components/BubbleMenu.tsx","../src/components/TemplateRenderer.tsx"],"sourcesContent":["export * from \"./hooks/usePromptEditor\";\nexport * from \"./hooks/useEditor\";\nexport * from \"./components/EditorContent\";\nexport * from \"./components/BubbleMenu\";\nexport * from \"./components/TemplateRenderer\";\n\n// Re-export core types for easy consumer access\nexport type {\n Template,\n TemplateVersion,\n Token,\n TokenType,\n ColorGradient,\n RichTextPresetOptions,\n} from \"@pixerate/editor\";\n","import { useMemo, useEffect, useRef } from \"react\";\nimport { useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport { Slice, Fragment, Node as ProsemirrorNode } from \"@tiptap/pm/model\";\nimport {\n Template,\n ColorGradient,\n GradientText,\n TemplateSuggestions,\n LoadingNode,\n plainTextToTipTapHtml,\n getEditorText,\n} from \"@pixerate/editor\";\n\nexport interface UsePromptEditorProps {\n /** The initial or controlled content of the editor as plain text */\n content: string;\n /** Callback fired when the editor content changes (returns plain text) */\n onContentChange: (text: string) => void;\n /** List of available templates to be suggested and highlighted */\n templates?: Template[];\n /** Custom gradient color map for templates */\n templateColorMap?: Map<string, ColorGradient>;\n /** Whether the editor is editable */\n isEditing?: boolean;\n /** Placeholder text for the editor */\n placeholder?: string;\n /** Additional CSS classes for the editor container */\n className?: string;\n /** Callback fired when the Escape key is pressed */\n onEscape?: () => void;\n /** Custom keydown handler. Return true to prevent default behavior */\n onKeyDown?: (event: KeyboardEvent) => boolean;\n /** Callback fired when text selection changes */\n onSelectionChange?: (selection: { from: number; to: number } | null) => void;\n /** Callback fired when a slash command (e.g., /template) matches */\n onSlashCommandMatch?: (\n match: RegExpMatchArray,\n from: number,\n to: number,\n coords: { top: number; left: number },\n ) => void;\n /** Callback to dismiss/clear the active slash command */\n clearSlashCommand?: () => void;\n /** Whether a slash command menu is currently active */\n slashCommandActive?: boolean;\n /** Callback fired when certain milestones are reached (e.g., used_variable, used_magic) */\n onMilestone?: (milestone: string) => void;\n /** DOM element ID of the container, used for positioning popovers */\n containerId?: string;\n /** Variable names originating from structured JSON output steps */\n jsonVariables?: string[];\n /** Animation speed for gradient decorations */\n speed?: number;\n}\n\nconst DEFAULT_TEMPLATES: Template[] = [];\nconst DEFAULT_COLOR_MAP = new Map<string, ColorGradient>([\n [\"default\", { from: \"#f43f5e\", to: \"#8b5cf6\" }],\n]);\n\nexport function usePromptEditor({\n content,\n onContentChange,\n templates = DEFAULT_TEMPLATES,\n templateColorMap: userColorMap,\n isEditing = true,\n placeholder = \"Write a prompt, use {{template}}, {variable}, or /command...\",\n className,\n onEscape,\n onKeyDown,\n onSelectionChange,\n onSlashCommandMatch,\n clearSlashCommand,\n slashCommandActive = false,\n onMilestone,\n containerId = \"editor-container\",\n jsonVariables,\n speed = 2,\n}: UsePromptEditorProps) {\n const slashCommandActiveRef = useRef(slashCommandActive);\n slashCommandActiveRef.current = slashCommandActive;\n\n const clearSlashCommandRef = useRef(clearSlashCommand);\n clearSlashCommandRef.current = clearSlashCommand;\n\n const onEscapeRef = useRef(onEscape);\n onEscapeRef.current = onEscape;\n\n const onKeyDownRef = useRef(onKeyDown);\n onKeyDownRef.current = onKeyDown;\n\n const onSelectionChangeRef = useRef(onSelectionChange);\n onSelectionChangeRef.current = onSelectionChange;\n\n const onSlashCommandMatchRef = useRef(onSlashCommandMatch);\n onSlashCommandMatchRef.current = onSlashCommandMatch;\n\n const onContentChangeRef = useRef(onContentChange);\n onContentChangeRef.current = onContentChange;\n\n const onMilestoneRef = useRef(onMilestone);\n onMilestoneRef.current = onMilestone;\n\n const containerIdRef = useRef(containerId);\n containerIdRef.current = containerId;\n\n const resolvedColorMap = useMemo(() => {\n if (userColorMap) return userColorMap;\n const map = new Map<string, ColorGradient>();\n templates.forEach((t) => {\n map.set(t.name, { from: \"#f43f5e\", to: \"#8b5cf6\" });\n });\n return map.size > 0 ? map : DEFAULT_COLOR_MAP;\n }, [userColorMap, templates]);\n\n const editor = useEditor(\n {\n immediatelyRender: false,\n enablePasteRules: false,\n enableInputRules: false,\n editable: isEditing,\n extensions: [\n StarterKit.configure({\n heading: false,\n codeBlock: false,\n }),\n Placeholder.configure({ placeholder }),\n GradientText.configure({\n templateColorMap: resolvedColorMap,\n speed,\n jsonVariables,\n }),\n TemplateSuggestions.configure({\n templates,\n }),\n LoadingNode,\n ],\n content: plainTextToTipTapHtml(content),\n onUpdate: ({ editor: ed }) => {\n const plainText = getEditorText(ed);\n onContentChangeRef.current?.(plainText);\n\n if (onMilestoneRef.current) {\n if (plainText.match(/(?<!\\{)\\{([^{}]+)\\}(?!\\})/g)) {\n onMilestoneRef.current(\"used_variable\");\n }\n if (plainText.match(/__([^_]+)__/g)) {\n onMilestoneRef.current(\"used_magic\");\n }\n }\n\n const { state, view } = ed;\n const { selection } = state;\n const { $from, from, to } = selection;\n\n onSelectionChangeRef.current?.({ from, to });\n\n if (onSlashCommandMatchRef.current && clearSlashCommandRef.current) {\n const textBeforeCursor = $from?.parent?.textBetween\n ? $from.parent.textBetween(0, $from.parentOffset)\n : \"\";\n const slashMatch = textBeforeCursor.match(\n /(?:^|\\s)\\/([a-zA-Z0-9_-]*)$/,\n );\n\n if (slashMatch) {\n const coords = view.coordsAtPos($from.pos);\n const containerEl =\n document.getElementById(containerIdRef.current) ||\n document.getElementById(\"editor-container\") ||\n (view.dom.closest?.(\".relative\") as HTMLElement | null);\n const containerCoords = containerEl?.getBoundingClientRect();\n\n if (containerCoords) {\n onSlashCommandMatchRef.current(\n slashMatch,\n $from.pos -\n slashMatch[0].length +\n (slashMatch[0].startsWith(\" \") ? 1 : 0),\n $from.pos,\n {\n top: coords.bottom - containerCoords.top + 5,\n left: coords.left - containerCoords.left,\n },\n );\n }\n } else {\n clearSlashCommandRef.current();\n }\n }\n },\n onSelectionUpdate: ({ editor: ed }) => {\n const { from, to } = ed.state.selection;\n onSelectionChangeRef.current?.({ from, to });\n },\n editorProps: {\n attributes: {\n class:\n className ||\n \"w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words\",\n },\n handleKeyDown: (view, event) => {\n if (event.key === \"Escape\" && onEscapeRef.current) {\n onEscapeRef.current();\n return true;\n }\n if (onKeyDownRef.current) {\n return onKeyDownRef.current(event);\n }\n return false;\n },\n clipboardTextParser: (text, context, _plain, view) => {\n const schema =\n (context as any)?.doc?.type?.schema ||\n (context as any)?.schema ||\n view?.state?.schema;\n\n if (!schema) return Slice.empty;\n\n const blocks = text.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const nodes: ProsemirrorNode[] = [];\n\n blocks.forEach((line) => {\n const nodeJson: any = { type: \"paragraph\" };\n if (line.length > 0) {\n nodeJson.content = [{ type: \"text\", text: line }];\n }\n try {\n const node = ProsemirrorNode.fromJSON(schema, nodeJson);\n nodes.push(node);\n } catch {\n // Ignore\n }\n });\n\n return Slice.maxOpen(Fragment.fromArray(nodes));\n },\n },\n },\n [templates, resolvedColorMap, isEditing, placeholder, speed],\n );\n\n return editor;\n}\n","import { useEditor as useTipTapEditor, UseEditorOptions } from \"@tiptap/react\";\nimport { createRichTextPreset, RichTextPresetOptions } from \"@pixerate/editor\";\n\nexport interface UseRichEditorProps extends UseEditorOptions {\n richTextOptions?: RichTextPresetOptions;\n}\n\n/**\n * Hook to initialize a TipTap rich-text editor instance with pre-configured extensions.\n */\nexport function useEditor(options: UseRichEditorProps = {}) {\n const { richTextOptions, extensions = [], ...editorOptions } = options;\n\n const preset = createRichTextPreset(richTextOptions);\n const combinedExtensions = [...preset, ...extensions];\n\n return useTipTapEditor({\n extensions: combinedExtensions,\n ...editorOptions,\n });\n}\n","import React from \"react\";\nimport {\n EditorContent as TipTapEditorContent,\n EditorContentProps as TipTapEditorContentProps,\n} from \"@tiptap/react\";\n\nexport interface EditorContentProps extends TipTapEditorContentProps {\n className?: string;\n}\n\nexport const EditorContent: React.FC<EditorContentProps> = ({\n className,\n editor,\n ...props\n}) => {\n return (\n <div className={className}>\n <TipTapEditorContent editor={editor} {...props} />\n </div>\n );\n};\n","import React from \"react\";\nimport { BubbleMenu as TipTapBubbleMenu } from \"@tiptap/react\";\nimport type { Editor } from \"@tiptap/core\";\n\nexport interface BubbleMenuProps {\n editor: Editor | null;\n className?: string;\n children: React.ReactNode;\n}\n\nexport const BubbleMenu: React.FC<BubbleMenuProps> = ({\n editor,\n className = \"flex items-center gap-1 bg-popover text-popover-foreground border shadow-md rounded-md p-1\",\n children,\n}) => {\n if (!editor) return null;\n\n return (\n <TipTapBubbleMenu\n editor={editor}\n tippyOptions={{ duration: 100 }}\n className={className}\n >\n {children}\n </TipTapBubbleMenu>\n );\n};\n","import React, { useMemo } from \"react\";\nimport {\n tokenizePrompt,\n resolveTemplates,\n Template,\n ColorGradient,\n} from \"@pixerate/editor\";\n\nexport interface TemplateRendererProps {\n /** Raw prompt or text string to render */\n content: string;\n /** Available templates for recursive resolution and custom coloring */\n templates?: Template[];\n /** Custom gradient color map for templates */\n templateColorMap?: Map<string, ColorGradient>;\n /** Whether to resolve nested templates recursively before displaying */\n resolve?: boolean;\n /** Custom class name for the wrapper */\n className?: string;\n /** Click callback for template badges */\n onTemplateClick?: (templateName: string) => void;\n /** Click callback for variable badges */\n onVariableClick?: (variableName: string) => void;\n}\n\nexport const TemplateRenderer: React.FC<TemplateRendererProps> = ({\n content,\n templates = [],\n templateColorMap,\n resolve = false,\n className = \"font-mono text-sm leading-relaxed whitespace-pre-wrap break-words\",\n onTemplateClick,\n onVariableClick,\n}) => {\n const displayText = useMemo(() => {\n if (!resolve || templates.length === 0) return content;\n return resolveTemplates(content, templates);\n }, [content, templates, resolve]);\n\n const tokens = useMemo(() => {\n return tokenizePrompt(displayText);\n }, [displayText]);\n\n return (\n <div className={className}>\n {tokens.map((token, index) => {\n if (token.type === \"template\") {\n const colors = templateColorMap?.get(token.value) || {\n from: \"#f43f5e\",\n to: \"#8b5cf6\",\n };\n const style = {\n \"--color-from\": colors.from,\n \"--color-to\": colors.to,\n } as React.CSSProperties;\n\n return (\n <span\n key={index}\n style={style}\n onClick={() => onTemplateClick?.(token.value)}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-semibold rounded-md border border-[var(--color-to)]/40 bg-gradient-to-r from-[var(--color-from)]/15 to-[var(--color-to)]/15 text-[var(--color-to)] hover:brightness-110 cursor-pointer transition-all\"\n title={`Template: ${token.value}`}\n >\n {`{{${token.value}}}`}\n </span>\n );\n }\n\n if (token.type === \"variable\") {\n return (\n <span\n key={index}\n onClick={() => onVariableClick?.(token.value)}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-medium rounded-md border border-violet-400/40 bg-violet-400/15 text-violet-600 dark:text-violet-300 hover:brightness-110 cursor-pointer transition-all\"\n title={`Variable: ${token.value}`}\n >\n {`{${token.value}}`}\n </span>\n );\n }\n\n if (token.type === \"instruction\") {\n return (\n <span\n key={index}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs italic font-medium rounded-md border border-rose-400/40 bg-rose-400/15 text-rose-600 dark:text-rose-300\"\n title={`Instruction: ${token.value}`}\n >\n {`__${token.value}__`}\n </span>\n );\n }\n\n return <span key={index}>{token.value}</span>;\n })}\n </div>\n );\n};\n"],"mappings":"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","names":["useEditor","import_react","StarterKit","Placeholder","ProsemirrorNode","import_react","import_editor","useEditor","useTipTapEditor","import_react","TipTapEditorContent","import_react","import_jsx_runtime","TipTapBubbleMenu","import_react","import_editor","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/hooks/usePromptEditor.ts","../src/hooks/useEditor.ts","../src/components/EditorContent.tsx","../src/components/BubbleMenu.tsx","../src/components/TemplateRenderer.tsx"],"sourcesContent":["export * from \"./hooks/usePromptEditor\";\nexport * from \"./hooks/useEditor\";\nexport * from \"./components/EditorContent\";\nexport * from \"./components/BubbleMenu\";\nexport * from \"./components/TemplateRenderer\";\n\n// Re-export core types for easy consumer access\nexport type {\n Template,\n TemplateVersion,\n Token,\n TokenType,\n ColorGradient,\n RichTextPresetOptions,\n} from \"@pixerate/editor\";\n","import { useMemo, useEffect, useRef } from \"react\";\nimport { useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport { Slice, Fragment, Node as ProsemirrorNode } from \"@tiptap/pm/model\";\nimport {\n Template,\n ColorGradient,\n GradientText,\n TemplateSuggestions,\n LoadingNode,\n plainTextToTipTapHtml,\n getEditorText,\n} from \"@pixerate/editor\";\n\nexport interface UsePromptEditorProps {\n /** The initial or controlled content of the editor as plain text */\n content: string;\n /** Callback fired when the editor content changes (returns plain text) */\n onContentChange: (text: string) => void;\n /** List of available templates to be suggested and highlighted */\n templates?: Template[];\n /** Custom gradient color map for templates */\n templateColorMap?: Map<string, ColorGradient>;\n /** Whether the editor is editable */\n isEditing?: boolean;\n /** Placeholder text for the editor */\n placeholder?: string;\n /** Additional CSS classes for the editor container */\n className?: string;\n /** Callback fired when the Escape key is pressed */\n onEscape?: () => void;\n /** Custom keydown handler. Return true to prevent default behavior */\n onKeyDown?: (event: KeyboardEvent) => boolean;\n /** Callback fired when text selection changes */\n onSelectionChange?: (selection: { from: number; to: number } | null) => void;\n /** Callback fired when a slash command (e.g., /template) matches */\n onSlashCommandMatch?: (\n match: RegExpMatchArray,\n from: number,\n to: number,\n coords: { top: number; left: number },\n ) => void;\n /** Callback to dismiss/clear the active slash command */\n clearSlashCommand?: () => void;\n /** Whether a slash command menu is currently active */\n slashCommandActive?: boolean;\n /** Callback fired when certain milestones are reached (e.g., used_variable, used_magic) */\n onMilestone?: (milestone: string) => void;\n /** DOM element ID of the container, used for positioning popovers */\n containerId?: string;\n /** Variable names originating from structured JSON output steps */\n jsonVariables?: string[];\n /** Animation speed for gradient decorations */\n speed?: number;\n}\n\nconst DEFAULT_TEMPLATES: Template[] = [];\nconst DEFAULT_COLOR_MAP = new Map<string, ColorGradient>([\n [\"default\", { from: \"#f43f5e\", to: \"#8b5cf6\" }],\n]);\n\nexport function usePromptEditor({\n content,\n onContentChange,\n templates = DEFAULT_TEMPLATES,\n templateColorMap: userColorMap,\n isEditing = true,\n placeholder = \"Write a prompt, use {{template}}, {variable}, or /command...\",\n className,\n onEscape,\n onKeyDown,\n onSelectionChange,\n onSlashCommandMatch,\n clearSlashCommand,\n slashCommandActive = false,\n onMilestone,\n containerId = \"editor-container\",\n jsonVariables,\n speed = 2,\n}: UsePromptEditorProps) {\n const slashCommandActiveRef = useRef(slashCommandActive);\n slashCommandActiveRef.current = slashCommandActive;\n\n const clearSlashCommandRef = useRef(clearSlashCommand);\n clearSlashCommandRef.current = clearSlashCommand;\n\n const onEscapeRef = useRef(onEscape);\n onEscapeRef.current = onEscape;\n\n const onKeyDownRef = useRef(onKeyDown);\n onKeyDownRef.current = onKeyDown;\n\n const onSelectionChangeRef = useRef(onSelectionChange);\n onSelectionChangeRef.current = onSelectionChange;\n\n const onSlashCommandMatchRef = useRef(onSlashCommandMatch);\n onSlashCommandMatchRef.current = onSlashCommandMatch;\n\n const onContentChangeRef = useRef(onContentChange);\n onContentChangeRef.current = onContentChange;\n\n const onMilestoneRef = useRef(onMilestone);\n onMilestoneRef.current = onMilestone;\n\n const containerIdRef = useRef(containerId);\n containerIdRef.current = containerId;\n\n const templateKey = useMemo(() => {\n return (templates || [])\n .map((t) => `${t.id || t.name}:${(t as any).latestVersion || \"\"}`)\n .sort()\n .join(\",\");\n }, [templates]);\n\n const resolvedColorMap = useMemo(() => {\n if (userColorMap) return userColorMap;\n const map = new Map<string, ColorGradient>();\n templates.forEach((t) => {\n map.set(t.name, { from: \"#f43f5e\", to: \"#8b5cf6\" });\n });\n return map.size > 0 ? map : DEFAULT_COLOR_MAP;\n }, [userColorMap, templateKey]);\n\n const StarterKitExt =\n (StarterKit as any)?.configure\n ? StarterKit\n : (StarterKit as any)?.default?.configure\n ? (StarterKit as any)?.default\n : StarterKit;\n\n const PlaceholderExt =\n (Placeholder as any)?.configure\n ? Placeholder\n : (Placeholder as any)?.default?.configure\n ? (Placeholder as any)?.default\n : Placeholder;\n\n const editor = useEditor(\n {\n immediatelyRender: false,\n enablePasteRules: false,\n enableInputRules: false,\n editable: isEditing,\n extensions: [\n StarterKitExt.configure({\n heading: false,\n codeBlock: false,\n }),\n PlaceholderExt.configure({ placeholder }),\n GradientText.configure({\n templateColorMap: resolvedColorMap,\n speed,\n jsonVariables,\n }),\n TemplateSuggestions.configure({\n templates,\n }),\n LoadingNode,\n ],\n content: plainTextToTipTapHtml(content),\n onUpdate: ({ editor: ed }) => {\n const plainText = getEditorText(ed);\n onContentChangeRef.current?.(plainText);\n\n if (onMilestoneRef.current) {\n if (plainText.match(/(?<!\\{)\\{([^{}]+)\\}(?!\\})/g)) {\n onMilestoneRef.current(\"used_variable\");\n }\n if (plainText.match(/__([^_]+)__/g)) {\n onMilestoneRef.current(\"used_magic\");\n }\n }\n\n const { state, view } = ed;\n const { selection } = state;\n const { $from, from, to } = selection;\n\n onSelectionChangeRef.current?.({ from, to });\n\n if (onSlashCommandMatchRef.current && clearSlashCommandRef.current) {\n const textBeforeCursor = $from?.parent?.textBetween\n ? $from.parent.textBetween(0, $from.parentOffset)\n : \"\";\n const slashMatch = textBeforeCursor.match(\n /(?:^|\\s)\\/([a-zA-Z0-9_-]*)$/,\n );\n\n if (slashMatch) {\n const coords = view.coordsAtPos($from.pos);\n const containerEl =\n document.getElementById(containerIdRef.current) ||\n document.getElementById(\"editor-container\") ||\n (view.dom.closest?.(\".relative\") as HTMLElement | null);\n const containerCoords = containerEl?.getBoundingClientRect();\n\n if (containerCoords) {\n onSlashCommandMatchRef.current(\n slashMatch,\n $from.pos -\n slashMatch[0].length +\n (slashMatch[0].startsWith(\" \") ? 1 : 0),\n $from.pos,\n {\n top: coords.bottom - containerCoords.top + 5,\n left: coords.left - containerCoords.left,\n },\n );\n }\n } else {\n clearSlashCommandRef.current();\n }\n }\n },\n onSelectionUpdate: ({ editor: ed }) => {\n const { from, to } = ed.state.selection;\n onSelectionChangeRef.current?.({ from, to });\n },\n editorProps: {\n attributes: {\n class:\n className ||\n \"w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words\",\n role: \"textbox\",\n \"aria-label\": \"Prompt editor\",\n },\n handleKeyDown(view, event) {\n if (slashCommandActiveRef.current) {\n if (\n event.key === \"ArrowUp\" ||\n event.key === \"ArrowDown\" ||\n event.key === \"Enter\"\n ) {\n event.preventDefault();\n return true;\n }\n if (event.key === \"Escape\") {\n clearSlashCommandRef.current?.();\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n }\n if (onKeyDownRef.current && onKeyDownRef.current(event as KeyboardEvent)) {\n return true;\n }\n if (event.key === \"Escape\" && onEscapeRef.current) {\n onEscapeRef.current();\n return true;\n }\n return false;\n },\n handlePaste: (view, event) => {\n const text = event.clipboardData?.getData(\"text/plain\");\n if (!text) return false;\n event.preventDefault();\n const schema = view.state.schema;\n if (!schema) return false;\n\n const blocks = text.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const nodes: ProsemirrorNode[] = [];\n blocks.forEach((line) => {\n const nodeJson: any = { type: \"paragraph\" };\n if (line.length > 0) {\n nodeJson.content = [{ type: \"text\", text: line }];\n }\n try {\n const node = ProsemirrorNode.fromJSON(schema, nodeJson);\n nodes.push(node);\n } catch {\n // Ignore\n }\n });\n\n const parsedSlice = Slice.maxOpen(Fragment.fromArray(nodes));\n const tr = view.state.tr.replaceSelection(parsedSlice);\n view.dispatch(tr);\n return true;\n },\n clipboardTextParser: (text, context, _plain, view) => {\n const schema =\n (context as any)?.doc?.type?.schema ||\n (context as any)?.schema ||\n view?.state?.schema;\n\n if (!schema) return Slice.empty;\n\n const blocks = text.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const nodes: ProsemirrorNode[] = [];\n\n blocks.forEach((line) => {\n const nodeJson: any = { type: \"paragraph\" };\n if (line.length > 0) {\n nodeJson.content = [{ type: \"text\", text: line }];\n }\n try {\n const node = ProsemirrorNode.fromJSON(schema, nodeJson);\n nodes.push(node);\n } catch {\n // Ignore\n }\n });\n\n return Slice.maxOpen(Fragment.fromArray(nodes));\n },\n },\n },\n [resolvedColorMap, templateKey, isEditing, placeholder, speed],\n );\n\n useEffect(() => {\n if (editor && !editor.isDestroyed) {\n editor.setEditable(isEditing);\n }\n }, [editor, isEditing]);\n\n return editor;\n}\n","import { useEditor as useTipTapEditor, UseEditorOptions } from \"@tiptap/react\";\nimport { createRichTextPreset, RichTextPresetOptions } from \"@pixerate/editor\";\n\nexport interface UseRichEditorProps extends UseEditorOptions {\n richTextOptions?: RichTextPresetOptions;\n}\n\n/**\n * Hook to initialize a TipTap rich-text editor instance with pre-configured extensions.\n */\nexport function useEditor(options: UseRichEditorProps = {}) {\n const { richTextOptions, extensions = [], ...editorOptions } = options;\n\n const preset = createRichTextPreset(richTextOptions);\n const combinedExtensions = [...preset, ...extensions];\n\n return useTipTapEditor({\n extensions: combinedExtensions,\n ...editorOptions,\n });\n}\n","import React from \"react\";\nimport {\n EditorContent as TipTapEditorContent,\n EditorContentProps as TipTapEditorContentProps,\n} from \"@tiptap/react\";\n\nexport interface EditorContentProps extends TipTapEditorContentProps {\n className?: string;\n}\n\nexport const EditorContent: React.FC<EditorContentProps> = ({\n className,\n editor,\n ...props\n}) => {\n return (\n <div className={className}>\n <TipTapEditorContent editor={editor} {...props} />\n </div>\n );\n};\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n BubbleMenuPlugin,\n type BubbleMenuPluginProps,\n} from \"@tiptap/extension-bubble-menu\";\nimport type { Editor } from \"@tiptap/core\";\n\nexport interface BubbleMenuProps {\n editor: Editor | null;\n className?: string;\n children: React.ReactNode;\n tippyOptions?: BubbleMenuPluginProps[\"tippyOptions\"];\n shouldShow?: BubbleMenuPluginProps[\"shouldShow\"];\n pluginKey?: BubbleMenuPluginProps[\"pluginKey\"];\n updateDelay?: BubbleMenuPluginProps[\"updateDelay\"];\n}\n\nexport const BubbleMenu: React.FC<BubbleMenuProps> = ({\n editor,\n className = \"flex items-center gap-1 bg-popover text-popover-foreground border shadow-md rounded-md p-1\",\n children,\n tippyOptions = { duration: 100 },\n shouldShow = null,\n pluginKey = \"bubbleMenu\",\n updateDelay,\n}) => {\n const menuEl = useRef<HTMLDivElement | null>(null);\n const [mounted, setMounted] = useState(false);\n\n if (!menuEl.current && typeof document !== \"undefined\") {\n menuEl.current = document.createElement(\"div\");\n }\n\n useEffect(() => {\n if (!editor || !menuEl.current || editor.isDestroyed) {\n return;\n }\n\n const element = menuEl.current;\n element.className = className;\n\n const plugin = BubbleMenuPlugin({\n editor,\n element,\n pluginKey,\n tippyOptions,\n shouldShow,\n updateDelay,\n });\n\n editor.registerPlugin(plugin);\n setMounted(true);\n\n return () => {\n setMounted(false);\n editor.unregisterPlugin(pluginKey);\n if (element.parentNode) {\n element.parentNode.removeChild(element);\n }\n };\n }, [editor, className, pluginKey]);\n\n if (!editor || !menuEl.current || !mounted) {\n return null;\n }\n\n return createPortal(children, menuEl.current);\n};\n","import React, { useMemo } from \"react\";\nimport {\n tokenizePrompt,\n resolveTemplates,\n Template,\n ColorGradient,\n} from \"@pixerate/editor\";\n\nexport interface TemplateRendererProps {\n /** Raw prompt or text string to render */\n content: string;\n /** Available templates for recursive resolution and custom coloring */\n templates?: Template[];\n /** Custom gradient color map for templates */\n templateColorMap?: Map<string, ColorGradient>;\n /** Whether to resolve nested templates recursively before displaying */\n resolve?: boolean;\n /** Custom class name for the wrapper */\n className?: string;\n /** Click callback for template badges */\n onTemplateClick?: (templateName: string) => void;\n /** Click callback for variable badges */\n onVariableClick?: (variableName: string) => void;\n}\n\nexport const TemplateRenderer: React.FC<TemplateRendererProps> = ({\n content,\n templates = [],\n templateColorMap,\n resolve = false,\n className = \"font-mono text-sm leading-relaxed whitespace-pre-wrap break-words\",\n onTemplateClick,\n onVariableClick,\n}) => {\n const displayText = useMemo(() => {\n if (!resolve || templates.length === 0) return content;\n return resolveTemplates(content, templates);\n }, [content, templates, resolve]);\n\n const tokens = useMemo(() => {\n return tokenizePrompt(displayText);\n }, [displayText]);\n\n return (\n <div className={className}>\n {tokens.map((token, index) => {\n if (token.type === \"template\") {\n const colors = templateColorMap?.get(token.value) || {\n from: \"#f43f5e\",\n to: \"#8b5cf6\",\n };\n const style = {\n \"--color-from\": colors.from,\n \"--color-to\": colors.to,\n } as React.CSSProperties;\n\n return (\n <span\n key={index}\n style={style}\n onClick={() => onTemplateClick?.(token.value)}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-semibold rounded-md border border-[var(--color-to)]/40 bg-gradient-to-r from-[var(--color-from)]/15 to-[var(--color-to)]/15 text-[var(--color-to)] hover:brightness-110 cursor-pointer transition-all\"\n title={`Template: ${token.value}`}\n >\n {`{{${token.value}}}`}\n </span>\n );\n }\n\n if (token.type === \"variable\") {\n return (\n <span\n key={index}\n onClick={() => onVariableClick?.(token.value)}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-medium rounded-md border border-violet-400/40 bg-violet-400/15 text-violet-600 dark:text-violet-300 hover:brightness-110 cursor-pointer transition-all\"\n title={`Variable: ${token.value}`}\n >\n {`{${token.value}}`}\n </span>\n );\n }\n\n if (token.type === \"instruction\") {\n return (\n <span\n key={index}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs italic font-medium rounded-md border border-rose-400/40 bg-rose-400/15 text-rose-600 dark:text-rose-300\"\n title={`Instruction: ${token.value}`}\n >\n {`__${token.value}__`}\n </span>\n );\n }\n\n return <span key={index}>{token.value}</span>;\n })}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAAA;AAAA,EAAA;AAAA;AAAA;;;ACAA,mBAA2C;AAC3C,IAAAC,gBAA0B;AAC1B,yBAAuB;AACvB,mCAAwB;AACxB,mBAAyD;AACzD,oBAQO;AA4CP,IAAM,oBAAgC,CAAC;AACvC,IAAM,oBAAoB,oBAAI,IAA2B;AAAA,EACvD,CAAC,WAAW,EAAE,MAAM,WAAW,IAAI,UAAU,CAAC;AAChD,CAAC;AAEM,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,QAAQ;AACV,GAAyB;AACvB,QAAM,4BAAwB,qBAAO,kBAAkB;AACvD,wBAAsB,UAAU;AAEhC,QAAM,2BAAuB,qBAAO,iBAAiB;AACrD,uBAAqB,UAAU;AAE/B,QAAM,kBAAc,qBAAO,QAAQ;AACnC,cAAY,UAAU;AAEtB,QAAM,mBAAe,qBAAO,SAAS;AACrC,eAAa,UAAU;AAEvB,QAAM,2BAAuB,qBAAO,iBAAiB;AACrD,uBAAqB,UAAU;AAE/B,QAAM,6BAAyB,qBAAO,mBAAmB;AACzD,yBAAuB,UAAU;AAEjC,QAAM,yBAAqB,qBAAO,eAAe;AACjD,qBAAmB,UAAU;AAE7B,QAAM,qBAAiB,qBAAO,WAAW;AACzC,iBAAe,UAAU;AAEzB,QAAM,qBAAiB,qBAAO,WAAW;AACzC,iBAAe,UAAU;AAEzB,QAAM,kBAAc,sBAAQ,MAAM;AAChC,YAAQ,aAAa,CAAC,GACnB,IAAI,CAAC,MAAM,GAAG,EAAE,MAAM,EAAE,IAAI,IAAK,EAAU,iBAAiB,EAAE,EAAE,EAChE,KAAK,EACL,KAAK,GAAG;AAAA,EACb,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,uBAAmB,sBAAQ,MAAM;AACrC,QAAI,aAAc,QAAO;AACzB,UAAM,MAAM,oBAAI,IAA2B;AAC3C,cAAU,QAAQ,CAAC,MAAM;AACvB,UAAI,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,IAAI,UAAU,CAAC;AAAA,IACpD,CAAC;AACD,WAAO,IAAI,OAAO,IAAI,MAAM;AAAA,EAC9B,GAAG,CAAC,cAAc,WAAW,CAAC;AAE9B,QAAM,gBACH,mBAAAC,SAAoB,YACjB,mBAAAA,UACC,mBAAAA,SAAoB,SAAS,YAC3B,mBAAAA,SAAoB,UACrB,mBAAAA;AAER,QAAM,iBACH,6BAAAC,SAAqB,YAClB,6BAAAA,UACC,6BAAAA,SAAqB,SAAS,YAC5B,6BAAAA,SAAqB,UACtB,6BAAAA;AAER,QAAM,aAAS;AAAA,IACb;AAAA,MACE,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,MAClB,kBAAkB;AAAA,MAClB,UAAU;AAAA,MACV,YAAY;AAAA,QACV,cAAc,UAAU;AAAA,UACtB,SAAS;AAAA,UACT,WAAW;AAAA,QACb,CAAC;AAAA,QACD,eAAe,UAAU,EAAE,YAAY,CAAC;AAAA,QACxC,2BAAa,UAAU;AAAA,UACrB,kBAAkB;AAAA,UAClB;AAAA,UACA;AAAA,QACF,CAAC;AAAA,QACD,kCAAoB,UAAU;AAAA,UAC5B;AAAA,QACF,CAAC;AAAA,QACD;AAAA,MACF;AAAA,MACA,aAAS,qCAAsB,OAAO;AAAA,MACtC,UAAU,CAAC,EAAE,QAAQ,GAAG,MAAM;AAC5B,cAAM,gBAAY,6BAAc,EAAE;AAClC,2BAAmB,UAAU,SAAS;AAEtC,YAAI,eAAe,SAAS;AAC1B,cAAI,UAAU,MAAM,4BAA4B,GAAG;AACjD,2BAAe,QAAQ,eAAe;AAAA,UACxC;AACA,cAAI,UAAU,MAAM,cAAc,GAAG;AACnC,2BAAe,QAAQ,YAAY;AAAA,UACrC;AAAA,QACF;AAEA,cAAM,EAAE,OAAO,KAAK,IAAI;AACxB,cAAM,EAAE,UAAU,IAAI;AACtB,cAAM,EAAE,OAAO,MAAM,GAAG,IAAI;AAE5B,6BAAqB,UAAU,EAAE,MAAM,GAAG,CAAC;AAE3C,YAAI,uBAAuB,WAAW,qBAAqB,SAAS;AAClE,gBAAM,mBAAmB,OAAO,QAAQ,cACpC,MAAM,OAAO,YAAY,GAAG,MAAM,YAAY,IAC9C;AACJ,gBAAM,aAAa,iBAAiB;AAAA,YAClC;AAAA,UACF;AAEA,cAAI,YAAY;AACd,kBAAM,SAAS,KAAK,YAAY,MAAM,GAAG;AACzC,kBAAM,cACJ,SAAS,eAAe,eAAe,OAAO,KAC9C,SAAS,eAAe,kBAAkB,KACzC,KAAK,IAAI,UAAU,WAAW;AACjC,kBAAM,kBAAkB,aAAa,sBAAsB;AAE3D,gBAAI,iBAAiB;AACnB,qCAAuB;AAAA,gBACrB;AAAA,gBACA,MAAM,MACJ,WAAW,CAAC,EAAE,UACb,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,IAAI;AAAA,gBACvC,MAAM;AAAA,gBACN;AAAA,kBACE,KAAK,OAAO,SAAS,gBAAgB,MAAM;AAAA,kBAC3C,MAAM,OAAO,OAAO,gBAAgB;AAAA,gBACtC;AAAA,cACF;AAAA,YACF;AAAA,UACF,OAAO;AACL,iCAAqB,QAAQ;AAAA,UAC/B;AAAA,QACF;AAAA,MACF;AAAA,MACA,mBAAmB,CAAC,EAAE,QAAQ,GAAG,MAAM;AACrC,cAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM;AAC9B,6BAAqB,UAAU,EAAE,MAAM,GAAG,CAAC;AAAA,MAC7C;AAAA,MACA,aAAa;AAAA,QACX,YAAY;AAAA,UACV,OACE,aACA;AAAA,UACF,MAAM;AAAA,UACN,cAAc;AAAA,QAChB;AAAA,QACA,cAAc,MAAM,OAAO;AACzB,cAAI,sBAAsB,SAAS;AACjC,gBACE,MAAM,QAAQ,aACd,MAAM,QAAQ,eACd,MAAM,QAAQ,SACd;AACA,oBAAM,eAAe;AACrB,qBAAO;AAAA,YACT;AACA,gBAAI,MAAM,QAAQ,UAAU;AAC1B,mCAAqB,UAAU;AAC/B,oBAAM,eAAe;AACrB,oBAAM,gBAAgB;AACtB,qBAAO;AAAA,YACT;AAAA,UACF;AACA,cAAI,aAAa,WAAW,aAAa,QAAQ,KAAsB,GAAG;AACxE,mBAAO;AAAA,UACT;AACA,cAAI,MAAM,QAAQ,YAAY,YAAY,SAAS;AACjD,wBAAY,QAAQ;AACpB,mBAAO;AAAA,UACT;AACA,iBAAO;AAAA,QACT;AAAA,QACA,aAAa,CAAC,MAAM,UAAU;AAC5B,gBAAM,OAAO,MAAM,eAAe,QAAQ,YAAY;AACtD,cAAI,CAAC,KAAM,QAAO;AAClB,gBAAM,eAAe;AACrB,gBAAM,SAAS,KAAK,MAAM;AAC1B,cAAI,CAAC,OAAQ,QAAO;AAEpB,gBAAM,SAAS,KAAK,QAAQ,UAAU,IAAI,EAAE,MAAM,IAAI;AACtD,gBAAM,QAA2B,CAAC;AAClC,iBAAO,QAAQ,CAAC,SAAS;AACvB,kBAAM,WAAgB,EAAE,MAAM,YAAY;AAC1C,gBAAI,KAAK,SAAS,GAAG;AACnB,uBAAS,UAAU,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,YAClD;AACA,gBAAI;AACF,oBAAM,OAAO,aAAAC,KAAgB,SAAS,QAAQ,QAAQ;AACtD,oBAAM,KAAK,IAAI;AAAA,YACjB,QAAQ;AAAA,YAER;AAAA,UACF,CAAC;AAED,gBAAM,cAAc,mBAAM,QAAQ,sBAAS,UAAU,KAAK,CAAC;AAC3D,gBAAM,KAAK,KAAK,MAAM,GAAG,iBAAiB,WAAW;AACrD,eAAK,SAAS,EAAE;AAChB,iBAAO;AAAA,QACT;AAAA,QACA,qBAAqB,CAAC,MAAM,SAAS,QAAQ,SAAS;AACpD,gBAAM,SACH,SAAiB,KAAK,MAAM,UAC5B,SAAiB,UAClB,MAAM,OAAO;AAEf,cAAI,CAAC,OAAQ,QAAO,mBAAM;AAE1B,gBAAM,SAAS,KAAK,QAAQ,UAAU,IAAI,EAAE,MAAM,IAAI;AACtD,gBAAM,QAA2B,CAAC;AAElC,iBAAO,QAAQ,CAAC,SAAS;AACvB,kBAAM,WAAgB,EAAE,MAAM,YAAY;AAC1C,gBAAI,KAAK,SAAS,GAAG;AACnB,uBAAS,UAAU,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,CAAC;AAAA,YAClD;AACA,gBAAI;AACF,oBAAM,OAAO,aAAAA,KAAgB,SAAS,QAAQ,QAAQ;AACtD,oBAAM,KAAK,IAAI;AAAA,YACjB,QAAQ;AAAA,YAER;AAAA,UACF,CAAC;AAED,iBAAO,mBAAM,QAAQ,sBAAS,UAAU,KAAK,CAAC;AAAA,QAChD;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,kBAAkB,aAAa,WAAW,aAAa,KAAK;AAAA,EAC/D;AAEA,8BAAU,MAAM;AACd,QAAI,UAAU,CAAC,OAAO,aAAa;AACjC,aAAO,YAAY,SAAS;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,QAAQ,SAAS,CAAC;AAEtB,SAAO;AACT;;;AC7TA,IAAAC,gBAA+D;AAC/D,IAAAC,iBAA4D;AASrD,SAASC,WAAU,UAA8B,CAAC,GAAG;AAC1D,QAAM,EAAE,iBAAiB,aAAa,CAAC,GAAG,GAAG,cAAc,IAAI;AAE/D,QAAM,aAAS,qCAAqB,eAAe;AACnD,QAAM,qBAAqB,CAAC,GAAG,QAAQ,GAAG,UAAU;AAEpD,aAAO,cAAAC,WAAgB;AAAA,IACrB,YAAY;AAAA,IACZ,GAAG;AAAA,EACL,CAAC;AACH;;;ACnBA,IAAAC,gBAGO;AAaD;AAPC,IAAM,gBAA8C,CAAC;AAAA,EAC1D;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAM;AACJ,SACE,4CAAC,SAAI,WACH,sDAAC,cAAAC,eAAA,EAAoB,QAAiB,GAAG,OAAO,GAClD;AAEJ;;;ACpBA,IAAAC,gBAAmD;AACnD,uBAA6B;AAC7B,mCAGO;AAaA,IAAM,aAAwC,CAAC;AAAA,EACpD;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,eAAe,EAAE,UAAU,IAAI;AAAA,EAC/B,aAAa;AAAA,EACb,YAAY;AAAA,EACZ;AACF,MAAM;AACJ,QAAM,aAAS,sBAA8B,IAAI;AACjD,QAAM,CAAC,SAAS,UAAU,QAAI,wBAAS,KAAK;AAE5C,MAAI,CAAC,OAAO,WAAW,OAAO,aAAa,aAAa;AACtD,WAAO,UAAU,SAAS,cAAc,KAAK;AAAA,EAC/C;AAEA,+BAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,OAAO,WAAW,OAAO,aAAa;AACpD;AAAA,IACF;AAEA,UAAM,UAAU,OAAO;AACvB,YAAQ,YAAY;AAEpB,UAAM,aAAS,+CAAiB;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO,eAAe,MAAM;AAC5B,eAAW,IAAI;AAEf,WAAO,MAAM;AACX,iBAAW,KAAK;AAChB,aAAO,iBAAiB,SAAS;AACjC,UAAI,QAAQ,YAAY;AACtB,gBAAQ,WAAW,YAAY,OAAO;AAAA,MACxC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,WAAW,SAAS,CAAC;AAEjC,MAAI,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,SAAS;AAC1C,WAAO;AAAA,EACT;AAEA,aAAO,+BAAa,UAAU,OAAO,OAAO;AAC9C;;;ACpEA,IAAAC,gBAA+B;AAC/B,IAAAC,iBAKO;AAmDK,IAAAC,sBAAA;AAhCL,IAAM,mBAAoD,CAAC;AAAA,EAChE;AAAA,EACA,YAAY,CAAC;AAAA,EACb;AAAA,EACA,UAAU;AAAA,EACV,YAAY;AAAA,EACZ;AAAA,EACA;AACF,MAAM;AACJ,QAAM,kBAAc,uBAAQ,MAAM;AAChC,QAAI,CAAC,WAAW,UAAU,WAAW,EAAG,QAAO;AAC/C,eAAO,iCAAiB,SAAS,SAAS;AAAA,EAC5C,GAAG,CAAC,SAAS,WAAW,OAAO,CAAC;AAEhC,QAAM,aAAS,uBAAQ,MAAM;AAC3B,eAAO,+BAAe,WAAW;AAAA,EACnC,GAAG,CAAC,WAAW,CAAC;AAEhB,SACE,6CAAC,SAAI,WACF,iBAAO,IAAI,CAAC,OAAO,UAAU;AAC5B,QAAI,MAAM,SAAS,YAAY;AAC7B,YAAM,SAAS,kBAAkB,IAAI,MAAM,KAAK,KAAK;AAAA,QACnD,MAAM;AAAA,QACN,IAAI;AAAA,MACN;AACA,YAAM,QAAQ;AAAA,QACZ,gBAAgB,OAAO;AAAA,QACvB,cAAc,OAAO;AAAA,MACvB;AAEA,aACE;AAAA,QAAC;AAAA;AAAA,UAEC;AAAA,UACA,SAAS,MAAM,kBAAkB,MAAM,KAAK;AAAA,UAC5C,WAAU;AAAA,UACV,OAAO,aAAa,MAAM,KAAK;AAAA,UAE9B,eAAK,MAAM,KAAK;AAAA;AAAA,QANZ;AAAA,MAOP;AAAA,IAEJ;AAEA,QAAI,MAAM,SAAS,YAAY;AAC7B,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,SAAS,MAAM,kBAAkB,MAAM,KAAK;AAAA,UAC5C,WAAU;AAAA,UACV,OAAO,aAAa,MAAM,KAAK;AAAA,UAE9B,cAAI,MAAM,KAAK;AAAA;AAAA,QALX;AAAA,MAMP;AAAA,IAEJ;AAEA,QAAI,MAAM,SAAS,eAAe;AAChC,aACE;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UACV,OAAO,gBAAgB,MAAM,KAAK;AAAA,UAEjC,eAAK,MAAM,KAAK;AAAA;AAAA,QAJZ;AAAA,MAKP;AAAA,IAEJ;AAEA,WAAO,6CAAC,UAAkB,gBAAM,SAAd,KAAoB;AAAA,EACxC,CAAC,GACH;AAEJ;","names":["useEditor","import_react","StarterKit","Placeholder","ProsemirrorNode","import_react","import_editor","useEditor","useTipTapEditor","import_react","TipTapEditorContent","import_react","import_react","import_editor","import_jsx_runtime"]}
package/dist/index.d.cts CHANGED
@@ -4,6 +4,7 @@ import { Template, ColorGradient, RichTextPresetOptions } from '@pixerate/editor
4
4
  export { ColorGradient, RichTextPresetOptions, Template, TemplateVersion, Token, TokenType } from '@pixerate/editor';
5
5
  import { UseEditorOptions, EditorContentProps as EditorContentProps$1 } from '@tiptap/react';
6
6
  import React from 'react';
7
+ import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu';
7
8
 
8
9
  interface UsePromptEditorProps {
9
10
  /** The initial or controlled content of the editor as plain text */
@@ -66,6 +67,10 @@ interface BubbleMenuProps {
66
67
  editor: Editor | null;
67
68
  className?: string;
68
69
  children: React.ReactNode;
70
+ tippyOptions?: BubbleMenuPluginProps["tippyOptions"];
71
+ shouldShow?: BubbleMenuPluginProps["shouldShow"];
72
+ pluginKey?: BubbleMenuPluginProps["pluginKey"];
73
+ updateDelay?: BubbleMenuPluginProps["updateDelay"];
69
74
  }
70
75
  declare const BubbleMenu: React.FC<BubbleMenuProps>;
71
76
 
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Template, ColorGradient, RichTextPresetOptions } from '@pixerate/editor
4
4
  export { ColorGradient, RichTextPresetOptions, Template, TemplateVersion, Token, TokenType } from '@pixerate/editor';
5
5
  import { UseEditorOptions, EditorContentProps as EditorContentProps$1 } from '@tiptap/react';
6
6
  import React from 'react';
7
+ import { BubbleMenuPluginProps } from '@tiptap/extension-bubble-menu';
7
8
 
8
9
  interface UsePromptEditorProps {
9
10
  /** The initial or controlled content of the editor as plain text */
@@ -66,6 +67,10 @@ interface BubbleMenuProps {
66
67
  editor: Editor | null;
67
68
  className?: string;
68
69
  children: React.ReactNode;
70
+ tippyOptions?: BubbleMenuPluginProps["tippyOptions"];
71
+ shouldShow?: BubbleMenuPluginProps["shouldShow"];
72
+ pluginKey?: BubbleMenuPluginProps["pluginKey"];
73
+ updateDelay?: BubbleMenuPluginProps["updateDelay"];
69
74
  }
70
75
  declare const BubbleMenu: React.FC<BubbleMenuProps>;
71
76
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/hooks/usePromptEditor.ts
2
- import { useMemo, useRef } from "react";
2
+ import { useMemo, useEffect, useRef } from "react";
3
3
  import { useEditor } from "@tiptap/react";
4
4
  import StarterKit from "@tiptap/starter-kit";
5
5
  import Placeholder from "@tiptap/extension-placeholder";
@@ -52,6 +52,9 @@ function usePromptEditor({
52
52
  onMilestoneRef.current = onMilestone;
53
53
  const containerIdRef = useRef(containerId);
54
54
  containerIdRef.current = containerId;
55
+ const templateKey = useMemo(() => {
56
+ return (templates || []).map((t) => `${t.id || t.name}:${t.latestVersion || ""}`).sort().join(",");
57
+ }, [templates]);
55
58
  const resolvedColorMap = useMemo(() => {
56
59
  if (userColorMap) return userColorMap;
57
60
  const map = /* @__PURE__ */ new Map();
@@ -59,7 +62,9 @@ function usePromptEditor({
59
62
  map.set(t.name, { from: "#f43f5e", to: "#8b5cf6" });
60
63
  });
61
64
  return map.size > 0 ? map : DEFAULT_COLOR_MAP;
62
- }, [userColorMap, templates]);
65
+ }, [userColorMap, templateKey]);
66
+ const StarterKitExt = StarterKit?.configure ? StarterKit : StarterKit?.default?.configure ? StarterKit?.default : StarterKit;
67
+ const PlaceholderExt = Placeholder?.configure ? Placeholder : Placeholder?.default?.configure ? Placeholder?.default : Placeholder;
63
68
  const editor = useEditor(
64
69
  {
65
70
  immediatelyRender: false,
@@ -67,11 +72,11 @@ function usePromptEditor({
67
72
  enableInputRules: false,
68
73
  editable: isEditing,
69
74
  extensions: [
70
- StarterKit.configure({
75
+ StarterKitExt.configure({
71
76
  heading: false,
72
77
  codeBlock: false
73
78
  }),
74
- Placeholder.configure({ placeholder }),
79
+ PlaceholderExt.configure({ placeholder }),
75
80
  GradientText.configure({
76
81
  templateColorMap: resolvedColorMap,
77
82
  speed,
@@ -129,18 +134,56 @@ function usePromptEditor({
129
134
  },
130
135
  editorProps: {
131
136
  attributes: {
132
- class: className || "w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words"
137
+ class: className || "w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words",
138
+ role: "textbox",
139
+ "aria-label": "Prompt editor"
133
140
  },
134
- handleKeyDown: (view, event) => {
141
+ handleKeyDown(view, event) {
142
+ if (slashCommandActiveRef.current) {
143
+ if (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter") {
144
+ event.preventDefault();
145
+ return true;
146
+ }
147
+ if (event.key === "Escape") {
148
+ clearSlashCommandRef.current?.();
149
+ event.preventDefault();
150
+ event.stopPropagation();
151
+ return true;
152
+ }
153
+ }
154
+ if (onKeyDownRef.current && onKeyDownRef.current(event)) {
155
+ return true;
156
+ }
135
157
  if (event.key === "Escape" && onEscapeRef.current) {
136
158
  onEscapeRef.current();
137
159
  return true;
138
160
  }
139
- if (onKeyDownRef.current) {
140
- return onKeyDownRef.current(event);
141
- }
142
161
  return false;
143
162
  },
163
+ handlePaste: (view, event) => {
164
+ const text = event.clipboardData?.getData("text/plain");
165
+ if (!text) return false;
166
+ event.preventDefault();
167
+ const schema = view.state.schema;
168
+ if (!schema) return false;
169
+ const blocks = text.replace(/\r\n?/g, "\n").split("\n");
170
+ const nodes = [];
171
+ blocks.forEach((line) => {
172
+ const nodeJson = { type: "paragraph" };
173
+ if (line.length > 0) {
174
+ nodeJson.content = [{ type: "text", text: line }];
175
+ }
176
+ try {
177
+ const node = ProsemirrorNode.fromJSON(schema, nodeJson);
178
+ nodes.push(node);
179
+ } catch {
180
+ }
181
+ });
182
+ const parsedSlice = Slice.maxOpen(Fragment.fromArray(nodes));
183
+ const tr = view.state.tr.replaceSelection(parsedSlice);
184
+ view.dispatch(tr);
185
+ return true;
186
+ },
144
187
  clipboardTextParser: (text, context, _plain, view) => {
145
188
  const schema = context?.doc?.type?.schema || context?.schema || view?.state?.schema;
146
189
  if (!schema) return Slice.empty;
@@ -161,8 +204,13 @@ function usePromptEditor({
161
204
  }
162
205
  }
163
206
  },
164
- [templates, resolvedColorMap, isEditing, placeholder, speed]
207
+ [resolvedColorMap, templateKey, isEditing, placeholder, speed]
165
208
  );
209
+ useEffect(() => {
210
+ if (editor && !editor.isDestroyed) {
211
+ editor.setEditable(isEditing);
212
+ }
213
+ }, [editor, isEditing]);
166
214
  return editor;
167
215
  }
168
216
 
@@ -193,23 +241,53 @@ var EditorContent = ({
193
241
  };
194
242
 
195
243
  // src/components/BubbleMenu.tsx
196
- import { BubbleMenu as TipTapBubbleMenu } from "@tiptap/react";
197
- import { jsx as jsx2 } from "react/jsx-runtime";
244
+ import { useEffect as useEffect2, useRef as useRef2, useState } from "react";
245
+ import { createPortal } from "react-dom";
246
+ import {
247
+ BubbleMenuPlugin
248
+ } from "@tiptap/extension-bubble-menu";
198
249
  var BubbleMenu = ({
199
250
  editor,
200
251
  className = "flex items-center gap-1 bg-popover text-popover-foreground border shadow-md rounded-md p-1",
201
- children
252
+ children,
253
+ tippyOptions = { duration: 100 },
254
+ shouldShow = null,
255
+ pluginKey = "bubbleMenu",
256
+ updateDelay
202
257
  }) => {
203
- if (!editor) return null;
204
- return /* @__PURE__ */ jsx2(
205
- TipTapBubbleMenu,
206
- {
207
- editor,
208
- tippyOptions: { duration: 100 },
209
- className,
210
- children
258
+ const menuEl = useRef2(null);
259
+ const [mounted, setMounted] = useState(false);
260
+ if (!menuEl.current && typeof document !== "undefined") {
261
+ menuEl.current = document.createElement("div");
262
+ }
263
+ useEffect2(() => {
264
+ if (!editor || !menuEl.current || editor.isDestroyed) {
265
+ return;
211
266
  }
212
- );
267
+ const element = menuEl.current;
268
+ element.className = className;
269
+ const plugin = BubbleMenuPlugin({
270
+ editor,
271
+ element,
272
+ pluginKey,
273
+ tippyOptions,
274
+ shouldShow,
275
+ updateDelay
276
+ });
277
+ editor.registerPlugin(plugin);
278
+ setMounted(true);
279
+ return () => {
280
+ setMounted(false);
281
+ editor.unregisterPlugin(pluginKey);
282
+ if (element.parentNode) {
283
+ element.parentNode.removeChild(element);
284
+ }
285
+ };
286
+ }, [editor, className, pluginKey]);
287
+ if (!editor || !menuEl.current || !mounted) {
288
+ return null;
289
+ }
290
+ return createPortal(children, menuEl.current);
213
291
  };
214
292
 
215
293
  // src/components/TemplateRenderer.tsx
@@ -218,7 +296,7 @@ import {
218
296
  tokenizePrompt,
219
297
  resolveTemplates
220
298
  } from "@pixerate/editor";
221
- import { jsx as jsx3 } from "react/jsx-runtime";
299
+ import { jsx as jsx2 } from "react/jsx-runtime";
222
300
  var TemplateRenderer = ({
223
301
  content,
224
302
  templates = [],
@@ -235,7 +313,7 @@ var TemplateRenderer = ({
235
313
  const tokens = useMemo2(() => {
236
314
  return tokenizePrompt(displayText);
237
315
  }, [displayText]);
238
- return /* @__PURE__ */ jsx3("div", { className, children: tokens.map((token, index) => {
316
+ return /* @__PURE__ */ jsx2("div", { className, children: tokens.map((token, index) => {
239
317
  if (token.type === "template") {
240
318
  const colors = templateColorMap?.get(token.value) || {
241
319
  from: "#f43f5e",
@@ -245,7 +323,7 @@ var TemplateRenderer = ({
245
323
  "--color-from": colors.from,
246
324
  "--color-to": colors.to
247
325
  };
248
- return /* @__PURE__ */ jsx3(
326
+ return /* @__PURE__ */ jsx2(
249
327
  "span",
250
328
  {
251
329
  style,
@@ -258,7 +336,7 @@ var TemplateRenderer = ({
258
336
  );
259
337
  }
260
338
  if (token.type === "variable") {
261
- return /* @__PURE__ */ jsx3(
339
+ return /* @__PURE__ */ jsx2(
262
340
  "span",
263
341
  {
264
342
  onClick: () => onVariableClick?.(token.value),
@@ -270,7 +348,7 @@ var TemplateRenderer = ({
270
348
  );
271
349
  }
272
350
  if (token.type === "instruction") {
273
- return /* @__PURE__ */ jsx3(
351
+ return /* @__PURE__ */ jsx2(
274
352
  "span",
275
353
  {
276
354
  className: "inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs italic font-medium rounded-md border border-rose-400/40 bg-rose-400/15 text-rose-600 dark:text-rose-300",
@@ -280,7 +358,7 @@ var TemplateRenderer = ({
280
358
  index
281
359
  );
282
360
  }
283
- return /* @__PURE__ */ jsx3("span", { children: token.value }, index);
361
+ return /* @__PURE__ */ jsx2("span", { children: token.value }, index);
284
362
  }) });
285
363
  };
286
364
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/usePromptEditor.ts","../src/hooks/useEditor.ts","../src/components/EditorContent.tsx","../src/components/BubbleMenu.tsx","../src/components/TemplateRenderer.tsx"],"sourcesContent":["import { useMemo, useEffect, useRef } from \"react\";\nimport { useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport { Slice, Fragment, Node as ProsemirrorNode } from \"@tiptap/pm/model\";\nimport {\n Template,\n ColorGradient,\n GradientText,\n TemplateSuggestions,\n LoadingNode,\n plainTextToTipTapHtml,\n getEditorText,\n} from \"@pixerate/editor\";\n\nexport interface UsePromptEditorProps {\n /** The initial or controlled content of the editor as plain text */\n content: string;\n /** Callback fired when the editor content changes (returns plain text) */\n onContentChange: (text: string) => void;\n /** List of available templates to be suggested and highlighted */\n templates?: Template[];\n /** Custom gradient color map for templates */\n templateColorMap?: Map<string, ColorGradient>;\n /** Whether the editor is editable */\n isEditing?: boolean;\n /** Placeholder text for the editor */\n placeholder?: string;\n /** Additional CSS classes for the editor container */\n className?: string;\n /** Callback fired when the Escape key is pressed */\n onEscape?: () => void;\n /** Custom keydown handler. Return true to prevent default behavior */\n onKeyDown?: (event: KeyboardEvent) => boolean;\n /** Callback fired when text selection changes */\n onSelectionChange?: (selection: { from: number; to: number } | null) => void;\n /** Callback fired when a slash command (e.g., /template) matches */\n onSlashCommandMatch?: (\n match: RegExpMatchArray,\n from: number,\n to: number,\n coords: { top: number; left: number },\n ) => void;\n /** Callback to dismiss/clear the active slash command */\n clearSlashCommand?: () => void;\n /** Whether a slash command menu is currently active */\n slashCommandActive?: boolean;\n /** Callback fired when certain milestones are reached (e.g., used_variable, used_magic) */\n onMilestone?: (milestone: string) => void;\n /** DOM element ID of the container, used for positioning popovers */\n containerId?: string;\n /** Variable names originating from structured JSON output steps */\n jsonVariables?: string[];\n /** Animation speed for gradient decorations */\n speed?: number;\n}\n\nconst DEFAULT_TEMPLATES: Template[] = [];\nconst DEFAULT_COLOR_MAP = new Map<string, ColorGradient>([\n [\"default\", { from: \"#f43f5e\", to: \"#8b5cf6\" }],\n]);\n\nexport function usePromptEditor({\n content,\n onContentChange,\n templates = DEFAULT_TEMPLATES,\n templateColorMap: userColorMap,\n isEditing = true,\n placeholder = \"Write a prompt, use {{template}}, {variable}, or /command...\",\n className,\n onEscape,\n onKeyDown,\n onSelectionChange,\n onSlashCommandMatch,\n clearSlashCommand,\n slashCommandActive = false,\n onMilestone,\n containerId = \"editor-container\",\n jsonVariables,\n speed = 2,\n}: UsePromptEditorProps) {\n const slashCommandActiveRef = useRef(slashCommandActive);\n slashCommandActiveRef.current = slashCommandActive;\n\n const clearSlashCommandRef = useRef(clearSlashCommand);\n clearSlashCommandRef.current = clearSlashCommand;\n\n const onEscapeRef = useRef(onEscape);\n onEscapeRef.current = onEscape;\n\n const onKeyDownRef = useRef(onKeyDown);\n onKeyDownRef.current = onKeyDown;\n\n const onSelectionChangeRef = useRef(onSelectionChange);\n onSelectionChangeRef.current = onSelectionChange;\n\n const onSlashCommandMatchRef = useRef(onSlashCommandMatch);\n onSlashCommandMatchRef.current = onSlashCommandMatch;\n\n const onContentChangeRef = useRef(onContentChange);\n onContentChangeRef.current = onContentChange;\n\n const onMilestoneRef = useRef(onMilestone);\n onMilestoneRef.current = onMilestone;\n\n const containerIdRef = useRef(containerId);\n containerIdRef.current = containerId;\n\n const resolvedColorMap = useMemo(() => {\n if (userColorMap) return userColorMap;\n const map = new Map<string, ColorGradient>();\n templates.forEach((t) => {\n map.set(t.name, { from: \"#f43f5e\", to: \"#8b5cf6\" });\n });\n return map.size > 0 ? map : DEFAULT_COLOR_MAP;\n }, [userColorMap, templates]);\n\n const editor = useEditor(\n {\n immediatelyRender: false,\n enablePasteRules: false,\n enableInputRules: false,\n editable: isEditing,\n extensions: [\n StarterKit.configure({\n heading: false,\n codeBlock: false,\n }),\n Placeholder.configure({ placeholder }),\n GradientText.configure({\n templateColorMap: resolvedColorMap,\n speed,\n jsonVariables,\n }),\n TemplateSuggestions.configure({\n templates,\n }),\n LoadingNode,\n ],\n content: plainTextToTipTapHtml(content),\n onUpdate: ({ editor: ed }) => {\n const plainText = getEditorText(ed);\n onContentChangeRef.current?.(plainText);\n\n if (onMilestoneRef.current) {\n if (plainText.match(/(?<!\\{)\\{([^{}]+)\\}(?!\\})/g)) {\n onMilestoneRef.current(\"used_variable\");\n }\n if (plainText.match(/__([^_]+)__/g)) {\n onMilestoneRef.current(\"used_magic\");\n }\n }\n\n const { state, view } = ed;\n const { selection } = state;\n const { $from, from, to } = selection;\n\n onSelectionChangeRef.current?.({ from, to });\n\n if (onSlashCommandMatchRef.current && clearSlashCommandRef.current) {\n const textBeforeCursor = $from?.parent?.textBetween\n ? $from.parent.textBetween(0, $from.parentOffset)\n : \"\";\n const slashMatch = textBeforeCursor.match(\n /(?:^|\\s)\\/([a-zA-Z0-9_-]*)$/,\n );\n\n if (slashMatch) {\n const coords = view.coordsAtPos($from.pos);\n const containerEl =\n document.getElementById(containerIdRef.current) ||\n document.getElementById(\"editor-container\") ||\n (view.dom.closest?.(\".relative\") as HTMLElement | null);\n const containerCoords = containerEl?.getBoundingClientRect();\n\n if (containerCoords) {\n onSlashCommandMatchRef.current(\n slashMatch,\n $from.pos -\n slashMatch[0].length +\n (slashMatch[0].startsWith(\" \") ? 1 : 0),\n $from.pos,\n {\n top: coords.bottom - containerCoords.top + 5,\n left: coords.left - containerCoords.left,\n },\n );\n }\n } else {\n clearSlashCommandRef.current();\n }\n }\n },\n onSelectionUpdate: ({ editor: ed }) => {\n const { from, to } = ed.state.selection;\n onSelectionChangeRef.current?.({ from, to });\n },\n editorProps: {\n attributes: {\n class:\n className ||\n \"w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words\",\n },\n handleKeyDown: (view, event) => {\n if (event.key === \"Escape\" && onEscapeRef.current) {\n onEscapeRef.current();\n return true;\n }\n if (onKeyDownRef.current) {\n return onKeyDownRef.current(event);\n }\n return false;\n },\n clipboardTextParser: (text, context, _plain, view) => {\n const schema =\n (context as any)?.doc?.type?.schema ||\n (context as any)?.schema ||\n view?.state?.schema;\n\n if (!schema) return Slice.empty;\n\n const blocks = text.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const nodes: ProsemirrorNode[] = [];\n\n blocks.forEach((line) => {\n const nodeJson: any = { type: \"paragraph\" };\n if (line.length > 0) {\n nodeJson.content = [{ type: \"text\", text: line }];\n }\n try {\n const node = ProsemirrorNode.fromJSON(schema, nodeJson);\n nodes.push(node);\n } catch {\n // Ignore\n }\n });\n\n return Slice.maxOpen(Fragment.fromArray(nodes));\n },\n },\n },\n [templates, resolvedColorMap, isEditing, placeholder, speed],\n );\n\n return editor;\n}\n","import { useEditor as useTipTapEditor, UseEditorOptions } from \"@tiptap/react\";\nimport { createRichTextPreset, RichTextPresetOptions } from \"@pixerate/editor\";\n\nexport interface UseRichEditorProps extends UseEditorOptions {\n richTextOptions?: RichTextPresetOptions;\n}\n\n/**\n * Hook to initialize a TipTap rich-text editor instance with pre-configured extensions.\n */\nexport function useEditor(options: UseRichEditorProps = {}) {\n const { richTextOptions, extensions = [], ...editorOptions } = options;\n\n const preset = createRichTextPreset(richTextOptions);\n const combinedExtensions = [...preset, ...extensions];\n\n return useTipTapEditor({\n extensions: combinedExtensions,\n ...editorOptions,\n });\n}\n","import React from \"react\";\nimport {\n EditorContent as TipTapEditorContent,\n EditorContentProps as TipTapEditorContentProps,\n} from \"@tiptap/react\";\n\nexport interface EditorContentProps extends TipTapEditorContentProps {\n className?: string;\n}\n\nexport const EditorContent: React.FC<EditorContentProps> = ({\n className,\n editor,\n ...props\n}) => {\n return (\n <div className={className}>\n <TipTapEditorContent editor={editor} {...props} />\n </div>\n );\n};\n","import React from \"react\";\nimport { BubbleMenu as TipTapBubbleMenu } from \"@tiptap/react\";\nimport type { Editor } from \"@tiptap/core\";\n\nexport interface BubbleMenuProps {\n editor: Editor | null;\n className?: string;\n children: React.ReactNode;\n}\n\nexport const BubbleMenu: React.FC<BubbleMenuProps> = ({\n editor,\n className = \"flex items-center gap-1 bg-popover text-popover-foreground border shadow-md rounded-md p-1\",\n children,\n}) => {\n if (!editor) return null;\n\n return (\n <TipTapBubbleMenu\n editor={editor}\n tippyOptions={{ duration: 100 }}\n className={className}\n >\n {children}\n </TipTapBubbleMenu>\n );\n};\n","import React, { useMemo } from \"react\";\nimport {\n tokenizePrompt,\n resolveTemplates,\n Template,\n ColorGradient,\n} from \"@pixerate/editor\";\n\nexport interface TemplateRendererProps {\n /** Raw prompt or text string to render */\n content: string;\n /** Available templates for recursive resolution and custom coloring */\n templates?: Template[];\n /** Custom gradient color map for templates */\n templateColorMap?: Map<string, ColorGradient>;\n /** Whether to resolve nested templates recursively before displaying */\n resolve?: boolean;\n /** Custom class name for the wrapper */\n className?: string;\n /** Click callback for template badges */\n onTemplateClick?: (templateName: string) => void;\n /** Click callback for variable badges */\n onVariableClick?: (variableName: string) => void;\n}\n\nexport const TemplateRenderer: React.FC<TemplateRendererProps> = ({\n content,\n templates = [],\n templateColorMap,\n resolve = false,\n className = \"font-mono text-sm leading-relaxed whitespace-pre-wrap break-words\",\n onTemplateClick,\n onVariableClick,\n}) => {\n const displayText = useMemo(() => {\n if (!resolve || templates.length === 0) return content;\n return resolveTemplates(content, templates);\n }, [content, templates, resolve]);\n\n const tokens = useMemo(() => {\n return tokenizePrompt(displayText);\n }, [displayText]);\n\n return (\n <div className={className}>\n {tokens.map((token, index) => {\n if (token.type === \"template\") {\n const colors = templateColorMap?.get(token.value) || {\n from: \"#f43f5e\",\n to: \"#8b5cf6\",\n };\n const style = {\n \"--color-from\": colors.from,\n \"--color-to\": colors.to,\n } as React.CSSProperties;\n\n return (\n <span\n key={index}\n style={style}\n onClick={() => onTemplateClick?.(token.value)}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-semibold rounded-md border border-[var(--color-to)]/40 bg-gradient-to-r from-[var(--color-from)]/15 to-[var(--color-to)]/15 text-[var(--color-to)] hover:brightness-110 cursor-pointer transition-all\"\n title={`Template: ${token.value}`}\n >\n {`{{${token.value}}}`}\n </span>\n );\n }\n\n if (token.type === \"variable\") {\n return (\n <span\n key={index}\n onClick={() => onVariableClick?.(token.value)}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-medium rounded-md border border-violet-400/40 bg-violet-400/15 text-violet-600 dark:text-violet-300 hover:brightness-110 cursor-pointer transition-all\"\n title={`Variable: ${token.value}`}\n >\n {`{${token.value}}`}\n </span>\n );\n }\n\n if (token.type === \"instruction\") {\n return (\n <span\n key={index}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs italic font-medium rounded-md border border-rose-400/40 bg-rose-400/15 text-rose-600 dark:text-rose-300\"\n title={`Instruction: ${token.value}`}\n >\n {`__${token.value}__`}\n </span>\n );\n }\n\n return <span key={index}>{token.value}</span>;\n })}\n </div>\n );\n};\n"],"mappings":"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","names":["useEditor","jsx","useMemo","jsx"]}
1
+ {"version":3,"sources":["../src/hooks/usePromptEditor.ts","../src/hooks/useEditor.ts","../src/components/EditorContent.tsx","../src/components/BubbleMenu.tsx","../src/components/TemplateRenderer.tsx"],"sourcesContent":["import { useMemo, useEffect, useRef } from \"react\";\nimport { useEditor } from \"@tiptap/react\";\nimport StarterKit from \"@tiptap/starter-kit\";\nimport Placeholder from \"@tiptap/extension-placeholder\";\nimport { Slice, Fragment, Node as ProsemirrorNode } from \"@tiptap/pm/model\";\nimport {\n Template,\n ColorGradient,\n GradientText,\n TemplateSuggestions,\n LoadingNode,\n plainTextToTipTapHtml,\n getEditorText,\n} from \"@pixerate/editor\";\n\nexport interface UsePromptEditorProps {\n /** The initial or controlled content of the editor as plain text */\n content: string;\n /** Callback fired when the editor content changes (returns plain text) */\n onContentChange: (text: string) => void;\n /** List of available templates to be suggested and highlighted */\n templates?: Template[];\n /** Custom gradient color map for templates */\n templateColorMap?: Map<string, ColorGradient>;\n /** Whether the editor is editable */\n isEditing?: boolean;\n /** Placeholder text for the editor */\n placeholder?: string;\n /** Additional CSS classes for the editor container */\n className?: string;\n /** Callback fired when the Escape key is pressed */\n onEscape?: () => void;\n /** Custom keydown handler. Return true to prevent default behavior */\n onKeyDown?: (event: KeyboardEvent) => boolean;\n /** Callback fired when text selection changes */\n onSelectionChange?: (selection: { from: number; to: number } | null) => void;\n /** Callback fired when a slash command (e.g., /template) matches */\n onSlashCommandMatch?: (\n match: RegExpMatchArray,\n from: number,\n to: number,\n coords: { top: number; left: number },\n ) => void;\n /** Callback to dismiss/clear the active slash command */\n clearSlashCommand?: () => void;\n /** Whether a slash command menu is currently active */\n slashCommandActive?: boolean;\n /** Callback fired when certain milestones are reached (e.g., used_variable, used_magic) */\n onMilestone?: (milestone: string) => void;\n /** DOM element ID of the container, used for positioning popovers */\n containerId?: string;\n /** Variable names originating from structured JSON output steps */\n jsonVariables?: string[];\n /** Animation speed for gradient decorations */\n speed?: number;\n}\n\nconst DEFAULT_TEMPLATES: Template[] = [];\nconst DEFAULT_COLOR_MAP = new Map<string, ColorGradient>([\n [\"default\", { from: \"#f43f5e\", to: \"#8b5cf6\" }],\n]);\n\nexport function usePromptEditor({\n content,\n onContentChange,\n templates = DEFAULT_TEMPLATES,\n templateColorMap: userColorMap,\n isEditing = true,\n placeholder = \"Write a prompt, use {{template}}, {variable}, or /command...\",\n className,\n onEscape,\n onKeyDown,\n onSelectionChange,\n onSlashCommandMatch,\n clearSlashCommand,\n slashCommandActive = false,\n onMilestone,\n containerId = \"editor-container\",\n jsonVariables,\n speed = 2,\n}: UsePromptEditorProps) {\n const slashCommandActiveRef = useRef(slashCommandActive);\n slashCommandActiveRef.current = slashCommandActive;\n\n const clearSlashCommandRef = useRef(clearSlashCommand);\n clearSlashCommandRef.current = clearSlashCommand;\n\n const onEscapeRef = useRef(onEscape);\n onEscapeRef.current = onEscape;\n\n const onKeyDownRef = useRef(onKeyDown);\n onKeyDownRef.current = onKeyDown;\n\n const onSelectionChangeRef = useRef(onSelectionChange);\n onSelectionChangeRef.current = onSelectionChange;\n\n const onSlashCommandMatchRef = useRef(onSlashCommandMatch);\n onSlashCommandMatchRef.current = onSlashCommandMatch;\n\n const onContentChangeRef = useRef(onContentChange);\n onContentChangeRef.current = onContentChange;\n\n const onMilestoneRef = useRef(onMilestone);\n onMilestoneRef.current = onMilestone;\n\n const containerIdRef = useRef(containerId);\n containerIdRef.current = containerId;\n\n const templateKey = useMemo(() => {\n return (templates || [])\n .map((t) => `${t.id || t.name}:${(t as any).latestVersion || \"\"}`)\n .sort()\n .join(\",\");\n }, [templates]);\n\n const resolvedColorMap = useMemo(() => {\n if (userColorMap) return userColorMap;\n const map = new Map<string, ColorGradient>();\n templates.forEach((t) => {\n map.set(t.name, { from: \"#f43f5e\", to: \"#8b5cf6\" });\n });\n return map.size > 0 ? map : DEFAULT_COLOR_MAP;\n }, [userColorMap, templateKey]);\n\n const StarterKitExt =\n (StarterKit as any)?.configure\n ? StarterKit\n : (StarterKit as any)?.default?.configure\n ? (StarterKit as any)?.default\n : StarterKit;\n\n const PlaceholderExt =\n (Placeholder as any)?.configure\n ? Placeholder\n : (Placeholder as any)?.default?.configure\n ? (Placeholder as any)?.default\n : Placeholder;\n\n const editor = useEditor(\n {\n immediatelyRender: false,\n enablePasteRules: false,\n enableInputRules: false,\n editable: isEditing,\n extensions: [\n StarterKitExt.configure({\n heading: false,\n codeBlock: false,\n }),\n PlaceholderExt.configure({ placeholder }),\n GradientText.configure({\n templateColorMap: resolvedColorMap,\n speed,\n jsonVariables,\n }),\n TemplateSuggestions.configure({\n templates,\n }),\n LoadingNode,\n ],\n content: plainTextToTipTapHtml(content),\n onUpdate: ({ editor: ed }) => {\n const plainText = getEditorText(ed);\n onContentChangeRef.current?.(plainText);\n\n if (onMilestoneRef.current) {\n if (plainText.match(/(?<!\\{)\\{([^{}]+)\\}(?!\\})/g)) {\n onMilestoneRef.current(\"used_variable\");\n }\n if (plainText.match(/__([^_]+)__/g)) {\n onMilestoneRef.current(\"used_magic\");\n }\n }\n\n const { state, view } = ed;\n const { selection } = state;\n const { $from, from, to } = selection;\n\n onSelectionChangeRef.current?.({ from, to });\n\n if (onSlashCommandMatchRef.current && clearSlashCommandRef.current) {\n const textBeforeCursor = $from?.parent?.textBetween\n ? $from.parent.textBetween(0, $from.parentOffset)\n : \"\";\n const slashMatch = textBeforeCursor.match(\n /(?:^|\\s)\\/([a-zA-Z0-9_-]*)$/,\n );\n\n if (slashMatch) {\n const coords = view.coordsAtPos($from.pos);\n const containerEl =\n document.getElementById(containerIdRef.current) ||\n document.getElementById(\"editor-container\") ||\n (view.dom.closest?.(\".relative\") as HTMLElement | null);\n const containerCoords = containerEl?.getBoundingClientRect();\n\n if (containerCoords) {\n onSlashCommandMatchRef.current(\n slashMatch,\n $from.pos -\n slashMatch[0].length +\n (slashMatch[0].startsWith(\" \") ? 1 : 0),\n $from.pos,\n {\n top: coords.bottom - containerCoords.top + 5,\n left: coords.left - containerCoords.left,\n },\n );\n }\n } else {\n clearSlashCommandRef.current();\n }\n }\n },\n onSelectionUpdate: ({ editor: ed }) => {\n const { from, to } = ed.state.selection;\n onSelectionChangeRef.current?.({ from, to });\n },\n editorProps: {\n attributes: {\n class:\n className ||\n \"w-full border border-input rounded-md p-3 font-mono text-sm leading-relaxed focus:outline-none focus:ring-2 focus:ring-primary whitespace-pre-wrap break-words\",\n role: \"textbox\",\n \"aria-label\": \"Prompt editor\",\n },\n handleKeyDown(view, event) {\n if (slashCommandActiveRef.current) {\n if (\n event.key === \"ArrowUp\" ||\n event.key === \"ArrowDown\" ||\n event.key === \"Enter\"\n ) {\n event.preventDefault();\n return true;\n }\n if (event.key === \"Escape\") {\n clearSlashCommandRef.current?.();\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n }\n if (onKeyDownRef.current && onKeyDownRef.current(event as KeyboardEvent)) {\n return true;\n }\n if (event.key === \"Escape\" && onEscapeRef.current) {\n onEscapeRef.current();\n return true;\n }\n return false;\n },\n handlePaste: (view, event) => {\n const text = event.clipboardData?.getData(\"text/plain\");\n if (!text) return false;\n event.preventDefault();\n const schema = view.state.schema;\n if (!schema) return false;\n\n const blocks = text.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const nodes: ProsemirrorNode[] = [];\n blocks.forEach((line) => {\n const nodeJson: any = { type: \"paragraph\" };\n if (line.length > 0) {\n nodeJson.content = [{ type: \"text\", text: line }];\n }\n try {\n const node = ProsemirrorNode.fromJSON(schema, nodeJson);\n nodes.push(node);\n } catch {\n // Ignore\n }\n });\n\n const parsedSlice = Slice.maxOpen(Fragment.fromArray(nodes));\n const tr = view.state.tr.replaceSelection(parsedSlice);\n view.dispatch(tr);\n return true;\n },\n clipboardTextParser: (text, context, _plain, view) => {\n const schema =\n (context as any)?.doc?.type?.schema ||\n (context as any)?.schema ||\n view?.state?.schema;\n\n if (!schema) return Slice.empty;\n\n const blocks = text.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n const nodes: ProsemirrorNode[] = [];\n\n blocks.forEach((line) => {\n const nodeJson: any = { type: \"paragraph\" };\n if (line.length > 0) {\n nodeJson.content = [{ type: \"text\", text: line }];\n }\n try {\n const node = ProsemirrorNode.fromJSON(schema, nodeJson);\n nodes.push(node);\n } catch {\n // Ignore\n }\n });\n\n return Slice.maxOpen(Fragment.fromArray(nodes));\n },\n },\n },\n [resolvedColorMap, templateKey, isEditing, placeholder, speed],\n );\n\n useEffect(() => {\n if (editor && !editor.isDestroyed) {\n editor.setEditable(isEditing);\n }\n }, [editor, isEditing]);\n\n return editor;\n}\n","import { useEditor as useTipTapEditor, UseEditorOptions } from \"@tiptap/react\";\nimport { createRichTextPreset, RichTextPresetOptions } from \"@pixerate/editor\";\n\nexport interface UseRichEditorProps extends UseEditorOptions {\n richTextOptions?: RichTextPresetOptions;\n}\n\n/**\n * Hook to initialize a TipTap rich-text editor instance with pre-configured extensions.\n */\nexport function useEditor(options: UseRichEditorProps = {}) {\n const { richTextOptions, extensions = [], ...editorOptions } = options;\n\n const preset = createRichTextPreset(richTextOptions);\n const combinedExtensions = [...preset, ...extensions];\n\n return useTipTapEditor({\n extensions: combinedExtensions,\n ...editorOptions,\n });\n}\n","import React from \"react\";\nimport {\n EditorContent as TipTapEditorContent,\n EditorContentProps as TipTapEditorContentProps,\n} from \"@tiptap/react\";\n\nexport interface EditorContentProps extends TipTapEditorContentProps {\n className?: string;\n}\n\nexport const EditorContent: React.FC<EditorContentProps> = ({\n className,\n editor,\n ...props\n}) => {\n return (\n <div className={className}>\n <TipTapEditorContent editor={editor} {...props} />\n </div>\n );\n};\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n BubbleMenuPlugin,\n type BubbleMenuPluginProps,\n} from \"@tiptap/extension-bubble-menu\";\nimport type { Editor } from \"@tiptap/core\";\n\nexport interface BubbleMenuProps {\n editor: Editor | null;\n className?: string;\n children: React.ReactNode;\n tippyOptions?: BubbleMenuPluginProps[\"tippyOptions\"];\n shouldShow?: BubbleMenuPluginProps[\"shouldShow\"];\n pluginKey?: BubbleMenuPluginProps[\"pluginKey\"];\n updateDelay?: BubbleMenuPluginProps[\"updateDelay\"];\n}\n\nexport const BubbleMenu: React.FC<BubbleMenuProps> = ({\n editor,\n className = \"flex items-center gap-1 bg-popover text-popover-foreground border shadow-md rounded-md p-1\",\n children,\n tippyOptions = { duration: 100 },\n shouldShow = null,\n pluginKey = \"bubbleMenu\",\n updateDelay,\n}) => {\n const menuEl = useRef<HTMLDivElement | null>(null);\n const [mounted, setMounted] = useState(false);\n\n if (!menuEl.current && typeof document !== \"undefined\") {\n menuEl.current = document.createElement(\"div\");\n }\n\n useEffect(() => {\n if (!editor || !menuEl.current || editor.isDestroyed) {\n return;\n }\n\n const element = menuEl.current;\n element.className = className;\n\n const plugin = BubbleMenuPlugin({\n editor,\n element,\n pluginKey,\n tippyOptions,\n shouldShow,\n updateDelay,\n });\n\n editor.registerPlugin(plugin);\n setMounted(true);\n\n return () => {\n setMounted(false);\n editor.unregisterPlugin(pluginKey);\n if (element.parentNode) {\n element.parentNode.removeChild(element);\n }\n };\n }, [editor, className, pluginKey]);\n\n if (!editor || !menuEl.current || !mounted) {\n return null;\n }\n\n return createPortal(children, menuEl.current);\n};\n","import React, { useMemo } from \"react\";\nimport {\n tokenizePrompt,\n resolveTemplates,\n Template,\n ColorGradient,\n} from \"@pixerate/editor\";\n\nexport interface TemplateRendererProps {\n /** Raw prompt or text string to render */\n content: string;\n /** Available templates for recursive resolution and custom coloring */\n templates?: Template[];\n /** Custom gradient color map for templates */\n templateColorMap?: Map<string, ColorGradient>;\n /** Whether to resolve nested templates recursively before displaying */\n resolve?: boolean;\n /** Custom class name for the wrapper */\n className?: string;\n /** Click callback for template badges */\n onTemplateClick?: (templateName: string) => void;\n /** Click callback for variable badges */\n onVariableClick?: (variableName: string) => void;\n}\n\nexport const TemplateRenderer: React.FC<TemplateRendererProps> = ({\n content,\n templates = [],\n templateColorMap,\n resolve = false,\n className = \"font-mono text-sm leading-relaxed whitespace-pre-wrap break-words\",\n onTemplateClick,\n onVariableClick,\n}) => {\n const displayText = useMemo(() => {\n if (!resolve || templates.length === 0) return content;\n return resolveTemplates(content, templates);\n }, [content, templates, resolve]);\n\n const tokens = useMemo(() => {\n return tokenizePrompt(displayText);\n }, [displayText]);\n\n return (\n <div className={className}>\n {tokens.map((token, index) => {\n if (token.type === \"template\") {\n const colors = templateColorMap?.get(token.value) || {\n from: \"#f43f5e\",\n to: \"#8b5cf6\",\n };\n const style = {\n \"--color-from\": colors.from,\n \"--color-to\": colors.to,\n } as React.CSSProperties;\n\n return (\n <span\n key={index}\n style={style}\n onClick={() => onTemplateClick?.(token.value)}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-semibold rounded-md border border-[var(--color-to)]/40 bg-gradient-to-r from-[var(--color-from)]/15 to-[var(--color-to)]/15 text-[var(--color-to)] hover:brightness-110 cursor-pointer transition-all\"\n title={`Template: ${token.value}`}\n >\n {`{{${token.value}}}`}\n </span>\n );\n }\n\n if (token.type === \"variable\") {\n return (\n <span\n key={index}\n onClick={() => onVariableClick?.(token.value)}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs font-medium rounded-md border border-violet-400/40 bg-violet-400/15 text-violet-600 dark:text-violet-300 hover:brightness-110 cursor-pointer transition-all\"\n title={`Variable: ${token.value}`}\n >\n {`{${token.value}}`}\n </span>\n );\n }\n\n if (token.type === \"instruction\") {\n return (\n <span\n key={index}\n className=\"inline-flex items-center px-1.5 py-0.5 mx-0.5 text-xs italic font-medium rounded-md border border-rose-400/40 bg-rose-400/15 text-rose-600 dark:text-rose-300\"\n title={`Instruction: ${token.value}`}\n >\n {`__${token.value}__`}\n </span>\n );\n }\n\n return <span key={index}>{token.value}</span>;\n })}\n </div>\n );\n};\n"],"mappings":"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","names":["useEditor","useEffect","useRef","useMemo","jsx"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-react",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "React bindings and prompt/rich-text components for @pixerate/editor",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -24,13 +24,13 @@
24
24
  "dist"
25
25
  ],
26
26
  "dependencies": {
27
- "@tiptap/core": "^2.11.5",
28
- "@tiptap/extension-bubble-menu": "^2.11.5",
29
- "@tiptap/extension-placeholder": "^2.11.5",
30
- "@tiptap/pm": "^2.11.5",
31
- "@tiptap/react": "^2.11.5",
32
- "@tiptap/starter-kit": "^2.11.5",
33
- "@pixerate/editor": "0.2.0"
27
+ "@tiptap/core": "^2.11.5 || ^3.0.0",
28
+ "@tiptap/extension-bubble-menu": "^2.11.5 || ^3.0.0",
29
+ "@tiptap/extension-placeholder": "^2.11.5 || ^3.0.0",
30
+ "@tiptap/pm": "^2.11.5 || ^3.0.0",
31
+ "@tiptap/react": "^2.11.5 || ^3.0.0",
32
+ "@tiptap/starter-kit": "^2.11.5 || ^3.0.0",
33
+ "@pixerate/editor": "0.2.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": ">=18.0.0",