@illinois-grad/grad-vue-rte 2.3.4 → 2.4.0

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.
@@ -1,40 +1,73 @@
1
- interface Props {
1
+ /**
2
+ * The GChatInput component provides a rich text editing experience using Tiptap. It supports:
3
+ *
4
+ * - **Bold** and *italic* text formatting
5
+ * - Bullet and numbered lists
6
+ * - Bubble menu for formatting (appears when text is selected)
7
+ * - Press <kbd>Enter</kbd> to send, <kbd>Shift+Enter</kbd> for new line
8
+ * - Undo/redo support
9
+ *
10
+ * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.
11
+ */
12
+ declare const _default: typeof __VLS_export;
13
+ export default _default;
14
+ declare const __VLS_export: import("vue").DefineComponent<{
2
15
  /**
3
16
  * Placeholder text
17
+ * @demo
4
18
  */
5
19
  placeholder?: string;
6
20
  /**
7
21
  * Disabled
22
+ * @demo
8
23
  */
9
24
  disabled?: boolean;
10
25
  /**
11
26
  * Maximum number of rows
27
+ * @demo
12
28
  */
13
29
  maxRows?: number;
14
30
  /**
15
31
  * Accessible label
32
+ * @demo
16
33
  */
17
34
  label?: string;
18
- }
19
- type __VLS_Props = Props;
20
- declare function focusInput(): void;
21
- type __VLS_ModelProps = {
35
+ } & {
22
36
  modelValue?: object | "";
23
- };
24
- type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
25
- declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
26
- focusInput: typeof focusInput;
37
+ }, {
38
+ focusInput: () => void;
27
39
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
28
- send: (content: object) => any;
29
40
  "update:modelValue": (value: object | "" | undefined) => any;
30
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
31
- onSend?: ((content: object) => any) | undefined;
41
+ send: (content: object) => any;
42
+ }, string, import("vue").PublicProps, Readonly<{
43
+ /**
44
+ * Placeholder text
45
+ * @demo
46
+ */
47
+ placeholder?: string;
48
+ /**
49
+ * Disabled
50
+ * @demo
51
+ */
52
+ disabled?: boolean;
53
+ /**
54
+ * Maximum number of rows
55
+ * @demo
56
+ */
57
+ maxRows?: number;
58
+ /**
59
+ * Accessible label
60
+ * @demo
61
+ */
62
+ label?: string;
63
+ } & {
64
+ modelValue?: object | "";
65
+ }> & Readonly<{
32
66
  "onUpdate:modelValue"?: ((value: object | "" | undefined) => any) | undefined;
67
+ onSend?: ((content: object) => any) | undefined;
33
68
  }>, {
34
69
  placeholder: string;
35
70
  label: string;
36
71
  disabled: boolean;
37
72
  maxRows: number;
38
73
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
- declare const _default: typeof __VLS_export;
40
- export default _default;
@@ -1,28 +1,48 @@
1
- interface Props {
1
+ /**
2
+ * The GNoteInput component provides a rich text editing experience using Tiptap for writing notes. It supports:
3
+ *
4
+ * - **Bold** and *italic* text formatting
5
+ * - Bullet and numbered lists
6
+ * - Always visible toolbar for formatting
7
+ * - Undo/redo support
8
+ *
9
+ * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.
10
+ */
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
13
+ declare const __VLS_export: import("vue").DefineComponent<{
2
14
  /**
3
15
  * Placeholder text
16
+ * @demo
4
17
  */
5
18
  placeholder?: string;
6
19
  /**
7
20
  * Accessible label
21
+ * @demo
8
22
  */
9
23
  label?: string;
10
- }
11
- type __VLS_Props = Props;
12
- declare function focusInput(): void;
13
- type __VLS_ModelProps = {
24
+ } & {
14
25
  modelValue?: object | "";
15
- };
16
- type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
17
- declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
18
- focusInput: typeof focusInput;
26
+ }, {
27
+ focusInput: () => void;
19
28
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
20
29
  "update:modelValue": (value: object | "" | undefined) => any;
21
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
30
+ }, string, import("vue").PublicProps, Readonly<{
31
+ /**
32
+ * Placeholder text
33
+ * @demo
34
+ */
35
+ placeholder?: string;
36
+ /**
37
+ * Accessible label
38
+ * @demo
39
+ */
40
+ label?: string;
41
+ } & {
42
+ modelValue?: object | "";
43
+ }> & Readonly<{
22
44
  "onUpdate:modelValue"?: ((value: object | "" | undefined) => any) | undefined;
23
45
  }>, {
24
46
  placeholder: string;
25
47
  label: string;
26
48
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
- declare const _default: typeof __VLS_export;
28
- export default _default;
@@ -1,10 +1,38 @@
1
- interface Props {
1
+ /**
2
+ * Renders a JSON string of tiptap content as HTML.
3
+ * Supports all formatting produced by GChatInput and GNoteInput:
4
+ * bold, italic, ordered lists, and bullet lists.
5
+ *
6
+ * - Empty content is handled gracefully (renders nothing).
7
+ * - Displays an error message when the content cannot be parsed or rendered.
8
+ *
9
+ * The rendering only happens in the client when used with Nuxt.js.
10
+ *
11
+ * **Security note**: rendered HTML is produced by tiptap's `generateHTML`, which only
12
+ * serializes recognized document nodes - it does not inject raw HTML from the JSON.
13
+ *
14
+ * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.
15
+ */
16
+ declare const _default: typeof __VLS_export;
17
+ export default _default;
18
+ declare const __VLS_export: import("vue").DefineComponent<{
2
19
  /**
3
20
  * Error message when rendering fails
21
+ * @demo
4
22
  */
5
23
  error?: string;
24
+ /**
25
+ * JSON-encoded tiptap content string to render.
26
+ */
6
27
  content: string;
7
- }
8
- declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
- declare const _default: typeof __VLS_export;
10
- export default _default;
28
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{
29
+ /**
30
+ * Error message when rendering fails
31
+ * @demo
32
+ */
33
+ error?: string;
34
+ /**
35
+ * JSON-encoded tiptap content string to render.
36
+ */
37
+ content: string;
38
+ }> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1 +1 @@
1
- .g-rich-text-toolbar[data-v-8c490655]{display:flex}.g-rich-text-toolbar button[data-v-8c490655]{background-color:unset;padding:.4rem .5rem;font-size:.875rem;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center}.g-rich-text-toolbar button.bold[data-v-8c490655]{font-weight:700}.g-rich-text-toolbar button.italic[data-v-8c490655]{font-style:italic}.g-rich-text-toolbar button.is-active[data-v-8c490655]{color:var(--g-accent-700);background-color:var(--g-surface-150)}.g-rich-text-toolbar button[data-v-8c490655]:hover{background-color:var(--g-primary-300);color:var(--g-primary-text)}.g-chat-input-wrap .tiptap{background:transparent;border:none;padding:.15em 0;font-size:15px;max-height:10em;flex:1;outline:none}.g-chat-input-wrap .tiptap p{margin:.375em 0 0}.g-chat-input-wrap .tiptap>:first-child{margin-top:0}.g-chat-input-wrap .tiptap>:last-child{margin-bottom:0}.g-chat-input-wrap .tiptap ul,.g-chat-input-wrap .tiptap ol{padding:0 1em;margin:.375em 1em 0 .4em}:is(.g-chat-input-wrap .tiptap ul,.g-chat-input-wrap .tiptap ol) li p{margin-top:0;margin-bottom:0}.g-chat-input-wrap .tiptap p.is-editor-empty:first-child:before{color:var(--g-surface-600);content:attr(data-placeholder);float:left;height:0;pointer-events:none}.bubble-menu[data-v-45a4a5f2]{box-shadow:0 1px 4px #00000080;background-color:var(--g-surface-100)}.bubble-menu [data-v-45a4a5f2] button:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.bubble-menu [data-v-45a4a5f2] button:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.g-chat-input-wrap[data-v-45a4a5f2]{position:relative;display:flex;align-items:center;background:var(--g-surface-0);border:2px solid var(--g-primary-500);border-radius:4px;padding:.5em}.g-chat-input-wrap[data-v-45a4a5f2]:has(.ProseMirror-focused){outline:2px solid var(--g-primary-500);outline-offset:2px;box-shadow:0 0 0 2px var(--g-info-200);border-color:var(--g-info-200)}.editor-content[data-v-45a4a5f2]{flex:1;min-width:0}.g-chat-send-btn[data-v-45a4a5f2]{color:var(--g-primary-500);font-size:1em;border:2px solid transparent;border-radius:4px;padding:.4em;margin:0;align-self:flex-end;cursor:pointer;display:flex;align-items:center;justify-content:center;background:transparent;flex-shrink:0}.g-chat-send-btn[data-v-45a4a5f2]:hover:not(:disabled){color:var(--g-accent-700);background-color:var(--g-surface-100)}.g-chat-send-btn[data-v-45a4a5f2]:focus:not(:disabled){background-color:var(--g-info-200);color:var(--g-primary-500)}.g-chat-send-btn[data-v-45a4a5f2]:active:not(:disabled){background-color:var(--g-primary-500);color:var(--g-surface-0)}.g-chat-send-btn[data-v-45a4a5f2]:disabled{color:var(--g-surface-300);cursor:not-allowed}.g-note-input-wrap .tiptap{background:transparent;border:none;padding:.5em;font-size:15px;min-height:12em;flex:1;outline:none}.g-note-input-wrap .tiptap p{margin:.375em 0 0}.g-note-input-wrap .tiptap>:first-child{margin-top:0}.g-note-input-wrap .tiptap>:last-child{margin-bottom:0}.g-note-input-wrap .tiptap ul,.g-note-input-wrap .tiptap ol{padding:0 1em;margin:.375em 1em 0 .4em}:is(.g-note-input-wrap .tiptap ul,.g-note-input-wrap .tiptap ol) li p{margin-top:0;margin-bottom:0}.g-note-input-wrap .tiptap p.is-editor-empty:first-child:before{color:var(--g-surface-600);content:attr(data-placeholder);float:left;height:0;pointer-events:none}.g-note-input-wrap[data-v-c2d30b96]{display:flex;flex-direction:column;background:var(--g-surface-0);border:2px solid var(--g-primary-500);border-radius:4px}.g-note-input-wrap[data-v-c2d30b96]:has(.ProseMirror-focused){outline:2px solid var(--g-primary-500);outline-offset:2px;box-shadow:0 0 0 2px var(--g-info-200);border-color:var(--g-info-200)}.toolbar[data-v-c2d30b96]{border-bottom:1px solid var(--g-surface-200);background-color:var(--g-surface-0);padding:.25rem}.toolbar [data-v-c2d30b96] button{border-radius:4px}.toolbar [data-v-c2d30b96] button:focus{outline:2px solid var(--g-primary-500);outline-offset:2px}.editor-content[data-v-c2d30b96]{flex:1;min-width:0}.g-rich-text-content p[data-v-923b2b7c]{margin:.375em 0}.g-rich-text-content [data-v-923b2b7c]>:first-child{margin-top:0}.g-rich-text-content [data-v-923b2b7c]>:last-child{margin-bottom:0}:is(.g-rich-text-content ul,.g-rich-text-content ol)[data-v-923b2b7c]{padding:0 1em;margin:.375em 1em 0 .4em}:is(.g-rich-text-content ul,.g-rich-text-content ol) li p[data-v-923b2b7c]{margin-top:0;margin-bottom:0}.g-rich-text-content-error[data-v-923b2b7c]{color:var(--g-danger-700);font-size:.875em}
1
+ .g-rich-text-toolbar[data-v-8c490655]{display:flex}.g-rich-text-toolbar button[data-v-8c490655]{background-color:unset;padding:.4rem .5rem;font-size:.875rem;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center}.g-rich-text-toolbar button.bold[data-v-8c490655]{font-weight:700}.g-rich-text-toolbar button.italic[data-v-8c490655]{font-style:italic}.g-rich-text-toolbar button.is-active[data-v-8c490655]{color:var(--g-accent-700);background-color:var(--g-surface-150)}.g-rich-text-toolbar button[data-v-8c490655]:hover{background-color:var(--g-primary-300);color:var(--g-primary-text)}.g-chat-input-wrap .tiptap{background:transparent;border:none;padding:.15em 0;font-size:15px;max-height:10em;flex:1;outline:none}.g-chat-input-wrap .tiptap p{margin:.375em 0 0}.g-chat-input-wrap .tiptap>:first-child{margin-top:0}.g-chat-input-wrap .tiptap>:last-child{margin-bottom:0}.g-chat-input-wrap .tiptap ul,.g-chat-input-wrap .tiptap ol{padding:0 1em;margin:.375em 1em 0 .4em}:is(.g-chat-input-wrap .tiptap ul,.g-chat-input-wrap .tiptap ol) li p{margin-top:0;margin-bottom:0}.g-chat-input-wrap .tiptap p.is-editor-empty:first-child:before{color:var(--g-surface-600);content:attr(data-placeholder);float:left;height:0;pointer-events:none}.bubble-menu[data-v-09bab62a]{box-shadow:0 1px 4px #00000080;background-color:var(--g-surface-100)}.bubble-menu [data-v-09bab62a] button:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px}.bubble-menu [data-v-09bab62a] button:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px}.g-chat-input-wrap[data-v-09bab62a]{position:relative;display:flex;align-items:center;background:var(--g-surface-0);border:2px solid var(--g-primary-500);border-radius:4px;padding:.5em}.g-chat-input-wrap[data-v-09bab62a]:has(.ProseMirror-focused){outline:2px solid var(--g-primary-500);outline-offset:2px;box-shadow:0 0 0 2px var(--g-info-200);border-color:var(--g-info-200)}.editor-content[data-v-09bab62a]{flex:1;min-width:0}.g-chat-send-btn[data-v-09bab62a]{color:var(--g-primary-500);font-size:1em;border:2px solid transparent;border-radius:4px;padding:.4em;margin:0;align-self:flex-end;cursor:pointer;display:flex;align-items:center;justify-content:center;background:transparent;flex-shrink:0}.g-chat-send-btn[data-v-09bab62a]:hover:not(:disabled){color:var(--g-accent-700);background-color:var(--g-surface-100)}.g-chat-send-btn[data-v-09bab62a]:focus:not(:disabled){background-color:var(--g-info-200);color:var(--g-primary-500)}.g-chat-send-btn[data-v-09bab62a]:active:not(:disabled){background-color:var(--g-primary-500);color:var(--g-surface-0)}.g-chat-send-btn[data-v-09bab62a]:disabled{color:var(--g-surface-300);cursor:not-allowed}.g-note-input-wrap .tiptap{background:transparent;border:none;padding:.5em;font-size:15px;min-height:12em;flex:1;outline:none}.g-note-input-wrap .tiptap p{margin:.375em 0 0}.g-note-input-wrap .tiptap>:first-child{margin-top:0}.g-note-input-wrap .tiptap>:last-child{margin-bottom:0}.g-note-input-wrap .tiptap ul,.g-note-input-wrap .tiptap ol{padding:0 1em;margin:.375em 1em 0 .4em}:is(.g-note-input-wrap .tiptap ul,.g-note-input-wrap .tiptap ol) li p{margin-top:0;margin-bottom:0}.g-note-input-wrap .tiptap p.is-editor-empty:first-child:before{color:var(--g-surface-600);content:attr(data-placeholder);float:left;height:0;pointer-events:none}.g-note-input-wrap[data-v-ebd8cfef]{display:flex;flex-direction:column;background:var(--g-surface-0);border:2px solid var(--g-primary-500);border-radius:4px}.g-note-input-wrap[data-v-ebd8cfef]:has(.ProseMirror-focused){outline:2px solid var(--g-primary-500);outline-offset:2px;box-shadow:0 0 0 2px var(--g-info-200);border-color:var(--g-info-200)}.toolbar[data-v-ebd8cfef]{border-bottom:1px solid var(--g-surface-200);background-color:var(--g-surface-0);padding:.25rem}.toolbar [data-v-ebd8cfef] button{border-radius:4px}.toolbar [data-v-ebd8cfef] button:focus{outline:2px solid var(--g-primary-500);outline-offset:2px}.editor-content[data-v-ebd8cfef]{flex:1;min-width:0}.g-rich-text-content p[data-v-68026e47]{margin:.375em 0}.g-rich-text-content [data-v-68026e47]>:first-child{margin-top:0}.g-rich-text-content [data-v-68026e47]>:last-child{margin-bottom:0}:is(.g-rich-text-content ul,.g-rich-text-content ol)[data-v-68026e47]{padding:0 1em;margin:.375em 1em 0 .4em}:is(.g-rich-text-content ul,.g-rich-text-content ol) li p[data-v-68026e47]{margin-top:0;margin-bottom:0}.g-rich-text-content-error[data-v-68026e47]{color:var(--g-danger-700);font-size:.875em}
@@ -1,4 +1,4 @@
1
- import { G as a, a as n, b as s, u as o } from "./main-DJc-hHqP.js";
1
+ import { G as a, a as n, b as s, u as o } from "./main-BRxFghDA.js";
2
2
  export {
3
3
  a as GChatInput,
4
4
  n as GNoteInput,
@@ -1,26 +1,26 @@
1
- import { watch as y, toRaw as N, ref as C, defineComponent as w, computed as v, openBlock as p, createElementBlock as b, unref as c, createElementVNode as d, normalizeClass as g, createCommentVNode as L, useModel as M, createBlock as O, withCtx as $, createVNode as x, mergeModels as k, onMounted as D, toValue as G, toRef as J, toDisplayString as K } from "vue";
2
- import { useEditor as P, EditorContent as V } from "@tiptap/vue-3";
3
- import { BubbleMenu as U } from "@tiptap/vue-3/menus";
1
+ import { watch as y, toRaw as E, ref as C, defineComponent as w, computed as v, openBlock as p, createElementBlock as b, unref as c, createElementVNode as d, normalizeClass as g, createCommentVNode as L, useModel as M, createBlock as N, withCtx as $, createVNode as x, mergeModels as k, onMounted as O, toValue as D, toRef as G, toDisplayString as J } from "vue";
2
+ import { useEditor as K, EditorContent as V } from "@tiptap/vue-3";
3
+ import { BubbleMenu as P } from "@tiptap/vue-3/menus";
4
4
  import I from "@tiptap/extension-document";
5
5
  import T from "@tiptap/extension-paragraph";
6
6
  import B from "@tiptap/extension-text";
7
7
  import A from "@tiptap/extension-bold";
8
- import z from "@tiptap/extension-italic";
9
- import { ListKit as R } from "@tiptap/extension-list";
10
- import { UndoRedo as _, Placeholder as F } from "@tiptap/extensions";
8
+ import _ from "@tiptap/extension-italic";
9
+ import { ListKit as z } from "@tiptap/extension-list";
10
+ import { UndoRedo as U, Placeholder as F } from "@tiptap/extensions";
11
11
  import { generateHTML as j } from "@tiptap/core";
12
- function S(e) {
13
- const { content: a, placeholder: l, label: o, onUpdate: n, editorProps: r = {} } = e, s = typeof l == "string" ? l : l.value, t = typeof o == "string" ? o : o.value, i = P({
14
- content: a.value || "",
12
+ function R(e) {
13
+ const { content: l, placeholder: s, label: o, onUpdate: n, editorProps: r = {} } = e, a = typeof s == "string" ? s : s.value, t = typeof o == "string" ? o : o.value, i = K({
14
+ content: l.value || "",
15
15
  extensions: [
16
16
  I,
17
17
  T,
18
18
  B,
19
19
  A,
20
- z,
21
- R,
22
20
  _,
23
- F.configure({ placeholder: s })
21
+ z,
22
+ U,
23
+ F.configure({ placeholder: a })
24
24
  ],
25
25
  editorProps: {
26
26
  ...r,
@@ -30,7 +30,7 @@ function S(e) {
30
30
  }
31
31
  },
32
32
  onUpdate({ editor: u }) {
33
- a.value = u.getJSON(), n && n(u);
33
+ l.value = u.getJSON(), n && n(u);
34
34
  }
35
35
  });
36
36
  y(
@@ -45,9 +45,9 @@ function S(e) {
45
45
  });
46
46
  }
47
47
  ), y(
48
- () => a.value,
48
+ () => l.value,
49
49
  (u) => {
50
- i.value && JSON.stringify(u) !== JSON.stringify(i.value.getJSON()) && i.value.commands.setContent(N(u) || "");
50
+ i.value && JSON.stringify(u) !== JSON.stringify(i.value.getJSON()) && i.value.commands.setContent(E(u) || "");
51
51
  }
52
52
  );
53
53
  function f() {
@@ -58,13 +58,13 @@ function S(e) {
58
58
  focusEditor: f
59
59
  };
60
60
  }
61
- function q(e, a) {
62
- const l = C(0);
61
+ function q(e, l) {
62
+ const s = C(0);
63
63
  function o(r) {
64
- const s = a.value;
65
- if (!s) return;
64
+ const a = l.value;
65
+ if (!a) return;
66
66
  const t = Array.from(
67
- s.querySelectorAll("button")
67
+ a.querySelectorAll("button")
68
68
  ), i = t.findIndex(
69
69
  (u) => u === document.activeElement
70
70
  );
@@ -93,13 +93,13 @@ function q(e, a) {
93
93
  default:
94
94
  return;
95
95
  }
96
- l.value = f, t[f]?.focus();
96
+ s.value = f, t[f]?.focus();
97
97
  }
98
98
  function n(r) {
99
- return r === l.value ? 0 : -1;
99
+ return r === s.value ? 0 : -1;
100
100
  }
101
101
  return {
102
- activeButtonIndex: l,
102
+ activeButtonIndex: s,
103
103
  handleToolbarKeyDown: o,
104
104
  getButtonTabIndex: n
105
105
  };
@@ -110,17 +110,17 @@ const W = ["aria-pressed", "tabindex"], Q = ["aria-pressed", "tabindex"], X = ["
110
110
  editor: {}
111
111
  },
112
112
  setup(e) {
113
- const a = e, l = C(null), o = v(() => a.editor), { handleToolbarKeyDown: n, getButtonTabIndex: r } = q(
113
+ const l = e, s = C(null), o = v(() => l.editor), { handleToolbarKeyDown: n, getButtonTabIndex: r } = q(
114
114
  o,
115
- l
115
+ s
116
116
  );
117
- return (s, t) => e.editor ? (p(), b("div", {
117
+ return (a, t) => e.editor ? (p(), b("div", {
118
118
  key: 0,
119
119
  class: "g-rich-text-toolbar",
120
120
  role: "toolbar",
121
121
  "aria-label": "Text formatting",
122
122
  ref_key: "toolbarRef",
123
- ref: l,
123
+ ref: s,
124
124
  onKeydown: t[4] || (t[4] = //@ts-ignore
125
125
  (...i) => c(n) && c(n)(...i)),
126
126
  tabindex: "-1"
@@ -213,12 +213,13 @@ const W = ["aria-pressed", "tabindex"], Q = ["aria-pressed", "tabindex"], X = ["
213
213
  ])], 10, Y)
214
214
  ], 544)) : L("", !0);
215
215
  }
216
- }), H = (e, a) => {
217
- const l = e.__vccOpts || e;
218
- for (const [o, n] of a)
219
- l[o] = n;
220
- return l;
221
- }, E = /* @__PURE__ */ H(Z, [["__scopeId", "data-v-8c490655"]]), ee = { class: "g-chat-input-wrap" }, te = ["disabled"], oe = /* @__PURE__ */ w({
216
+ }), H = (e, l) => {
217
+ const s = e.__vccOpts || e;
218
+ for (const [o, n] of l)
219
+ s[o] = n;
220
+ return s;
221
+ }, S = /* @__PURE__ */ H(Z, [["__scopeId", "data-v-8c490655"]]), ee = { class: "g-chat-input-wrap" }, te = ["disabled"], oe = {}, re = /* @__PURE__ */ w({
222
+ ...oe,
222
223
  inheritAttrs: !1,
223
224
  __name: "GChatInput",
224
225
  props: /* @__PURE__ */ k({
@@ -231,20 +232,20 @@ const W = ["aria-pressed", "tabindex"], Q = ["aria-pressed", "tabindex"], X = ["
231
232
  modelModifiers: {}
232
233
  }),
233
234
  emits: /* @__PURE__ */ k(["send"], ["update:modelValue"]),
234
- setup(e, { expose: a, emit: l }) {
235
- const o = e, n = M(e, "modelValue"), r = l, { editor: s, focusEditor: t } = S({
235
+ setup(e, { expose: l, emit: s }) {
236
+ const o = e, n = M(e, "modelValue"), r = s, { editor: a, focusEditor: t } = R({
236
237
  content: n,
237
238
  placeholder: v(() => o.placeholder),
238
239
  label: v(() => o.label),
239
240
  editorProps: {
240
241
  handleKeyDown(h, m) {
241
- if (s.value && m.key === "Enter") {
242
- if (s.value.isActive("orderedList") || s.value.isActive("bulletList"))
242
+ if (a.value && m.key === "Enter") {
243
+ if (a.value.isActive("orderedList") || a.value.isActive("bulletList"))
243
244
  return !1;
244
245
  if (m.shiftKey)
245
- s.value.commands.splitBlock();
246
+ a.value.commands.splitBlock();
246
247
  else
247
- return n.value = s.value?.getJSON(), m.preventDefault(), i(s.value?.getJSON()), !0;
248
+ return n.value = a.value?.getJSON(), m.preventDefault(), i(a.value?.getJSON()), !0;
248
249
  }
249
250
  return !1;
250
251
  },
@@ -257,26 +258,26 @@ const W = ["aria-pressed", "tabindex"], Q = ["aria-pressed", "tabindex"], X = ["
257
258
  h && r("send", h);
258
259
  }
259
260
  function f() {
260
- i(s.value?.getJSON());
261
+ i(a.value?.getJSON());
261
262
  }
262
263
  function u() {
263
264
  t();
264
265
  }
265
- return a({ focusInput: u }), (h, m) => (p(), b("div", ee, [
266
- c(s) ? (p(), O(c(U), {
266
+ return l({ focusInput: u }), (h, m) => (p(), b("div", ee, [
267
+ c(a) ? (p(), N(c(P), {
267
268
  key: 0,
268
- editor: c(s)
269
+ editor: c(a)
269
270
  }, {
270
271
  default: $(() => [
271
- x(E, {
272
- editor: c(s),
272
+ x(S, {
273
+ editor: c(a),
273
274
  class: "bubble-menu"
274
275
  }, null, 8, ["editor"])
275
276
  ]),
276
277
  _: 1
277
278
  }, 8, ["editor"])) : L("", !0),
278
279
  x(c(V), {
279
- editor: c(s),
280
+ editor: c(a),
280
281
  class: "editor-content"
281
282
  }, null, 8, ["editor"]),
282
283
  d("button", {
@@ -300,7 +301,8 @@ const W = ["aria-pressed", "tabindex"], Q = ["aria-pressed", "tabindex"], X = ["
300
301
  ])], 8, te)
301
302
  ]));
302
303
  }
303
- }), ye = /* @__PURE__ */ H(oe, [["__scopeId", "data-v-45a4a5f2"]]), re = { class: "g-note-input-wrap" }, ne = /* @__PURE__ */ w({
304
+ }), Le = /* @__PURE__ */ H(re, [["__scopeId", "data-v-09bab62a"]]), ne = { class: "g-note-input-wrap" }, ie = {}, se = /* @__PURE__ */ w({
305
+ ...ie,
304
306
  inheritAttrs: !1,
305
307
  __name: "GNoteInput",
306
308
  props: /* @__PURE__ */ k({
@@ -311,17 +313,17 @@ const W = ["aria-pressed", "tabindex"], Q = ["aria-pressed", "tabindex"], X = ["
311
313
  modelModifiers: {}
312
314
  }),
313
315
  emits: ["update:modelValue"],
314
- setup(e, { expose: a }) {
315
- const l = e, o = M(e, "modelValue"), { editor: n, focusEditor: r } = S({
316
+ setup(e, { expose: l }) {
317
+ const s = e, o = M(e, "modelValue"), { editor: n, focusEditor: r } = R({
316
318
  content: o,
317
- placeholder: v(() => l.placeholder),
318
- label: v(() => l.label)
319
+ placeholder: v(() => s.placeholder),
320
+ label: v(() => s.label)
319
321
  });
320
- function s() {
322
+ function a() {
321
323
  r();
322
324
  }
323
- return a({ focusInput: s }), (t, i) => (p(), b("div", re, [
324
- x(E, {
325
+ return l({ focusInput: a }), (t, i) => (p(), b("div", ne, [
326
+ x(S, {
325
327
  editor: c(n),
326
328
  class: "toolbar"
327
329
  }, null, 8, ["editor"]),
@@ -331,55 +333,55 @@ const W = ["aria-pressed", "tabindex"], Q = ["aria-pressed", "tabindex"], X = ["
331
333
  }, null, 8, ["editor"])
332
334
  ]));
333
335
  }
334
- }), ke = /* @__PURE__ */ H(ne, [["__scopeId", "data-v-c2d30b96"]]), ie = [I, T, B, A, z, R];
336
+ }), Me = /* @__PURE__ */ H(se, [["__scopeId", "data-v-ebd8cfef"]]), ae = [I, T, B, A, _, z];
335
337
  function le(e) {
336
- const a = C("");
337
- D(() => {
338
+ const l = C("");
339
+ O(() => {
338
340
  y(e, () => {
339
- const o = G(e);
340
- if (console.log("value", o), !o || o.trim() === "")
341
+ const o = D(e);
342
+ if (!o || o.trim() === "")
341
343
  return "";
342
344
  try {
343
345
  const n = JSON.parse(o);
344
- console.log("parsed", n);
345
346
  let r = j(
346
347
  n,
347
- ie
348
+ ae
348
349
  );
349
- console.log("html", r), a.value = r;
350
+ l.value = r;
350
351
  } catch (n) {
351
- console.error("Failed to parse content:", o), console.error(n), a.value = null;
352
+ console.error("Failed to parse content:", o), console.error(n), l.value = null;
352
353
  }
353
354
  }, { immediate: !0 });
354
355
  });
355
- const l = v(() => a.value === null);
356
- return { rendered: a, hasError: l };
356
+ const s = v(() => l.value === null);
357
+ return { rendered: l, hasError: s };
357
358
  }
358
- const se = { class: "g-rich-text-content-wrap" }, ae = {
359
+ const ce = { class: "g-rich-text-content-wrap" }, de = {
359
360
  key: 0,
360
361
  role: "alert",
361
362
  class: "g-rich-text-content-error"
362
- }, ce = ["innerHTML"], de = /* @__PURE__ */ w({
363
+ }, ue = ["innerHTML"], fe = {}, pe = /* @__PURE__ */ w({
364
+ ...fe,
363
365
  __name: "GRichTextContent",
364
366
  props: {
365
367
  error: {},
366
368
  content: {}
367
369
  },
368
370
  setup(e) {
369
- const l = J(e, "content"), { rendered: o, hasError: n } = le(l);
370
- return (r, s) => (p(), b("div", se, [
371
- c(n) ? (p(), b("div", ae, K(e.error || "Failed to render content."), 1)) : c(o) ? (p(), b("div", {
371
+ const s = G(e, "content"), { rendered: o, hasError: n } = le(s);
372
+ return (r, a) => (p(), b("div", ce, [
373
+ c(n) ? (p(), b("div", de, J(e.error || "Failed to render content."), 1)) : c(o) ? (p(), b("div", {
372
374
  key: 1,
373
375
  class: "g-rich-text-content",
374
376
  innerHTML: c(o)
375
- }, null, 8, ce)) : L("", !0)
377
+ }, null, 8, ue)) : L("", !0)
376
378
  ]));
377
379
  }
378
- }), Ce = /* @__PURE__ */ H(de, [["__scopeId", "data-v-923b2b7c"]]);
380
+ }), Ve = /* @__PURE__ */ H(pe, [["__scopeId", "data-v-68026e47"]]);
379
381
  export {
380
- ye as G,
381
- ke as a,
382
- Ce as b,
382
+ Le as G,
383
+ Me as a,
384
+ Ve as b,
383
385
  le as u
384
386
  };
385
- //# sourceMappingURL=main-DJc-hHqP.js.map
387
+ //# sourceMappingURL=main-BRxFghDA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-BRxFghDA.js","sources":["../src/composables/useRichTextEditor.ts","../src/composables/useToolbarNavigation.ts","../src/components/editor/GRichTextToolbar.vue","../src/components/GChatInput.vue","../src/components/GNoteInput.vue","../src/composables/useRichTextRenderer.ts","../src/components/GRichTextContent.vue"],"sourcesContent":["import { toRaw, watch, type Ref } from \"vue\";\nimport { useEditor } from \"@tiptap/vue-3\";\nimport Document from \"@tiptap/extension-document\";\nimport Paragraph from \"@tiptap/extension-paragraph\";\nimport Text from \"@tiptap/extension-text\";\nimport Bold from \"@tiptap/extension-bold\";\nimport Italic from \"@tiptap/extension-italic\";\nimport { ListKit } from \"@tiptap/extension-list\";\nimport { UndoRedo, Placeholder } from \"@tiptap/extensions\";\n\ninterface UseRichTextEditorOptions {\n content: Ref<object | \"\" | undefined>;\n placeholder: Ref<string> | string;\n label: Ref<string> | string;\n onUpdate?: (editor: any) => void;\n editorProps?: Record<string, any>;\n}\n\nexport function useRichTextEditor(options: UseRichTextEditorOptions) {\n const { content, placeholder, label, onUpdate, editorProps = {} } = options;\n\n const placeholderValue = typeof placeholder === 'string' ? placeholder : placeholder.value;\n const labelValue = typeof label === 'string' ? label : label.value;\n\n const editor = useEditor({\n content: content.value || \"\",\n extensions: [\n Document,\n Paragraph,\n Text,\n Bold,\n Italic,\n ListKit,\n UndoRedo,\n Placeholder.configure({ placeholder: placeholderValue }),\n ],\n editorProps: {\n ...editorProps,\n attributes: {\n \"aria-label\": labelValue,\n ...editorProps.attributes,\n },\n },\n onUpdate({ editor }) {\n content.value = editor.getJSON();\n if (onUpdate) {\n onUpdate(editor);\n }\n },\n });\n\n // Watch for label changes and update editor\n watch(\n () => typeof label === 'string' ? label : label.value,\n (val) => {\n if (editor.value) {\n editor.value?.setOptions({\n editorProps: {\n attributes: {\n \"aria-label\": val,\n },\n },\n });\n }\n },\n );\n\n // Watch for content changes and update editor\n watch(\n () => content.value,\n (val) => {\n if (\n editor.value &&\n JSON.stringify(val) !== JSON.stringify(editor.value.getJSON())\n ) {\n editor.value.commands.setContent(toRaw(val) || \"\");\n }\n },\n );\n\n function focusEditor() {\n editor.value?.commands?.focus();\n }\n\n return {\n editor,\n focusEditor,\n };\n}\n","import { ref, type Ref } from \"vue\";\nimport type { Editor } from \"@tiptap/vue-3\";\n\nexport function useToolbarNavigation(editor: Ref<Editor | undefined>, toolbarRef: Ref<HTMLElement | null>) {\n const activeButtonIndex = ref(0);\n\n function handleToolbarKeyDown(event: KeyboardEvent) {\n const toolbar = toolbarRef.value;\n if (!toolbar) return;\n\n const buttons = Array.from(\n toolbar.querySelectorAll(\"button\"),\n ) as HTMLButtonElement[];\n const currentIndex = buttons.findIndex(\n (btn) => btn === document.activeElement,\n );\n\n // Handle Escape key - return focus to editor\n if (event.key === \"Escape\") {\n event.preventDefault();\n editor.value?.commands?.focus();\n return;\n }\n\n // Don't handle Tab - let it exit the toolbar naturally\n if (event.key === \"Tab\") {\n return;\n }\n\n let nextIndex = currentIndex;\n\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowDown\":\n event.preventDefault();\n nextIndex =\n currentIndex < buttons.length - 1 ? currentIndex + 1 : 0;\n break;\n case \"ArrowLeft\":\n case \"ArrowUp\":\n event.preventDefault();\n nextIndex =\n currentIndex > 0 ? currentIndex - 1 : buttons.length - 1;\n break;\n case \"Home\":\n event.preventDefault();\n nextIndex = 0;\n break;\n case \"End\":\n event.preventDefault();\n nextIndex = buttons.length - 1;\n break;\n default:\n return;\n }\n\n // Update active button index and focus\n activeButtonIndex.value = nextIndex;\n buttons[nextIndex]?.focus();\n }\n\n function getButtonTabIndex(index: number): number {\n return index === activeButtonIndex.value ? 0 : -1;\n }\n\n return {\n activeButtonIndex,\n handleToolbarKeyDown,\n getButtonTabIndex,\n };\n}\n","<script lang=\"ts\" setup>\nimport { ref, computed } from \"vue\";\nimport type { Editor } from \"@tiptap/vue-3\";\nimport { useToolbarNavigation } from \"../../composables/useToolbarNavigation.ts\";\n\ninterface Props {\n editor: Editor | undefined;\n}\n\nconst props = defineProps<Props>();\n\nconst toolbarRef = ref<HTMLElement | null>(null);\nconst editorRef = computed(() => props.editor);\nconst { handleToolbarKeyDown, getButtonTabIndex } = useToolbarNavigation(\n editorRef,\n toolbarRef,\n);\n</script>\n\n<template>\n <div\n v-if=\"editor\"\n class=\"g-rich-text-toolbar\"\n role=\"toolbar\"\n aria-label=\"Text formatting\"\n ref=\"toolbarRef\"\n @keydown=\"handleToolbarKeyDown\"\n tabindex=\"-1\"\n >\n <button\n @click=\"editor.chain().focus().toggleBold().run()\"\n :class=\"{\n bold: true,\n 'is-active': editor.isActive('bold'),\n }\"\n :aria-pressed=\"editor.isActive('bold')\"\n title=\"Bold\"\n aria-label=\"Bold\"\n type=\"button\"\n :tabindex=\"getButtonTabIndex(0)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M0 64C0 46.3 14.3 32 32 32H80 96 224c70.7 0 128 57.3 128 128c0 31.3-11.3 60.1-30 82.3c37.1 22.4 62 63.1 62 109.7c0 70.7-57.3 128-128 128H96 80 32c-17.7 0-32-14.3-32-32s14.3-32 32-32H48V256 96H32C14.3 96 0 81.7 0 64zM224 224c35.3 0 64-28.7 64-64s-28.7-64-64-64H112V224H224zM112 288V416H256c35.3 0 64-28.7 64-64s-28.7-64-64-64H224 112z\"\n />\n </svg>\n </button>\n <button\n @click=\"editor.chain().focus().toggleItalic().run()\"\n :class=\"{\n italic: true,\n 'is-active': editor.isActive('italic'),\n }\"\n :aria-pressed=\"editor.isActive('italic')\"\n title=\"Italic\"\n aria-label=\"Italic\"\n type=\"button\"\n :tabindex=\"getButtonTabIndex(1)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M128 64c0-17.7 14.3-32 32-32H352c17.7 0 32 14.3 32 32s-14.3 32-32 32H293.3L160 416h64c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H90.7L224 96H160c-17.7 0-32-14.3-32-32z\"\n />\n </svg>\n </button>\n <button\n @click=\"editor.chain().focus().toggleOrderedList().run()\"\n :class=\"{ 'is-active': editor.isActive('orderedList') }\"\n :aria-pressed=\"editor.isActive('orderedList')\"\n title=\"Ordered List\"\n aria-label=\"Ordered List\"\n type=\"button\"\n :tabindex=\"getButtonTabIndex(2)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M24 56c0-13.3 10.7-24 24-24H80c13.3 0 24 10.7 24 24V176h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H48c-13.3 0-24-10.7-24-24s10.7-24 24-24H64V80H48C34.7 80 24 69.3 24 56zM86.7 341.2c-6.5-7.4-18.3-6.9-24 1.2L51.5 357.9c-7.7 10.8-22.7 13.3-33.5 5.6s-13.3-22.7-5.6-33.5l11.1-15.6c23.7-33.2 72.3-35.6 99.2-4.9c21.3 24.4 20.8 60.9-1.1 84.7L86.8 432H120c13.3 0 24 10.7 24 24s-10.7 24-24 24H48c-9.5 0-18.2-5.6-22-14.4s-2.1-18.9 4.3-25.9l72-78c5.3-5.8 5.4-14.6 .3-20.5zM224 64H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32zm0 160H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32zm0 160H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32z\"\n />\n </svg>\n </button>\n <button\n @click=\"editor.chain().focus().toggleBulletList().run()\"\n :class=\"{ 'is-active': editor.isActive('bulletList') }\"\n :aria-pressed=\"editor.isActive('bulletList')\"\n title=\"Unordered List\"\n aria-label=\"Unordered List\"\n type=\"button\"\n :tabindex=\"getButtonTabIndex(3)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M40 48C26.7 48 16 58.7 16 72v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V72c0-13.3-10.7-24-24-24H40zM192 64c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zM16 232v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V232c0-13.3-10.7-24-24-24H40c-13.3 0-24 10.7-24 24zM40 368c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V392c0-13.3-10.7-24-24-24H40z\"\n />\n </svg>\n </button>\n </div>\n</template>\n\n<style scoped>\n.g-rich-text-toolbar {\n display: flex;\n\n button {\n background-color: unset;\n padding: 0.4rem 0.5rem;\n font-size: 0.875rem;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &.bold {\n font-weight: 700;\n }\n &.italic {\n font-style: italic;\n }\n\n &.is-active {\n color: var(--g-accent-700);\n background-color: var(--g-surface-150);\n }\n\n &:hover {\n background-color: var(--g-primary-300);\n color: var(--g-primary-text);\n }\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * The GChatInput component provides a rich text editing experience using Tiptap. It supports:\n *\n * - **Bold** and *italic* text formatting\n * - Bullet and numbered lists\n * - Bubble menu for formatting (appears when text is selected)\n * - Press <kbd>Enter</kbd> to send, <kbd>Shift+Enter</kbd> for new line\n * - Undo/redo support\n *\n * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed } from \"vue\";\nimport { EditorContent } from \"@tiptap/vue-3\";\nimport { BubbleMenu } from \"@tiptap/vue-3/menus\";\nimport { useRichTextEditor } from \"../composables/useRichTextEditor\";\nimport GRichTextToolbar from \"./editor/GRichTextToolbar.vue\";\n\ndefineOptions({ inheritAttrs: false });\n\ntype Props = {\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Disabled\n * @demo\n */\n disabled?: boolean;\n /**\n * Maximum number of rows\n * @demo\n */\n maxRows?: number;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: \"Type a comment\",\n label: \"Comment input\",\n disabled: false,\n maxRows: 5,\n});\nconst model = defineModel<object | \"\">();\nconst emit = defineEmits<{ send: [content: object] }>();\n\nconst { editor, focusEditor } = useRichTextEditor({\n content: model as any,\n placeholder: computed(() => props.placeholder),\n label: computed(() => props.label),\n editorProps: {\n handleKeyDown(view: any, event: any) {\n if (editor.value && event.key === \"Enter\") {\n if (\n editor.value.isActive(\"orderedList\") ||\n editor.value.isActive(\"bulletList\")\n ) {\n return false;\n }\n if (!event.shiftKey) {\n model.value = editor.value?.getJSON();\n event.preventDefault();\n onSend(editor.value?.getJSON());\n return true;\n } else {\n editor.value.commands.splitBlock();\n }\n }\n return false;\n },\n attributes: {\n \"aria-keyshortcuts\": \"Shift+Enter\",\n },\n },\n});\n\nfunction onSend(content: any) {\n if (content) {\n emit(\"send\", content);\n }\n}\n\nfunction clickSend() {\n onSend(editor.value?.getJSON());\n}\n\nfunction focusInput() {\n focusEditor();\n}\n\ndefineExpose({ focusInput });\n</script>\n\n<template>\n <div class=\"g-chat-input-wrap\">\n <BubbleMenu :editor=\"editor\" v-if=\"editor\">\n <GRichTextToolbar :editor=\"editor\" class=\"bubble-menu\" />\n </BubbleMenu>\n <EditorContent :editor=\"editor\" class=\"editor-content\" />\n <button\n class=\"g-chat-send-btn\"\n :disabled=\"\n props.disabled ||\n !model ||\n (model && Object.keys(model).length === 0)\n \"\n @click=\"clickSend\"\n title=\"Send\"\n aria-label=\"Send\"\n type=\"button\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480V396.4c0-4 1.5-7.8 4.2-10.7L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z\"\n />\n </svg>\n </button>\n </div>\n</template>\n\n<style>\n.g-chat-input-wrap {\n .tiptap {\n background: transparent;\n border: none;\n padding: 0.15em 0;\n font-size: 15px;\n max-height: 10em;\n flex: 1;\n outline: none;\n\n p {\n margin: 0.375em 0 0;\n }\n\n > :first-child {\n margin-top: 0;\n }\n > :last-child {\n margin-bottom: 0;\n }\n\n ul,\n ol {\n padding: 0 1em;\n margin: 0.375em 1em 0 0.4em;\n\n li p {\n margin-top: 0;\n margin-bottom: 0;\n }\n }\n p.is-editor-empty:first-child::before {\n color: var(--g-surface-600);\n content: attr(data-placeholder);\n float: left;\n height: 0;\n pointer-events: none;\n }\n }\n}\n</style>\n\n<style scoped>\n.bubble-menu {\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);\n background-color: var(--g-surface-100);\n\n :deep(button) {\n &:first-child {\n border-top-left-radius: 4px;\n border-bottom-left-radius: 4px;\n }\n &:last-child {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 4px;\n }\n }\n}\n\n.g-chat-input-wrap {\n position: relative;\n display: flex;\n align-items: center;\n background: var(--g-surface-0);\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n padding: 0.5em;\n\n &:has(.ProseMirror-focused) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n border-color: var(--g-info-200);\n }\n}\n\n.editor-content {\n flex: 1;\n min-width: 0;\n}\n\n.g-chat-send-btn {\n color: var(--g-primary-500);\n font-size: 1em;\n border: 2px solid transparent;\n border-radius: 4px;\n padding: 0.4em;\n margin: 0;\n align-self: flex-end;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n flex-shrink: 0;\n\n &:hover:not(:disabled) {\n color: var(--g-accent-700);\n background-color: var(--g-surface-100);\n }\n\n &:focus:not(:disabled) {\n background-color: var(--g-info-200);\n color: var(--g-primary-500);\n }\n\n &:active:not(:disabled) {\n background-color: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n\n &:disabled {\n color: var(--g-surface-300);\n cursor: not-allowed;\n }\n}\n</style>\n","<script lang=\"ts\">\n/**\n * The GNoteInput component provides a rich text editing experience using Tiptap for writing notes. It supports:\n *\n * - **Bold** and *italic* text formatting\n * - Bullet and numbered lists\n * - Always visible toolbar for formatting\n * - Undo/redo support\n *\n * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.\n */\nexport default {};\n</script>\n\n<script lang=\"ts\" setup>\nimport { computed } from \"vue\";\nimport { EditorContent } from \"@tiptap/vue-3\";\nimport { useRichTextEditor } from \"../composables/useRichTextEditor\";\nimport GRichTextToolbar from \"./editor/GRichTextToolbar.vue\";\n\ndefineOptions({ inheritAttrs: false });\n\ntype Props = {\n /**\n * Placeholder text\n * @demo\n */\n placeholder?: string;\n /**\n * Accessible label\n * @demo\n */\n label?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: \"Write a note...\",\n label: \"Note input\",\n});\nconst model = defineModel<object | \"\">();\n\nconst { editor, focusEditor } = useRichTextEditor({\n content: model as any,\n placeholder: computed(() => props.placeholder),\n label: computed(() => props.label),\n});\n\nfunction focusInput() {\n focusEditor();\n}\n\ndefineExpose({ focusInput });\n</script>\n\n<template>\n <div class=\"g-note-input-wrap\">\n <GRichTextToolbar :editor=\"editor\" class=\"toolbar\" />\n <EditorContent :editor=\"editor\" class=\"editor-content\" />\n </div>\n</template>\n\n<style>\n.g-note-input-wrap {\n .tiptap {\n background: transparent;\n border: none;\n padding: 0.5em;\n font-size: 15px;\n min-height: 12em;\n flex: 1;\n outline: none;\n\n p {\n margin: 0.375em 0 0;\n }\n\n > :first-child {\n margin-top: 0;\n }\n > :last-child {\n margin-bottom: 0;\n }\n\n ul,\n ol {\n padding: 0 1em;\n margin: 0.375em 1em 0 0.4em;\n\n li p {\n margin-top: 0;\n margin-bottom: 0;\n }\n }\n p.is-editor-empty:first-child::before {\n color: var(--g-surface-600);\n content: attr(data-placeholder);\n float: left;\n height: 0;\n pointer-events: none;\n }\n }\n}\n</style>\n\n<style scoped>\n.g-note-input-wrap {\n display: flex;\n flex-direction: column;\n background: var(--g-surface-0);\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n\n &:has(.ProseMirror-focused) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n border-color: var(--g-info-200);\n }\n}\n\n.toolbar {\n border-bottom: 1px solid var(--g-surface-200);\n background-color: var(--g-surface-0);\n padding: 0.25rem;\n\n :deep(button) {\n border-radius: 4px;\n\n &:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n }\n }\n}\n\n.editor-content {\n flex: 1;\n min-width: 0;\n}\n</style>\n","import { computed, onMounted, ref, type Ref, toValue, watch } from \"vue\";\nimport Document from \"@tiptap/extension-document\";\nimport Paragraph from \"@tiptap/extension-paragraph\";\nimport Text from \"@tiptap/extension-text\";\nimport Bold from \"@tiptap/extension-bold\";\nimport Italic from \"@tiptap/extension-italic\";\nimport { ListKit } from \"@tiptap/extension-list\";\nimport { generateHTML } from \"@tiptap/core\";\n\nconst extensions = [Document, Paragraph, Text, Bold, Italic, ListKit];\n\n/**\n * Composable for rendering a JSON string of tiptap content to HTML.\n * Supports all extensions used by GChatInput and GNoteInput.\n *\n * @param content - A reactive ref or plain string containing JSON-encoded tiptap content\n * @returns `rendered` — rendered HTML string, empty string for empty content, or `null` when rendering fails;\n * `hasError` — `true` when the content cannot be parsed or rendered\n */\nexport function useRichTextRenderer(content: Ref<string>) {\n const rendered = ref<string | null>(\"\");\n\n onMounted(() => {\n watch(content, () => {\n const value = toValue(content);\n if (!value || value.trim() === \"\") {\n return \"\";\n }\n\n try {\n const parsed = JSON.parse(value);\n let html = generateHTML(\n parsed,\n extensions\n );\n rendered.value = html;\n } catch (error) {\n console.error(\"Failed to parse content:\", value);\n console.error(error);\n rendered.value = null;\n }\n }, {immediate: true});\n });\n\n const hasError = computed(() => rendered.value === null);\n\n return { rendered, hasError };\n}\n","<script lang=\"ts\">\n/**\n * Renders a JSON string of tiptap content as HTML.\n * Supports all formatting produced by GChatInput and GNoteInput:\n * bold, italic, ordered lists, and bullet lists.\n *\n * - Empty content is handled gracefully (renders nothing).\n * - Displays an error message when the content cannot be parsed or rendered.\n *\n * The rendering only happens in the client when used with Nuxt.js.\n *\n * **Security note**: rendered HTML is produced by tiptap's `generateHTML`, which only\n * serializes recognized document nodes - it does not inject raw HTML from the JSON.\n *\n * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.\n */\nexport default {};\n</script>\n\n<script setup lang=\"ts\">\nimport { toRef } from \"vue\";\nimport { useRichTextRenderer } from \"../composables/useRichTextRenderer\";\n\ntype Props = {\n /**\n * Error message when rendering fails\n * @demo\n */\n error?: string;\n\n /**\n * JSON-encoded tiptap content string to render.\n */\n content: string;\n}\n\nconst props = defineProps<Props>();\n\nconst contentRef = toRef(props, \"content\");\n\nconst { rendered, hasError } = useRichTextRenderer(contentRef);\n\n</script>\n\n<template>\n <div class=\"g-rich-text-content-wrap\">\n <div v-if=\"hasError\" role=\"alert\" class=\"g-rich-text-content-error\">\n {{ error || 'Failed to render content.' }}\n </div>\n <div v-else-if=\"rendered\" class=\"g-rich-text-content\" v-html=\"rendered\"></div>\n </div>\n</template>\n\n<style scoped>\n.g-rich-text-content {\n p {\n margin: 0.375em 0;\n }\n\n > :first-child {\n margin-top: 0;\n }\n\n > :last-child {\n margin-bottom: 0;\n }\n\n ul,\n ol {\n padding: 0 1em;\n margin: 0.375em 1em 0 0.4em;\n\n li p {\n margin-top: 0;\n margin-bottom: 0;\n }\n }\n}\n\n.g-rich-text-content-error {\n color: var(--g-danger-700);\n font-size: 0.875em;\n}\n</style>\n"],"names":["useRichTextEditor","options","content","placeholder","label","onUpdate","editorProps","placeholderValue","labelValue","editor","useEditor","Document","Paragraph","Text","Bold","Italic","ListKit","UndoRedo","Placeholder","watch","val","toRaw","focusEditor","useToolbarNavigation","toolbarRef","activeButtonIndex","ref","handleToolbarKeyDown","event","toolbar","buttons","currentIndex","btn","nextIndex","getButtonTabIndex","index","props","__props","editorRef","computed","_createElementBlock","_cache","_unref","args","_createElementVNode","$event","_normalizeClass","__default__","model","_useModel","emit","__emit","view","onSend","clickSend","focusInput","__expose","_openBlock","_hoisted_1","_createBlock","BubbleMenu","_createVNode","GRichTextToolbar","EditorContent","extensions","useRichTextRenderer","rendered","onMounted","value","toValue","parsed","html","generateHTML","error","hasError","contentRef","toRef","_hoisted_2","_toDisplayString"],"mappings":"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"}
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
- import { G as o, a as n, b as p } from "./main-DJc-hHqP.js";
1
+ import { G as o, a as n, b as p } from "./main-BRxFghDA.js";
2
2
  import "vue";
3
3
  import "@tiptap/extension-document";
4
4
  import "@tiptap/extension-paragraph";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illinois-grad/grad-vue-rte",
3
- "version": "2.3.4",
3
+ "version": "2.4.0",
4
4
  "description": "Rich text editor components for Graduate College apps using Tiptap.",
5
5
  "keywords": [
6
6
  "vue",
@@ -1 +0,0 @@
1
- {"version":3,"file":"main-DJc-hHqP.js","sources":["../src/composables/useRichTextEditor.ts","../src/composables/useToolbarNavigation.ts","../src/components/editor/GRichTextToolbar.vue","../src/components/GChatInput.vue","../src/components/GNoteInput.vue","../src/composables/useRichTextRenderer.ts","../src/components/GRichTextContent.vue"],"sourcesContent":["import { toRaw, watch, type Ref } from \"vue\";\nimport { useEditor } from \"@tiptap/vue-3\";\nimport Document from \"@tiptap/extension-document\";\nimport Paragraph from \"@tiptap/extension-paragraph\";\nimport Text from \"@tiptap/extension-text\";\nimport Bold from \"@tiptap/extension-bold\";\nimport Italic from \"@tiptap/extension-italic\";\nimport { ListKit } from \"@tiptap/extension-list\";\nimport { UndoRedo, Placeholder } from \"@tiptap/extensions\";\n\ninterface UseRichTextEditorOptions {\n content: Ref<object | \"\" | undefined>;\n placeholder: Ref<string> | string;\n label: Ref<string> | string;\n onUpdate?: (editor: any) => void;\n editorProps?: Record<string, any>;\n}\n\nexport function useRichTextEditor(options: UseRichTextEditorOptions) {\n const { content, placeholder, label, onUpdate, editorProps = {} } = options;\n\n const placeholderValue = typeof placeholder === 'string' ? placeholder : placeholder.value;\n const labelValue = typeof label === 'string' ? label : label.value;\n\n const editor = useEditor({\n content: content.value || \"\",\n extensions: [\n Document,\n Paragraph,\n Text,\n Bold,\n Italic,\n ListKit,\n UndoRedo,\n Placeholder.configure({ placeholder: placeholderValue }),\n ],\n editorProps: {\n ...editorProps,\n attributes: {\n \"aria-label\": labelValue,\n ...editorProps.attributes,\n },\n },\n onUpdate({ editor }) {\n content.value = editor.getJSON();\n if (onUpdate) {\n onUpdate(editor);\n }\n },\n });\n\n // Watch for label changes and update editor\n watch(\n () => typeof label === 'string' ? label : label.value,\n (val) => {\n if (editor.value) {\n editor.value?.setOptions({\n editorProps: {\n attributes: {\n \"aria-label\": val,\n },\n },\n });\n }\n },\n );\n\n // Watch for content changes and update editor\n watch(\n () => content.value,\n (val) => {\n if (\n editor.value &&\n JSON.stringify(val) !== JSON.stringify(editor.value.getJSON())\n ) {\n editor.value.commands.setContent(toRaw(val) || \"\");\n }\n },\n );\n\n function focusEditor() {\n editor.value?.commands?.focus();\n }\n\n return {\n editor,\n focusEditor,\n };\n}\n","import { ref, type Ref } from \"vue\";\nimport type { Editor } from \"@tiptap/vue-3\";\n\nexport function useToolbarNavigation(editor: Ref<Editor | undefined>, toolbarRef: Ref<HTMLElement | null>) {\n const activeButtonIndex = ref(0);\n\n function handleToolbarKeyDown(event: KeyboardEvent) {\n const toolbar = toolbarRef.value;\n if (!toolbar) return;\n\n const buttons = Array.from(\n toolbar.querySelectorAll(\"button\"),\n ) as HTMLButtonElement[];\n const currentIndex = buttons.findIndex(\n (btn) => btn === document.activeElement,\n );\n\n // Handle Escape key - return focus to editor\n if (event.key === \"Escape\") {\n event.preventDefault();\n editor.value?.commands?.focus();\n return;\n }\n\n // Don't handle Tab - let it exit the toolbar naturally\n if (event.key === \"Tab\") {\n return;\n }\n\n let nextIndex = currentIndex;\n\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowDown\":\n event.preventDefault();\n nextIndex =\n currentIndex < buttons.length - 1 ? currentIndex + 1 : 0;\n break;\n case \"ArrowLeft\":\n case \"ArrowUp\":\n event.preventDefault();\n nextIndex =\n currentIndex > 0 ? currentIndex - 1 : buttons.length - 1;\n break;\n case \"Home\":\n event.preventDefault();\n nextIndex = 0;\n break;\n case \"End\":\n event.preventDefault();\n nextIndex = buttons.length - 1;\n break;\n default:\n return;\n }\n\n // Update active button index and focus\n activeButtonIndex.value = nextIndex;\n buttons[nextIndex]?.focus();\n }\n\n function getButtonTabIndex(index: number): number {\n return index === activeButtonIndex.value ? 0 : -1;\n }\n\n return {\n activeButtonIndex,\n handleToolbarKeyDown,\n getButtonTabIndex,\n };\n}\n","<script lang=\"ts\" setup>\nimport { ref, computed } from \"vue\";\nimport type { Editor } from \"@tiptap/vue-3\";\nimport { useToolbarNavigation } from \"../../composables/useToolbarNavigation.ts\";\n\ninterface Props {\n editor: Editor | undefined;\n}\n\nconst props = defineProps<Props>();\n\nconst toolbarRef = ref<HTMLElement | null>(null);\nconst editorRef = computed(() => props.editor);\nconst { handleToolbarKeyDown, getButtonTabIndex } = useToolbarNavigation(\n editorRef,\n toolbarRef,\n);\n</script>\n\n<template>\n <div\n v-if=\"editor\"\n class=\"g-rich-text-toolbar\"\n role=\"toolbar\"\n aria-label=\"Text formatting\"\n ref=\"toolbarRef\"\n @keydown=\"handleToolbarKeyDown\"\n tabindex=\"-1\"\n >\n <button\n @click=\"editor.chain().focus().toggleBold().run()\"\n :class=\"{\n bold: true,\n 'is-active': editor.isActive('bold'),\n }\"\n :aria-pressed=\"editor.isActive('bold')\"\n title=\"Bold\"\n aria-label=\"Bold\"\n type=\"button\"\n :tabindex=\"getButtonTabIndex(0)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M0 64C0 46.3 14.3 32 32 32H80 96 224c70.7 0 128 57.3 128 128c0 31.3-11.3 60.1-30 82.3c37.1 22.4 62 63.1 62 109.7c0 70.7-57.3 128-128 128H96 80 32c-17.7 0-32-14.3-32-32s14.3-32 32-32H48V256 96H32C14.3 96 0 81.7 0 64zM224 224c35.3 0 64-28.7 64-64s-28.7-64-64-64H112V224H224zM112 288V416H256c35.3 0 64-28.7 64-64s-28.7-64-64-64H224 112z\"\n />\n </svg>\n </button>\n <button\n @click=\"editor.chain().focus().toggleItalic().run()\"\n :class=\"{\n italic: true,\n 'is-active': editor.isActive('italic'),\n }\"\n :aria-pressed=\"editor.isActive('italic')\"\n title=\"Italic\"\n aria-label=\"Italic\"\n type=\"button\"\n :tabindex=\"getButtonTabIndex(1)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 384 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M128 64c0-17.7 14.3-32 32-32H352c17.7 0 32 14.3 32 32s-14.3 32-32 32H293.3L160 416h64c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H90.7L224 96H160c-17.7 0-32-14.3-32-32z\"\n />\n </svg>\n </button>\n <button\n @click=\"editor.chain().focus().toggleOrderedList().run()\"\n :class=\"{ 'is-active': editor.isActive('orderedList') }\"\n :aria-pressed=\"editor.isActive('orderedList')\"\n title=\"Ordered List\"\n aria-label=\"Ordered List\"\n type=\"button\"\n :tabindex=\"getButtonTabIndex(2)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M24 56c0-13.3 10.7-24 24-24H80c13.3 0 24 10.7 24 24V176h16c13.3 0 24 10.7 24 24s-10.7 24-24 24H48c-13.3 0-24-10.7-24-24s10.7-24 24-24H64V80H48C34.7 80 24 69.3 24 56zM86.7 341.2c-6.5-7.4-18.3-6.9-24 1.2L51.5 357.9c-7.7 10.8-22.7 13.3-33.5 5.6s-13.3-22.7-5.6-33.5l11.1-15.6c23.7-33.2 72.3-35.6 99.2-4.9c21.3 24.4 20.8 60.9-1.1 84.7L86.8 432H120c13.3 0 24 10.7 24 24s-10.7 24-24 24H48c-9.5 0-18.2-5.6-22-14.4s-2.1-18.9 4.3-25.9l72-78c5.3-5.8 5.4-14.6 .3-20.5zM224 64H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32zm0 160H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32zm0 160H480c17.7 0 32 14.3 32 32s-14.3 32-32 32H224c-17.7 0-32-14.3-32-32s14.3-32 32-32z\"\n />\n </svg>\n </button>\n <button\n @click=\"editor.chain().focus().toggleBulletList().run()\"\n :class=\"{ 'is-active': editor.isActive('bulletList') }\"\n :aria-pressed=\"editor.isActive('bulletList')\"\n title=\"Unordered List\"\n aria-label=\"Unordered List\"\n type=\"button\"\n :tabindex=\"getButtonTabIndex(3)\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M40 48C26.7 48 16 58.7 16 72v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V72c0-13.3-10.7-24-24-24H40zM192 64c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zm0 160c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H192zM16 232v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V232c0-13.3-10.7-24-24-24H40c-13.3 0-24 10.7-24 24zM40 368c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24H88c13.3 0 24-10.7 24-24V392c0-13.3-10.7-24-24-24H40z\"\n />\n </svg>\n </button>\n </div>\n</template>\n\n<style scoped>\n.g-rich-text-toolbar {\n display: flex;\n\n button {\n background-color: unset;\n padding: 0.4rem 0.5rem;\n font-size: 0.875rem;\n border: none;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n\n &.bold {\n font-weight: 700;\n }\n &.italic {\n font-style: italic;\n }\n\n &.is-active {\n color: var(--g-accent-700);\n background-color: var(--g-surface-150);\n }\n\n &:hover {\n background-color: var(--g-primary-300);\n color: var(--g-primary-text);\n }\n }\n}\n</style>\n","<script lang=\"ts\" setup>\n/**\n * The GChatInput component provides a rich text editing experience using Tiptap. It supports:\n *\n * - **Bold** and *italic* text formatting\n * - Bullet and numbered lists\n * - Bubble menu for formatting (appears when text is selected)\n * - Press <kbd>Enter</kbd> to send, <kbd>Shift+Enter</kbd> for new line\n * - Undo/redo support\n *\n * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.\n */\nimport { computed } from \"vue\";\nimport { EditorContent } from \"@tiptap/vue-3\";\nimport { BubbleMenu } from \"@tiptap/vue-3/menus\";\nimport { useRichTextEditor } from \"../composables/useRichTextEditor\";\nimport GRichTextToolbar from \"./editor/GRichTextToolbar.vue\";\n\ndefineOptions({ inheritAttrs: false });\n\ninterface Props {\n /**\n * Placeholder text\n */\n placeholder?: string;\n /**\n * Disabled\n */\n disabled?: boolean;\n /**\n * Maximum number of rows\n */\n maxRows?: number;\n /**\n * Accessible label\n */\n label?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: \"Type a comment\",\n label: \"Comment input\",\n disabled: false,\n maxRows: 5,\n});\nconst model = defineModel<object | \"\">();\nconst emit = defineEmits<{ send: [content: object] }>();\n\nconst { editor, focusEditor } = useRichTextEditor({\n content: model as any,\n placeholder: computed(() => props.placeholder),\n label: computed(() => props.label),\n editorProps: {\n handleKeyDown(view: any, event: any) {\n if (editor.value && event.key === \"Enter\") {\n if (\n editor.value.isActive(\"orderedList\") ||\n editor.value.isActive(\"bulletList\")\n ) {\n return false;\n }\n if (!event.shiftKey) {\n model.value = editor.value?.getJSON();\n event.preventDefault();\n onSend(editor.value?.getJSON());\n return true;\n } else {\n editor.value.commands.splitBlock();\n }\n }\n return false;\n },\n attributes: {\n \"aria-keyshortcuts\": \"Shift+Enter\",\n },\n },\n});\n\nfunction onSend(content: any) {\n if (content) {\n emit(\"send\", content);\n }\n}\n\nfunction clickSend() {\n onSend(editor.value?.getJSON());\n}\n\nfunction focusInput() {\n focusEditor();\n}\n\ndefineExpose({ focusInput });\n</script>\n\n<template>\n <div class=\"g-chat-input-wrap\">\n <BubbleMenu :editor=\"editor\" v-if=\"editor\">\n <GRichTextToolbar :editor=\"editor\" class=\"bubble-menu\" />\n </BubbleMenu>\n <EditorContent :editor=\"editor\" class=\"editor-content\" />\n <button\n class=\"g-chat-send-btn\"\n :disabled=\"\n props.disabled ||\n !model ||\n (model && Object.keys(model).length === 0)\n \"\n @click=\"clickSend\"\n title=\"Send\"\n aria-label=\"Send\"\n type=\"button\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 512 512\"\n width=\"16\"\n height=\"16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480V396.4c0-4 1.5-7.8 4.2-10.7L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z\"\n />\n </svg>\n </button>\n </div>\n</template>\n\n<style>\n.g-chat-input-wrap {\n .tiptap {\n background: transparent;\n border: none;\n padding: 0.15em 0;\n font-size: 15px;\n max-height: 10em;\n flex: 1;\n outline: none;\n\n p {\n margin: 0.375em 0 0;\n }\n\n > :first-child {\n margin-top: 0;\n }\n > :last-child {\n margin-bottom: 0;\n }\n\n ul,\n ol {\n padding: 0 1em;\n margin: 0.375em 1em 0 0.4em;\n\n li p {\n margin-top: 0;\n margin-bottom: 0;\n }\n }\n p.is-editor-empty:first-child::before {\n color: var(--g-surface-600);\n content: attr(data-placeholder);\n float: left;\n height: 0;\n pointer-events: none;\n }\n }\n}\n</style>\n\n<style scoped>\n.bubble-menu {\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);\n background-color: var(--g-surface-100);\n\n :deep(button) {\n &:first-child {\n border-top-left-radius: 4px;\n border-bottom-left-radius: 4px;\n }\n &:last-child {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 4px;\n }\n }\n}\n\n.g-chat-input-wrap {\n position: relative;\n display: flex;\n align-items: center;\n background: var(--g-surface-0);\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n padding: 0.5em;\n\n &:has(.ProseMirror-focused) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n border-color: var(--g-info-200);\n }\n}\n\n.editor-content {\n flex: 1;\n min-width: 0;\n}\n\n.g-chat-send-btn {\n color: var(--g-primary-500);\n font-size: 1em;\n border: 2px solid transparent;\n border-radius: 4px;\n padding: 0.4em;\n margin: 0;\n align-self: flex-end;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n flex-shrink: 0;\n\n &:hover:not(:disabled) {\n color: var(--g-accent-700);\n background-color: var(--g-surface-100);\n }\n\n &:focus:not(:disabled) {\n background-color: var(--g-info-200);\n color: var(--g-primary-500);\n }\n\n &:active:not(:disabled) {\n background-color: var(--g-primary-500);\n color: var(--g-surface-0);\n }\n\n &:disabled {\n color: var(--g-surface-300);\n cursor: not-allowed;\n }\n}\n</style>\n","<script lang=\"ts\" setup>\n/**\n * The GNoteInput component provides a rich text editing experience using Tiptap for writing notes. It supports:\n *\n * - **Bold** and *italic* text formatting\n * - Bullet and numbered lists\n * - Always visible toolbar for formatting\n * - Undo/redo support\n *\n * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.\n */\nimport { computed } from \"vue\";\nimport { EditorContent } from \"@tiptap/vue-3\";\nimport { useRichTextEditor } from \"../composables/useRichTextEditor\";\nimport GRichTextToolbar from \"./editor/GRichTextToolbar.vue\";\n\ndefineOptions({ inheritAttrs: false });\n\ninterface Props {\n /**\n * Placeholder text\n */\n placeholder?: string;\n /**\n * Accessible label\n */\n label?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: \"Write a note...\",\n label: \"Note input\",\n});\nconst model = defineModel<object | \"\">();\n\nconst { editor, focusEditor } = useRichTextEditor({\n content: model as any,\n placeholder: computed(() => props.placeholder),\n label: computed(() => props.label),\n});\n\nfunction focusInput() {\n focusEditor();\n}\n\ndefineExpose({ focusInput });\n</script>\n\n<template>\n <div class=\"g-note-input-wrap\">\n <GRichTextToolbar :editor=\"editor\" class=\"toolbar\" />\n <EditorContent :editor=\"editor\" class=\"editor-content\" />\n </div>\n</template>\n\n<style>\n.g-note-input-wrap {\n .tiptap {\n background: transparent;\n border: none;\n padding: 0.5em;\n font-size: 15px;\n min-height: 12em;\n flex: 1;\n outline: none;\n\n p {\n margin: 0.375em 0 0;\n }\n\n > :first-child {\n margin-top: 0;\n }\n > :last-child {\n margin-bottom: 0;\n }\n\n ul,\n ol {\n padding: 0 1em;\n margin: 0.375em 1em 0 0.4em;\n\n li p {\n margin-top: 0;\n margin-bottom: 0;\n }\n }\n p.is-editor-empty:first-child::before {\n color: var(--g-surface-600);\n content: attr(data-placeholder);\n float: left;\n height: 0;\n pointer-events: none;\n }\n }\n}\n</style>\n\n<style scoped>\n.g-note-input-wrap {\n display: flex;\n flex-direction: column;\n background: var(--g-surface-0);\n border: 2px solid var(--g-primary-500);\n border-radius: 4px;\n\n &:has(.ProseMirror-focused) {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px var(--g-info-200);\n border-color: var(--g-info-200);\n }\n}\n\n.toolbar {\n border-bottom: 1px solid var(--g-surface-200);\n background-color: var(--g-surface-0);\n padding: 0.25rem;\n\n :deep(button) {\n border-radius: 4px;\n\n &:focus {\n outline: 2px solid var(--g-primary-500);\n outline-offset: 2px;\n }\n }\n}\n\n.editor-content {\n flex: 1;\n min-width: 0;\n}\n</style>\n","import { computed, onMounted, ref, type Ref, toValue, watch } from \"vue\";\nimport Document from \"@tiptap/extension-document\";\nimport Paragraph from \"@tiptap/extension-paragraph\";\nimport Text from \"@tiptap/extension-text\";\nimport Bold from \"@tiptap/extension-bold\";\nimport Italic from \"@tiptap/extension-italic\";\nimport { ListKit } from \"@tiptap/extension-list\";\nimport { generateHTML } from \"@tiptap/core\";\n\nconst extensions = [Document, Paragraph, Text, Bold, Italic, ListKit];\n\n/**\n * Composable for rendering a JSON string of tiptap content to HTML.\n * Supports all extensions used by GChatInput and GNoteInput.\n *\n * @param content - A reactive ref or plain string containing JSON-encoded tiptap content\n * @returns `rendered` — rendered HTML string, empty string for empty content, or `null` when rendering fails;\n * `hasError` — `true` when the content cannot be parsed or rendered\n */\nexport function useRichTextRenderer(content: Ref<string>) {\n const rendered = ref<string | null>(\"\");\n\n onMounted(() => {\n watch(content, () => {\n const value = toValue(content);\n console.log(\"value\", value);\n\n if (!value || value.trim() === \"\") {\n return \"\";\n }\n\n\n try {\n const parsed = JSON.parse(value);\n console.log(\"parsed\", parsed);\n let html = generateHTML(\n parsed,\n extensions\n );\n console.log(\"html\", html);\n rendered.value = html;\n } catch (error) {\n console.error(\"Failed to parse content:\", value);\n console.error(error);\n rendered.value = null;\n }\n }, {immediate: true});\n });\n\n const hasError = computed(() => rendered.value === null);\n\n return { rendered, hasError };\n}\n","<script setup lang=\"ts\">\n/**\n * Renders a JSON string of tiptap content as HTML.\n * Supports all formatting produced by GChatInput and GNoteInput:\n * bold, italic, ordered lists, and bullet lists.\n *\n * - Empty content is handled gracefully (renders nothing).\n * - Displays an error message when the content cannot be parsed or rendered.\n *\n * The rendering only happens in the client when used with Nuxt.js.\n *\n * **Security note**: rendered HTML is produced by tiptap's `generateHTML`, which only\n * serializes recognized document nodes - it does not inject raw HTML from the JSON.\n *\n * **Note**: This component is part of the `@illinois-grad/grad-vue-rte` package, which includes Tiptap dependencies.\n */\nimport { toRef } from \"vue\";\nimport { useRichTextRenderer } from \"../composables/useRichTextRenderer\";\n\ninterface Props {\n /**\n * Error message when rendering fails\n */\n error?: string;\n\n // JSON-encoded tiptap content string to render.\n content: string;\n}\n\nconst props = defineProps<Props>();\n\nconst contentRef = toRef(props, \"content\");\n\nconst { rendered, hasError } = useRichTextRenderer(contentRef);\n\n</script>\n\n<template>\n <div class=\"g-rich-text-content-wrap\">\n <div v-if=\"hasError\" role=\"alert\" class=\"g-rich-text-content-error\">\n {{ error || 'Failed to render content.' }}\n </div>\n <div v-else-if=\"rendered\" class=\"g-rich-text-content\" v-html=\"rendered\"></div>\n </div>\n</template>\n\n<style scoped>\n.g-rich-text-content {\n p {\n margin: 0.375em 0;\n }\n\n > :first-child {\n margin-top: 0;\n }\n\n > :last-child {\n margin-bottom: 0;\n }\n\n ul,\n ol {\n padding: 0 1em;\n margin: 0.375em 1em 0 0.4em;\n\n li p {\n margin-top: 0;\n margin-bottom: 0;\n }\n }\n}\n\n.g-rich-text-content-error {\n color: var(--g-danger-700);\n font-size: 0.875em;\n}\n</style>\n"],"names":["useRichTextEditor","options","content","placeholder","label","onUpdate","editorProps","placeholderValue","labelValue","editor","useEditor","Document","Paragraph","Text","Bold","Italic","ListKit","UndoRedo","Placeholder","watch","val","toRaw","focusEditor","useToolbarNavigation","toolbarRef","activeButtonIndex","ref","handleToolbarKeyDown","event","toolbar","buttons","currentIndex","btn","nextIndex","getButtonTabIndex","index","props","__props","editorRef","computed","_createElementBlock","_cache","_unref","args","_createElementVNode","$event","_normalizeClass","model","_useModel","emit","__emit","view","onSend","clickSend","focusInput","__expose","_openBlock","_hoisted_1","_createBlock","BubbleMenu","_createVNode","GRichTextToolbar","EditorContent","extensions","useRichTextRenderer","rendered","onMounted","value","toValue","parsed","html","generateHTML","error","hasError","contentRef","toRef","_hoisted_2","_toDisplayString"],"mappings":"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"}