@neuronection/assistant-ui 0.21.0 → 0.23.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @neuronection/assistant-ui
2
2
 
3
+ ## 0.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`9be7c37`](https://github.com/neuronection/assistant-ui/commit/9be7c37787b25b658ff01573d13b9c540a9e6c67) Thanks [@constLiakos](https://github.com/constLiakos)! - RichTextEditor becomes hostable by full apps: `extensions` is now a **full
8
+ override** of the built-in `[StarterKit, Markdown]` set (apps declare their
9
+ complete extension set), and three composition props land — `contentClassName`
10
+ (merged onto the editable region), `onReady` (editor instance on create,
11
+ `null` on destroy — the app handle for commands and event wiring), and
12
+ `headingLevels` (one toggle per level; `labels.heading` is now
13
+ `(level) => string`, default `Heading ${level}`). History buttons now reflect
14
+ undo/redo availability and disable while there is nothing to undo/redo. Also
15
+ fixes a spurious mount-time `onValueChange`: the editable-state effect no
16
+ longer calls `setEditable` when it is already correct (tiptap v3 emits an
17
+ update event from `setEditable`, which surfaced the parsed value back to the
18
+ app on mount).
19
+
20
+ ## 0.22.0
21
+
22
+ ### Minor Changes
23
+
24
+ - [`7537028`](https://github.com/neuronection/assistant-ui/commit/75370281e944b43b295ff6a315ff4fb7112d471f) Thanks [@constLiakos](https://github.com/constLiakos)! - Combobox and ComboboxMulti gain `allowCreate` (+ optional `createLabel`):
25
+ when the search term matches no option, an `Add "term"` row is offered
26
+ (keyboard included) and picking it reports the raw trimmed term — for
27
+ free-text entry into taxonomy-backed pickers.
28
+
29
+ - [`2b7e756`](https://github.com/neuronection/assistant-ui/commit/2b7e7566529af6e43528b63908ab590c04085864) Thanks [@constLiakos](https://github.com/constLiakos)! - RichTextEditor becomes hostable by full apps: `extensions` is now a **full
30
+ override** of the built-in `[StarterKit, Markdown]` set (apps declare their
31
+ complete extension set), and three composition props land — `contentClassName`
32
+ (merged onto the editable region), `onReady` (editor instance on create,
33
+ `null` on destroy — the app handle for commands and event wiring), and
34
+ `headingLevels` (one toggle per level; `labels.heading` is now
35
+ `(level) => string`, default `Heading ${level}`). History buttons now reflect
36
+ undo/redo availability and disable while there is nothing to undo/redo.
37
+
3
38
  ## 0.21.0
4
39
 
5
40
  ### Minor Changes
package/README.md CHANGED
@@ -76,7 +76,7 @@ Visual reference: [gallery](https://neuronection.github.io/assistant-ui/).
76
76
  | [`tooltip`](./docs/components/tooltip.md) | `Tooltip` + parts, `InfoTooltip` | [stories](https://neuronection.github.io/assistant-ui/?story=overlay--tooltips) |
77
77
  | [`wizard`](./docs/components/wizard.md) | `Wizard` (steps config + `renderStep`, per-step validation gates, modal/drawer variants), `Stepper` (dots/labels) | [stories](https://neuronection.github.io/assistant-ui/?story=wizard--modal-wizard) |
78
78
  | [`input`](./docs/components/input.md) / [`textarea`](./docs/components/textarea.md) / [`search-input`](./docs/components/search-input.md) / [`expandable-search`](./docs/components/expandable-search.md) | `Input` (label/hint/error wiring) · `Textarea` · `SearchInput` · `ExpandableSearch` | [stories](https://neuronection.github.io/assistant-ui/?story=misc--inputs) |
79
- | [`rich-text-editor`](./docs/components/rich-text-editor.md) | `RichTextEditor` — controlled tiptap markdown editor: configurable toolbar groups + `toolbarExtra` slot, label/icon props, caret-preserving external sync | [stories](https://neuronection.github.io/assistant-ui/?story=rich-text-editor--default) |
79
+ | [`rich-text-editor`](./docs/components/rich-text-editor.md) | `RichTextEditor` — controlled tiptap markdown editor: full-override `extensions`, configurable toolbar groups + per-level headings + `toolbarExtra` slot, `onReady`/`contentClassName` composition props, label/icon props, caret-preserving external sync | [stories](https://neuronection.github.io/assistant-ui/?story=rich-text-editor--default) |
80
80
  | [`dictation`](./docs/components/dictation.md) / [`ai-text-transform`](./docs/components/ai-text-transform.md) | `useDictation` + `DictationButton`/`DictationStrip` (transport-injected STT) · `useAiTextTransform` (streaming transform state machine) | [stories](https://neuronection.github.io/assistant-ui/?story=dictation--dictation) |
81
81
  | [`text-diff-view`](./docs/components/text-diff-view.md) | `TextDiffView` — side-by-side diff with word-level highlights, line numbers, change navigation, folds, virtualization (+ exported `computeLineDiff`/`wordSegments`) | [stories](https://neuronection.github.io/assistant-ui/?story=text-diff-view--default) |
82
82
  | [`chip-input`](./docs/components/chip-input.md) / [`chip-list`](./docs/components/chip-list.md) | `ChipInput` (Enter/comma commit, paste-splits, Backspace-removes) · `ChipList` (variant pills, clickable, removable) | [stories](https://neuronection.github.io/assistant-ui/?story=phase2--chip-editing) |
@@ -0,0 +1,83 @@
1
+ import { cn } from './chunk-BLJWR4ZV.js';
2
+ import * as React from 'react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var SettingsShell = React.forwardRef(
6
+ function SettingsShell2({ nav, active, onNavigate, header, children, className, navClassName, navTestId }, ref) {
7
+ const HeaderIcon = header?.icon;
8
+ return /* @__PURE__ */ jsxs(
9
+ "div",
10
+ {
11
+ ref,
12
+ "data-as": "settings-shell",
13
+ className: cn("grid grid-cols-1 gap-8 lg:grid-cols-4", className),
14
+ children: [
15
+ /* @__PURE__ */ jsx(
16
+ "nav",
17
+ {
18
+ className: "lg:col-span-1",
19
+ "aria-label": "Settings sections",
20
+ "data-testid": navTestId,
21
+ children: /* @__PURE__ */ jsxs(
22
+ "div",
23
+ {
24
+ className: cn(
25
+ "space-y-1 rounded-[var(--as-radius-lg)] border border-[var(--as-border)] bg-[var(--as-surface-raised)] p-3 lg:sticky lg:top-24",
26
+ navClassName
27
+ ),
28
+ children: [
29
+ header ? /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-2.5 border-b border-[var(--as-border)] px-3 py-2", children: [
30
+ HeaderIcon ? /* @__PURE__ */ jsx(
31
+ HeaderIcon,
32
+ {
33
+ className: "size-5 shrink-0 text-[var(--as-primary)]",
34
+ "aria-hidden": true
35
+ }
36
+ ) : null,
37
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-bold text-[var(--as-fg)]", children: header.title })
38
+ ] }) : null,
39
+ nav.map(({ id, label, description, icon: Icon, trailing }) => {
40
+ const isActive = id === active;
41
+ return /* @__PURE__ */ jsxs(
42
+ "button",
43
+ {
44
+ type: "button",
45
+ "aria-current": isActive ? "page" : void 0,
46
+ onClick: () => onNavigate(id),
47
+ className: cn(
48
+ "flex w-full cursor-pointer items-start rounded-[var(--as-radius)] px-3 py-2.5 text-left text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
49
+ isActive ? "bg-[color-mix(in_srgb,var(--as-primary)_12%,transparent)] font-bold text-[var(--as-primary)]" : "font-medium text-[var(--as-fg)] hover:bg-[var(--as-muted)]"
50
+ ),
51
+ children: [
52
+ Icon ? /* @__PURE__ */ jsx(
53
+ Icon,
54
+ {
55
+ className: "mr-3 mt-0.5 size-4 shrink-0",
56
+ "aria-hidden": true
57
+ }
58
+ ) : null,
59
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col", children: [
60
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label }),
61
+ description ? /* @__PURE__ */ jsx("span", { className: "text-[11px] font-normal text-[var(--as-muted-fg)]", children: description }) : null
62
+ ] }),
63
+ trailing ? /* @__PURE__ */ jsx("span", { className: "ml-auto flex items-center self-center pl-2", children: trailing }) : null
64
+ ]
65
+ },
66
+ id
67
+ );
68
+ })
69
+ ]
70
+ }
71
+ )
72
+ }
73
+ ),
74
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 lg:col-span-3", children })
75
+ ]
76
+ }
77
+ );
78
+ }
79
+ );
80
+
81
+ export { SettingsShell };
82
+ //# sourceMappingURL=chunk-7KD4MG75.js.map
83
+ //# sourceMappingURL=chunk-7KD4MG75.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/settings-shell/SettingsShell.tsx"],"names":["SettingsShell"],"mappings":";;;;AAyBO,IAAM,aAAA,GAAsB,KAAA,CAAA,UAAA;AAAA,EACjC,SAASA,cAAAA,CACP,EAAE,GAAA,EAAK,MAAA,EAAQ,UAAA,EAAY,MAAA,EAAQ,QAAA,EAAU,SAAA,EAAW,YAAA,EAAc,SAAA,EAAU,EAChF,GAAA,EACA;AACA,IAAA,MAAM,aAAa,MAAA,EAAQ,IAAA;AAC3B,IAAA,uBACE,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAQ,gBAAA;AAAA,QACR,SAAA,EAAW,EAAA,CAAG,uCAAA,EAAyC,SAAS,CAAA;AAAA,QAEhE,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAU,eAAA;AAAA,cACV,YAAA,EAAW,mBAAA;AAAA,cACX,aAAA,EAAa,SAAA;AAAA,cAEb,QAAA,kBAAA,IAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,SAAA,EAAW,EAAA;AAAA,oBACT,gIAAA;AAAA,oBACA;AAAA,mBACF;AAAA,kBAEC,QAAA,EAAA;AAAA,oBAAA,MAAA,mBACC,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6EAAA,EACZ,QAAA,EAAA;AAAA,sBAAA,UAAA,mBACC,GAAA;AAAA,wBAAC,UAAA;AAAA,wBAAA;AAAA,0BACC,SAAA,EAAU,0CAAA;AAAA,0BACV,aAAA,EAAW;AAAA;AAAA,uBACb,GACE,IAAA;AAAA,sCACJ,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gDAAA,EACb,iBAAO,KAAA,EACV;AAAA,qBAAA,EACF,CAAA,GACE,IAAA;AAAA,oBACH,GAAA,CAAI,GAAA,CAAI,CAAC,EAAE,EAAA,EAAI,OAAO,WAAA,EAAa,IAAA,EAAM,IAAA,EAAM,QAAA,EAAS,KAAM;AAC7D,sBAAA,MAAM,WAAW,EAAA,KAAO,MAAA;AACxB,sBAAA,uBACE,IAAA;AAAA,wBAAC,QAAA;AAAA,wBAAA;AAAA,0BAEC,IAAA,EAAK,QAAA;AAAA,0BACL,cAAA,EAAc,WAAW,MAAA,GAAS,MAAA;AAAA,0BAClC,OAAA,EAAS,MAAM,UAAA,CAAW,EAAE,CAAA;AAAA,0BAC5B,SAAA,EAAW,EAAA;AAAA,4BACT,uNAAA;AAAA,4BACA,WACI,8FAAA,GACA;AAAA,2BACN;AAAA,0BAEC,QAAA,EAAA;AAAA,4BAAA,IAAA,mBACC,GAAA;AAAA,8BAAC,IAAA;AAAA,8BAAA;AAAA,gCACC,SAAA,EAAU,6BAAA;AAAA,gCACV,aAAA,EAAW;AAAA;AAAA,6BACb,GACE,IAAA;AAAA,4CACJ,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EACd,QAAA,EAAA;AAAA,8CAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,UAAA,EAAY,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,8BACjC,8BACC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mDAAA,EACb,uBACH,CAAA,GACE;AAAA,6BAAA,EACN,CAAA;AAAA,4BACC,2BACC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,4CAAA,EACb,oBACH,CAAA,GACE;AAAA;AAAA,yBAAA;AAAA,wBA7BC;AAAA,uBA8BP;AAAA,oBAEJ,CAAC;AAAA;AAAA;AAAA;AACH;AAAA,WACF;AAAA,0BACA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uBAAA,EAAyB,QAAA,EAAS;AAAA;AAAA;AAAA,KACnD;AAAA,EAEJ;AACF","file":"chunk-7KD4MG75.js","sourcesContent":["import * as React from 'react'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '../../lib/utils'\n\nexport interface SettingsNavItem {\n id: string\n label: string\n description?: string\n icon?: LucideIcon\n /** Optional trailing node (status dot, count, badge) at the row edge. */\n trailing?: React.ReactNode\n}\n\nexport interface SettingsShellProps {\n nav: SettingsNavItem[]\n active: string\n onNavigate: (id: string) => void\n header?: { icon?: LucideIcon; title: string }\n children: React.ReactNode\n className?: string\n navClassName?: string\n /** Test hook: data-testid for the `<nav>` element. */\n navTestId?: string\n}\n\nexport const SettingsShell = React.forwardRef<HTMLDivElement, SettingsShellProps>(\n function SettingsShell(\n { nav, active, onNavigate, header, children, className, navClassName, navTestId },\n ref,\n ) {\n const HeaderIcon = header?.icon\n return (\n <div\n ref={ref}\n data-as=\"settings-shell\"\n className={cn('grid grid-cols-1 gap-8 lg:grid-cols-4', className)}\n >\n <nav\n className=\"lg:col-span-1\"\n aria-label=\"Settings sections\"\n data-testid={navTestId}\n >\n <div\n className={cn(\n 'space-y-1 rounded-[var(--as-radius-lg)] border border-[var(--as-border)] bg-[var(--as-surface-raised)] p-3 lg:sticky lg:top-24',\n navClassName,\n )}\n >\n {header ? (\n <div className=\"mb-2 flex items-center gap-2.5 border-b border-[var(--as-border)] px-3 py-2\">\n {HeaderIcon ? (\n <HeaderIcon\n className=\"size-5 shrink-0 text-[var(--as-primary)]\"\n aria-hidden\n />\n ) : null}\n <span className=\"truncate text-sm font-bold text-[var(--as-fg)]\">\n {header.title}\n </span>\n </div>\n ) : null}\n {nav.map(({ id, label, description, icon: Icon, trailing }) => {\n const isActive = id === active\n return (\n <button\n key={id}\n type=\"button\"\n aria-current={isActive ? 'page' : undefined}\n onClick={() => onNavigate(id)}\n className={cn(\n 'flex w-full cursor-pointer items-start rounded-[var(--as-radius)] px-3 py-2.5 text-left text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]',\n isActive\n ? 'bg-[color-mix(in_srgb,var(--as-primary)_12%,transparent)] font-bold text-[var(--as-primary)]'\n : 'font-medium text-[var(--as-fg)] hover:bg-[var(--as-muted)]',\n )}\n >\n {Icon ? (\n <Icon\n className=\"mr-3 mt-0.5 size-4 shrink-0\"\n aria-hidden\n />\n ) : null}\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate\">{label}</span>\n {description ? (\n <span className=\"text-[11px] font-normal text-[var(--as-muted-fg)]\">\n {description}\n </span>\n ) : null}\n </span>\n {trailing ? (\n <span className=\"ml-auto flex items-center self-center pl-2\">\n {trailing}\n </span>\n ) : null}\n </button>\n )\n })}\n </div>\n </nav>\n <div className=\"min-w-0 lg:col-span-3\">{children}</div>\n </div>\n )\n },\n)\n"]}
@@ -13,13 +13,15 @@ var DEFAULT_TOOLBAR = [
13
13
  "list",
14
14
  "quote"
15
15
  ];
16
+ var DEFAULT_HEADING_LEVELS = [2];
17
+ var DEFAULT_EXTENSIONS = [StarterKit, Markdown];
16
18
  var DEFAULT_LABELS = {
17
19
  toolbar: "Formatting",
18
20
  bold: "Bold",
19
21
  italic: "Italic",
20
22
  strike: "Strikethrough",
21
23
  code: "Code",
22
- heading: "Heading",
24
+ heading: (level) => `Heading ${level}`,
23
25
  bulletList: "Bulleted list",
24
26
  orderedList: "Numbered list",
25
27
  blockquote: "Quote",
@@ -60,6 +62,9 @@ var RichTextEditor = React.forwardRef(
60
62
  toolbar = DEFAULT_TOOLBAR,
61
63
  toolbarExtra,
62
64
  extensions,
65
+ contentClassName,
66
+ onReady,
67
+ headingLevels,
63
68
  parseMarkdown,
64
69
  serializeMarkdown,
65
70
  labels,
@@ -68,9 +73,16 @@ var RichTextEditor = React.forwardRef(
68
73
  }, ref) {
69
74
  const mergedLabels = { ...DEFAULT_LABELS, ...labels };
70
75
  const mergedIcons = { ...DEFAULT_ICONS, ...icons };
76
+ const resolvedExtensions = React.useMemo(
77
+ () => extensions ?? DEFAULT_EXTENSIONS,
78
+ [extensions]
79
+ );
80
+ const levels = headingLevels ?? DEFAULT_HEADING_LEVELS;
71
81
  const lastEmittedRef = React.useRef(value);
72
82
  const onValueChangeRef = React.useRef(onValueChange);
73
83
  onValueChangeRef.current = onValueChange;
84
+ const onReadyRef = React.useRef(onReady);
85
+ onReadyRef.current = onReady;
74
86
  const hooksRef = React.useRef({ parseMarkdown, serializeMarkdown });
75
87
  hooksRef.current = { parseMarkdown, serializeMarkdown };
76
88
  const [, bump] = React.useReducer((count) => count + 1, 0);
@@ -84,17 +96,17 @@ var RichTextEditor = React.forwardRef(
84
96
  );
85
97
  const editor = useEditor(
86
98
  {
87
- extensions: React.useMemo(
88
- () => [StarterKit, Markdown, ...extensions ?? []],
89
- [extensions]
90
- ),
99
+ extensions: resolvedExtensions,
91
100
  content: toDocument(value),
92
101
  editable: !disabled,
93
102
  immediatelyRender: false,
94
103
  editorProps: {
95
104
  attributes: {
96
105
  "aria-label": ariaLabel,
97
- class: "as-rich-text-editor__content min-h-24 w-full bg-transparent px-3 py-2 text-sm text-[var(--as-fg)] outline-none"
106
+ class: cn(
107
+ "as-rich-text-editor__content min-h-24 w-full bg-transparent px-3 py-2 text-sm text-[var(--as-fg)] outline-none",
108
+ contentClassName
109
+ )
98
110
  }
99
111
  },
100
112
  onUpdate: ({ editor: current }) => {
@@ -105,6 +117,15 @@ var RichTextEditor = React.forwardRef(
105
117
  }
106
118
  );
107
119
  const isMounted = editor !== null;
120
+ React.useEffect(() => {
121
+ if (editor === null) {
122
+ return;
123
+ }
124
+ onReadyRef.current?.(editor);
125
+ return () => {
126
+ onReadyRef.current?.(null);
127
+ };
128
+ }, [editor]);
108
129
  React.useEffect(() => {
109
130
  if (!isMounted || editor === null) {
110
131
  return;
@@ -117,11 +138,16 @@ var RichTextEditor = React.forwardRef(
117
138
  editor.off("update", handler);
118
139
  };
119
140
  }, [editor, isMounted]);
141
+ const editableRef = React.useRef(!disabled);
120
142
  React.useEffect(() => {
121
143
  if (editor === null || editor.isDestroyed) {
122
144
  return;
123
145
  }
124
- editor.setEditable(!disabled);
146
+ if (editableRef.current === !disabled) {
147
+ return;
148
+ }
149
+ editableRef.current = !disabled;
150
+ editor.setEditable(!disabled, false);
125
151
  }, [editor, disabled]);
126
152
  React.useEffect(() => {
127
153
  if (editor === null || editor.isDestroyed) {
@@ -139,26 +165,47 @@ var RichTextEditor = React.forwardRef(
139
165
  lastEmittedRef.current = value;
140
166
  }, [editor, value, toApp, toDocument]);
141
167
  const groups = toolbar === false ? [] : toolbar ?? DEFAULT_TOOLBAR;
142
- const renderButton = (key, group, onClick, active) => {
168
+ const buttonClass = (active) => cn(
169
+ "rounded p-1.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--as-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50",
170
+ active && "bg-[var(--as-surface-active)] text-[var(--as-fg)]"
171
+ );
172
+ const renderButton = (key, group, onClick, active, buttonDisabled = disabled) => {
143
173
  const Icon = mergedIcons[key];
174
+ const label = mergedLabels[key];
144
175
  return /* @__PURE__ */ jsx(
145
176
  "button",
146
177
  {
147
178
  type: "button",
148
- "aria-label": mergedLabels[key],
179
+ "aria-label": label,
149
180
  "aria-pressed": active,
150
- title: mergedLabels[key],
151
- disabled,
152
- className: cn(
153
- "rounded p-1.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--as-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50",
154
- active && "bg-[var(--as-surface-active)] text-[var(--as-fg)]"
155
- ),
181
+ title: label,
182
+ disabled: buttonDisabled,
183
+ className: buttonClass(active),
156
184
  onClick,
157
185
  children: /* @__PURE__ */ jsx(Icon, { className: "size-4", "aria-hidden": true })
158
186
  },
159
187
  `${group}-${key}`
160
188
  );
161
189
  };
190
+ const historyDisabled = (can) => disabled || editor === null || editor.can()[can]() !== true;
191
+ const renderHeadingButton = (level) => {
192
+ const Icon = mergedIcons.heading;
193
+ const label = mergedLabels.heading(level);
194
+ return /* @__PURE__ */ jsx(
195
+ "button",
196
+ {
197
+ type: "button",
198
+ "aria-label": label,
199
+ "aria-pressed": editor?.isActive("heading", { level }) === true,
200
+ title: label,
201
+ disabled,
202
+ className: buttonClass(editor?.isActive("heading", { level }) === true),
203
+ onClick: () => editor?.chain().focus().toggleHeading({ level }).run(),
204
+ children: /* @__PURE__ */ jsx(Icon, { className: "size-4", "aria-hidden": true })
205
+ },
206
+ `heading-${level}`
207
+ );
208
+ };
162
209
  return /* @__PURE__ */ jsxs(
163
210
  "div",
164
211
  {
@@ -181,20 +228,17 @@ var RichTextEditor = React.forwardRef(
181
228
  "undo",
182
229
  "history",
183
230
  () => editor?.chain().focus().undo().run(),
184
- false
231
+ false,
232
+ historyDisabled("undo")
185
233
  ) : null,
186
234
  groups.includes("history") ? renderButton(
187
235
  "redo",
188
236
  "history",
189
237
  () => editor?.chain().focus().redo().run(),
190
- false
191
- ) : null,
192
- groups.includes("heading") ? renderButton(
193
- "heading",
194
- "heading",
195
- () => editor?.chain().focus().toggleHeading({ level: 2 }).run(),
196
- editor?.isActive("heading", { level: 2 }) === true
238
+ false,
239
+ historyDisabled("redo")
197
240
  ) : null,
241
+ groups.includes("heading") ? levels.map((level) => renderHeadingButton(level)) : null,
198
242
  groups.includes("format") ? renderButton(
199
243
  "bold",
200
244
  "format",
@@ -249,5 +293,5 @@ var RichTextEditor = React.forwardRef(
249
293
  );
250
294
 
251
295
  export { RichTextEditor };
252
- //# sourceMappingURL=chunk-XUDCCQRN.js.map
253
- //# sourceMappingURL=chunk-XUDCCQRN.js.map
296
+ //# sourceMappingURL=chunk-AY77TWLT.js.map
297
+ //# sourceMappingURL=chunk-AY77TWLT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/rich-text-editor/RichTextEditor.tsx"],"names":["RichTextEditor"],"mappings":"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","file":"chunk-AY77TWLT.js","sourcesContent":["import * as React from 'react'\nimport { Editor, EditorContent, useEditor, type Extensions } from '@tiptap/react'\nimport StarterKit from '@tiptap/starter-kit'\nimport {\n Bold,\n Code,\n Heading2,\n Italic,\n List,\n ListOrdered,\n Quote,\n Redo2,\n Strikethrough,\n Undo2,\n type LucideIcon,\n} from 'lucide-react'\nimport { Markdown } from 'tiptap-markdown'\n\nimport { cn } from '../../lib/utils'\n\nexport type RichTextToolbarGroup = 'history' | 'heading' | 'format' | 'list' | 'quote'\n\nexport type RichTextHeadingLevel = 1 | 2 | 3 | 4\n\nconst DEFAULT_TOOLBAR: RichTextToolbarGroup[] = [\n 'history',\n 'heading',\n 'format',\n 'list',\n 'quote',\n]\n\nconst DEFAULT_HEADING_LEVELS: RichTextHeadingLevel[] = [2]\n\nconst DEFAULT_EXTENSIONS: Extensions = [StarterKit, Markdown]\n\nexport interface RichTextEditorLabels {\n toolbar?: string\n bold?: string\n italic?: string\n strike?: string\n code?: string\n heading?: (level: number) => string\n bulletList?: string\n orderedList?: string\n blockquote?: string\n undo?: string\n redo?: string\n}\n\nexport interface RichTextEditorIcons {\n bold?: LucideIcon\n italic?: LucideIcon\n strike?: LucideIcon\n code?: LucideIcon\n heading?: LucideIcon\n bulletList?: LucideIcon\n orderedList?: LucideIcon\n blockquote?: LucideIcon\n undo?: LucideIcon\n redo?: LucideIcon\n}\n\nconst DEFAULT_LABELS: Required<RichTextEditorLabels> = {\n toolbar: 'Formatting',\n bold: 'Bold',\n italic: 'Italic',\n strike: 'Strikethrough',\n code: 'Code',\n heading: (level) => `Heading ${level}`,\n bulletList: 'Bulleted list',\n orderedList: 'Numbered list',\n blockquote: 'Quote',\n undo: 'Undo',\n redo: 'Redo',\n}\n\nconst DEFAULT_ICONS: Required<RichTextEditorIcons> = {\n bold: Bold,\n italic: Italic,\n strike: Strikethrough,\n code: Code,\n heading: Heading2,\n bulletList: List,\n orderedList: ListOrdered,\n blockquote: Quote,\n undo: Undo2,\n redo: Redo2,\n}\n\ntype FormatKey =\n | 'bold'\n | 'italic'\n | 'strike'\n | 'code'\n | 'bulletList'\n | 'orderedList'\n | 'blockquote'\n | 'undo'\n | 'redo'\n\nexport interface RichTextEditorProps {\n value: string\n onValueChange: (markdown: string) => void\n ariaLabel: string\n disabled?: boolean\n toolbar?: RichTextToolbarGroup[] | false\n /** App-owned controls at the end of the toolbar; render-prop form receives the editor. */\n toolbarExtra?: React.ReactNode | ((editor: Editor | null) => React.ReactNode)\n /** Full override of the document extension set; default is `[StarterKit, Markdown]`. Apps declare their complete set. */\n extensions?: Extensions\n /** Extra classes for the editable region (merged after the built-ins via tailwind-merge). */\n contentClassName?: string\n /** Called once when the editor instance is created and again with `null` on destroy/unmount. */\n onReady?: (editor: Editor | null) => void\n /** Heading toggles rendered for the `heading` toolbar group, one button per level. */\n headingLevels?: RichTextHeadingLevel[]\n /** Map incoming app-domain markdown before it reaches the document. */\n parseMarkdown?: (markdown: string) => string\n /** Map the serialized document before it is emitted / echo-compared. */\n serializeMarkdown?: (markdown: string) => string\n labels?: Partial<RichTextEditorLabels>\n icons?: Partial<RichTextEditorIcons>\n className?: string\n}\n\nfunction getMarkdown(editor: Editor): string {\n const storage = editor.storage as { markdown?: { getMarkdown?: () => string } }\n return storage.markdown?.getMarkdown?.() ?? ''\n}\n\nfunction applyExternalValue(editor: Editor, markdown: string): void {\n const hadFocus = editor.isFocused\n const caret = editor.state.selection.head\n editor.commands.setContent(markdown, { emitUpdate: false })\n if (hadFocus) {\n editor.commands.setTextSelection(Math.min(caret, editor.state.doc.content.size))\n editor.commands.focus()\n }\n}\n\nexport const RichTextEditor = React.forwardRef<HTMLDivElement, RichTextEditorProps>(\n function RichTextEditor(\n {\n value,\n onValueChange,\n ariaLabel,\n disabled = false,\n toolbar = DEFAULT_TOOLBAR,\n toolbarExtra,\n extensions,\n contentClassName,\n onReady,\n headingLevels,\n parseMarkdown,\n serializeMarkdown,\n labels,\n icons,\n className,\n },\n ref,\n ) {\n const mergedLabels = { ...DEFAULT_LABELS, ...labels }\n const mergedIcons = { ...DEFAULT_ICONS, ...icons }\n const resolvedExtensions = React.useMemo(\n () => extensions ?? DEFAULT_EXTENSIONS,\n [extensions],\n )\n const levels = headingLevels ?? DEFAULT_HEADING_LEVELS\n const lastEmittedRef = React.useRef<string>(value)\n const onValueChangeRef = React.useRef(onValueChange)\n onValueChangeRef.current = onValueChange\n const onReadyRef = React.useRef(onReady)\n onReadyRef.current = onReady\n const hooksRef = React.useRef({ parseMarkdown, serializeMarkdown })\n hooksRef.current = { parseMarkdown, serializeMarkdown }\n const [, bump] = React.useReducer((count: number) => count + 1, 0)\n\n const toDocument = React.useCallback(\n (markdown: string) => (parseMarkdown ? parseMarkdown(markdown) : markdown),\n [parseMarkdown],\n )\n const toApp = React.useCallback(\n (markdown: string) => (serializeMarkdown ? serializeMarkdown(markdown) : markdown),\n [serializeMarkdown],\n )\n\n const editor = useEditor(\n {\n extensions: resolvedExtensions,\n content: toDocument(value),\n editable: !disabled,\n immediatelyRender: false,\n editorProps: {\n attributes: {\n 'aria-label': ariaLabel,\n class: cn(\n 'as-rich-text-editor__content min-h-24 w-full bg-transparent px-3 py-2 text-sm text-[var(--as-fg)] outline-none',\n contentClassName,\n ),\n },\n },\n onUpdate: ({ editor: current }) => {\n const markdown = toApp(getMarkdown(current))\n lastEmittedRef.current = markdown\n onValueChangeRef.current(markdown)\n },\n },\n )\n\n const isMounted = editor !== null\n\n React.useEffect(() => {\n if (editor === null) {\n return\n }\n onReadyRef.current?.(editor)\n return () => {\n onReadyRef.current?.(null)\n }\n }, [editor])\n\n React.useEffect(() => {\n if (!isMounted || editor === null) {\n return\n }\n const handler = () => bump()\n editor.on('transaction', handler)\n editor.on('update', handler)\n return () => {\n editor.off('transaction', handler)\n editor.off('update', handler)\n }\n }, [editor, isMounted])\n\n const editableRef = React.useRef(!disabled)\n React.useEffect(() => {\n if (editor === null || editor.isDestroyed) {\n return\n }\n if (editableRef.current === !disabled) {\n return\n }\n editableRef.current = !disabled\n editor.setEditable(!disabled, false)\n }, [editor, disabled])\n\n React.useEffect(() => {\n if (editor === null || editor.isDestroyed) {\n return\n }\n if (value === lastEmittedRef.current) {\n return\n }\n const current = toApp(getMarkdown(editor))\n if (value === current) {\n lastEmittedRef.current = value\n return\n }\n applyExternalValue(editor, toDocument(value))\n lastEmittedRef.current = value\n }, [editor, value, toApp, toDocument])\n\n const groups = toolbar === false ? [] : (toolbar ?? DEFAULT_TOOLBAR)\n\n const buttonClass = (active: boolean) =>\n cn(\n 'rounded p-1.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--as-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50',\n active && 'bg-[var(--as-surface-active)] text-[var(--as-fg)]',\n )\n\n const renderButton = (\n key: FormatKey,\n group: RichTextToolbarGroup,\n onClick: () => void,\n active: boolean,\n buttonDisabled = disabled,\n ) => {\n const Icon = mergedIcons[key]\n const label = mergedLabels[key]\n return (\n <button\n key={`${group}-${key}`}\n type=\"button\"\n aria-label={label}\n aria-pressed={active}\n title={label}\n disabled={buttonDisabled}\n className={buttonClass(active)}\n onClick={onClick}\n >\n <Icon className=\"size-4\" aria-hidden />\n </button>\n )\n }\n\n const historyDisabled = (can: 'undo' | 'redo') =>\n disabled || editor === null || editor.can()[can]() !== true\n\n const renderHeadingButton = (level: RichTextHeadingLevel) => {\n const Icon = mergedIcons.heading\n const label = mergedLabels.heading(level)\n return (\n <button\n key={`heading-${level}`}\n type=\"button\"\n aria-label={label}\n aria-pressed={editor?.isActive('heading', { level }) === true}\n title={label}\n disabled={disabled}\n className={buttonClass(editor?.isActive('heading', { level }) === true)}\n onClick={() => editor?.chain().focus().toggleHeading({ level }).run()}\n >\n <Icon className=\"size-4\" aria-hidden />\n </button>\n )\n }\n\n return (\n <div\n ref={ref}\n data-as=\"rich-text-editor\"\n className={cn(\n 'w-full rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] text-[var(--as-fg)] focus-within:border-[var(--as-focus-ring)]',\n disabled && 'opacity-50',\n className,\n )}\n >\n {groups.length > 0 || toolbarExtra ? (\n <div\n role=\"toolbar\"\n aria-label={mergedLabels.toolbar}\n className=\"flex flex-wrap items-center gap-0.5 border-b border-[var(--as-border)] p-1\"\n >\n {groups.includes('history')\n ? renderButton(\n 'undo',\n 'history',\n () => editor?.chain().focus().undo().run(),\n false,\n historyDisabled('undo'),\n )\n : null}\n {groups.includes('history')\n ? renderButton(\n 'redo',\n 'history',\n () => editor?.chain().focus().redo().run(),\n false,\n historyDisabled('redo'),\n )\n : null}\n {groups.includes('heading') ? levels.map((level) => renderHeadingButton(level)) : null}\n {groups.includes('format')\n ? renderButton(\n 'bold',\n 'format',\n () => editor?.chain().focus().toggleBold().run(),\n editor?.isActive('bold') === true,\n )\n : null}\n {groups.includes('format')\n ? renderButton(\n 'italic',\n 'format',\n () => editor?.chain().focus().toggleItalic().run(),\n editor?.isActive('italic') === true,\n )\n : null}\n {groups.includes('format')\n ? renderButton(\n 'strike',\n 'format',\n () => editor?.chain().focus().toggleStrike().run(),\n editor?.isActive('strike') === true,\n )\n : null}\n {groups.includes('format')\n ? renderButton(\n 'code',\n 'format',\n () => editor?.chain().focus().toggleCode().run(),\n editor?.isActive('code') === true,\n )\n : null}\n {groups.includes('list')\n ? renderButton(\n 'bulletList',\n 'list',\n () => editor?.chain().focus().toggleBulletList().run(),\n editor?.isActive('bulletList') === true,\n )\n : null}\n {groups.includes('list')\n ? renderButton(\n 'orderedList',\n 'list',\n () => editor?.chain().focus().toggleOrderedList().run(),\n editor?.isActive('orderedList') === true,\n )\n : null}\n {groups.includes('quote')\n ? renderButton(\n 'blockquote',\n 'quote',\n () => editor?.chain().focus().toggleBlockquote().run(),\n editor?.isActive('blockquote') === true,\n )\n : null}\n {typeof toolbarExtra === 'function' ? toolbarExtra(editor) : toolbarExtra}\n </div>\n ) : null}\n <EditorContent editor={editor} />\n </div>\n )\n },\n)\n"]}
@@ -1,5 +1,5 @@
1
1
  import { CapabilityChips } from './chunk-G3DWN2DJ.js';
2
- import { ModelPicker } from './chunk-SVQK7TXU.js';
2
+ import { ModelPicker } from './chunk-S4WHBQJ2.js';
3
3
  import { beautifyId } from './chunk-B2G66OSQ.js';
4
4
  import { Modal, ModalContent, ModalHeader, ModalTitle, ModalFooter } from './chunk-Y7BKBG57.js';
5
5
  import { Button } from './chunk-GTXGTQ6F.js';
@@ -616,5 +616,5 @@ var ModelRegistry = React.forwardRef(
616
616
  );
617
617
 
618
618
  export { ModelRegistry };
619
- //# sourceMappingURL=chunk-RBEPQY2A.js.map
620
- //# sourceMappingURL=chunk-RBEPQY2A.js.map
619
+ //# sourceMappingURL=chunk-BHHDCY2B.js.map
620
+ //# sourceMappingURL=chunk-BHHDCY2B.js.map