prompt-area 0.1.0 → 0.3.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +11 -3
  3. package/dist/action-bar/index.js +1 -4
  4. package/dist/chat-prompt-layout/index.js +1 -4
  5. package/dist/chunk-23Y7B365.js +2 -0
  6. package/dist/chunk-2DBBB352.js +3 -0
  7. package/dist/chunk-6VISE4VA.js +2 -0
  8. package/dist/chunk-AUCRB7HL.js +2 -0
  9. package/dist/chunk-CRC4ST6U.js +3 -0
  10. package/dist/chunk-VULUMPYE.js +2 -0
  11. package/dist/chunk-WBAKQRYT.js +20 -0
  12. package/dist/compact-prompt-area/index.js +1 -5
  13. package/dist/helpers/index.js +7 -291
  14. package/dist/index.js +1 -9
  15. package/dist/prompt-area/index.js +1 -5
  16. package/dist/status-bar/index.js +1 -4
  17. package/package.json +24 -12
  18. package/LICENSE +0 -21
  19. package/dist/action-bar/index.js.map +0 -1
  20. package/dist/chat-prompt-layout/index.js.map +0 -1
  21. package/dist/chunk-ANZZEZP2.js +0 -38
  22. package/dist/chunk-ANZZEZP2.js.map +0 -1
  23. package/dist/chunk-BPJO4DGM.js +0 -198
  24. package/dist/chunk-BPJO4DGM.js.map +0 -1
  25. package/dist/chunk-BWVBDP7C.js +0 -38
  26. package/dist/chunk-BWVBDP7C.js.map +0 -1
  27. package/dist/chunk-E7HUXORB.js +0 -2692
  28. package/dist/chunk-E7HUXORB.js.map +0 -1
  29. package/dist/chunk-NF2LHZIE.js +0 -12
  30. package/dist/chunk-NF2LHZIE.js.map +0 -1
  31. package/dist/chunk-UBBCAMJA.js +0 -116
  32. package/dist/chunk-UBBCAMJA.js.map +0 -1
  33. package/dist/chunk-XDKRP7UE.js +0 -125
  34. package/dist/chunk-XDKRP7UE.js.map +0 -1
  35. package/dist/compact-prompt-area/index.js.map +0 -1
  36. package/dist/helpers/index.js.map +0 -1
  37. package/dist/index.js.map +0 -1
  38. package/dist/prompt-area/index.js.map +0 -1
  39. package/dist/status-bar/index.js.map +0 -1
@@ -1,116 +0,0 @@
1
- 'use client';
2
- import { segmentsToPlainText } from './chunk-E7HUXORB.js';
3
- import { useState, useRef, useMemo, useCallback } from 'react';
4
-
5
- function usePromptAreaState(options = {}) {
6
- const { initialValue = [] } = options;
7
- const [value, setValue] = useState(initialValue);
8
- const ref = useRef(null);
9
- const plainText = useMemo(() => segmentsToPlainText(value), [value]);
10
- const isEmpty = useMemo(() => {
11
- if (value.length === 0) return true;
12
- return value.every((seg) => seg.type === "text" && seg.text.trim() === "");
13
- }, [value]);
14
- const hasChips2 = useMemo(() => value.some((seg) => seg.type === "chip"), [value]);
15
- const chips = useMemo(
16
- () => value.filter((seg) => seg.type === "chip"),
17
- [value]
18
- );
19
- const bind = useMemo(() => ({ ref, value, onChange: setValue }), [value]);
20
- const clear = useCallback(() => {
21
- if (ref.current) {
22
- ref.current.clear();
23
- } else {
24
- setValue([]);
25
- }
26
- }, []);
27
- const focus = useCallback(() => ref.current?.focus(), []);
28
- const blur = useCallback(() => ref.current?.blur(), []);
29
- const insertChip = useCallback(
30
- (chip2) => ref.current?.insertChip(chip2),
31
- []
32
- );
33
- return {
34
- bind,
35
- plainText,
36
- isEmpty,
37
- hasChips: hasChips2,
38
- chips,
39
- clear,
40
- focus,
41
- blur,
42
- insertChip
43
- };
44
- }
45
-
46
- // src/prompt-area/segment-helpers.ts
47
- function text(value) {
48
- return { type: "text", text: value };
49
- }
50
- function chip(opts) {
51
- return { type: "chip", ...opts };
52
- }
53
- function isSegmentsEmpty(segments) {
54
- if (segments.length === 0) return true;
55
- return segments.every((seg) => seg.type === "text" && seg.text.trim() === "");
56
- }
57
- function hasChips(segments) {
58
- return segments.some((seg) => seg.type === "chip");
59
- }
60
- function getChips(segments) {
61
- return segments.filter((seg) => seg.type === "chip");
62
- }
63
- function getChipsByTrigger(segments, trigger) {
64
- return segments.filter(
65
- (seg) => seg.type === "chip" && seg.trigger === trigger
66
- );
67
- }
68
-
69
- // src/prompt-area/trigger-presets.ts
70
- function mentionTrigger(opts = {}) {
71
- const { char = "@", ...rest } = opts;
72
- return {
73
- char,
74
- position: "any",
75
- mode: "dropdown",
76
- chipStyle: "pill",
77
- accessibilityLabel: "mention",
78
- ...rest
79
- };
80
- }
81
- function commandTrigger(opts = {}) {
82
- const { char = "/", ...rest } = opts;
83
- return {
84
- char,
85
- position: "start",
86
- mode: "dropdown",
87
- chipStyle: "inline",
88
- accessibilityLabel: "command",
89
- ...rest
90
- };
91
- }
92
- function hashtagTrigger(opts = {}) {
93
- const { char = "#", ...rest } = opts;
94
- return {
95
- char,
96
- position: "any",
97
- mode: "dropdown",
98
- chipStyle: "pill",
99
- resolveOnSpace: true,
100
- accessibilityLabel: "tag",
101
- ...rest
102
- };
103
- }
104
- function callbackTrigger(opts) {
105
- const { char, ...rest } = opts;
106
- return {
107
- char,
108
- position: "start",
109
- mode: "callback",
110
- ...rest
111
- };
112
- }
113
-
114
- export { callbackTrigger, chip, commandTrigger, getChips, getChipsByTrigger, hasChips, hashtagTrigger, isSegmentsEmpty, mentionTrigger, text, usePromptAreaState };
115
- //# sourceMappingURL=chunk-UBBCAMJA.js.map
116
- //# sourceMappingURL=chunk-UBBCAMJA.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/prompt-area/use-prompt-area-state.ts","../src/prompt-area/segment-helpers.ts","../src/prompt-area/trigger-presets.ts"],"names":["hasChips","chip"],"mappings":"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","file":"chunk-UBBCAMJA.js","sourcesContent":["/**\n * Convenience hook that wires up all the boilerplate state for a PromptArea.\n *\n * Instead of manually managing `useState<Segment[]>`, `useRef<PromptAreaHandle>`,\n * and computing derived values, call `usePromptAreaState()` once and spread\n * `bind` into your `<PromptArea>`.\n *\n * @example\n * ```tsx\n * function ChatInput() {\n * const { bind, plainText, isEmpty, chips, clear, focus } = usePromptAreaState()\n *\n * return (\n * <PromptArea\n * {...bind}\n * onSubmit={() => {\n * sendMessage(plainText)\n * clear()\n * }}\n * />\n * )\n * }\n * ```\n */\n\n'use client'\n\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport type { Segment, ChipSegment, PromptAreaHandle } from './types'\nimport { segmentsToPlainText } from './prompt-area-engine'\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport type UsePromptAreaStateOptions = {\n /** Initial segment value. Defaults to `[]`. */\n initialValue?: Segment[]\n}\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport type PromptAreaBind = {\n /** Ref to attach to PromptArea — gives access to imperative methods. */\n ref: React.RefObject<PromptAreaHandle | null>\n /** Current segment array — pass as `value` prop. */\n value: Segment[]\n /** Setter — pass as `onChange` prop. */\n onChange: (segments: Segment[]) => void\n}\n\nexport type PromptAreaState = {\n /** Props to spread directly onto `<PromptArea {...bind} />`. Contains ref, value, and onChange. */\n bind: PromptAreaBind\n /** Derived plain text representation of the current value. */\n plainText: string\n /** `true` when the value is empty or whitespace-only. */\n isEmpty: boolean\n /** `true` when the value contains at least one chip. */\n hasChips: boolean\n /** All chip segments in the current value. */\n chips: ChipSegment[]\n /** Clear all content (both state and the editor DOM). */\n clear: () => void\n /** Focus the editor. */\n focus: () => void\n /** Blur the editor. */\n blur: () => void\n /** Insert a chip at the current cursor position. */\n insertChip: (chip: Omit<ChipSegment, 'type'>) => void\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function usePromptAreaState(options: UsePromptAreaStateOptions = {}): PromptAreaState {\n const { initialValue = [] } = options\n\n const [value, setValue] = useState<Segment[]>(initialValue)\n const ref = useRef<PromptAreaHandle>(null)\n\n // Derived\n const plainText = useMemo(() => segmentsToPlainText(value), [value])\n\n const isEmpty = useMemo(() => {\n if (value.length === 0) return true\n return value.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n }, [value])\n\n const hasChips = useMemo(() => value.some((seg) => seg.type === 'chip'), [value])\n\n const chips = useMemo(\n () => value.filter((seg): seg is ChipSegment => seg.type === 'chip'),\n [value],\n )\n\n // Bind object — safe to spread onto <PromptArea>\n const bind = useMemo<PromptAreaBind>(() => ({ ref, value, onChange: setValue }), [value])\n\n // Actions that proxy to the imperative handle\n const clear = useCallback(() => {\n if (ref.current) {\n ref.current.clear()\n } else {\n setValue([])\n }\n }, [])\n\n const focus = useCallback(() => ref.current?.focus(), [])\n const blur = useCallback(() => ref.current?.blur(), [])\n\n const insertChip = useCallback(\n (chip: Omit<ChipSegment, 'type'>) => ref.current?.insertChip(chip),\n [],\n )\n\n return {\n bind,\n plainText,\n isEmpty,\n hasChips,\n chips,\n clear,\n focus,\n blur,\n insertChip,\n }\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","/**\n * Pre-built trigger configuration factories for common AI chat patterns.\n *\n * Each factory returns a full `TriggerConfig` with sensible defaults.\n * Pass only what you need to override.\n *\n * @example\n * ```tsx\n * <PromptArea\n * triggers={[\n * mentionTrigger({ onSearch: searchUsers }),\n * commandTrigger({ onSearch: searchCommands }),\n * hashtagTrigger(),\n * ]}\n * />\n * ```\n */\n\nimport type { TriggerConfig } from './types'\n\n// ---------------------------------------------------------------------------\n// Shared option type — everything in TriggerConfig except the keys each\n// factory sets by default.\n// ---------------------------------------------------------------------------\n\ntype TriggerPresetOptions = Omit<Partial<TriggerConfig>, 'char' | 'position' | 'mode'>\n\n// ---------------------------------------------------------------------------\n// @mention — dropdown at any position\n// ---------------------------------------------------------------------------\n\nexport type MentionTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'@'`. */\n char?: string\n}\n\n/**\n * Creates a **mention** trigger (`@`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * accessible label `\"mention\"`.\n */\nexport function mentionTrigger(opts: MentionTriggerOptions = {}): TriggerConfig {\n const { char = '@', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n accessibilityLabel: 'mention',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// /command — dropdown only at line start\n// ---------------------------------------------------------------------------\n\nexport type CommandTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'/'`. */\n char?: string\n}\n\n/**\n * Creates a **command** trigger (`/`).\n *\n * Defaults: `position: 'start'`, `mode: 'dropdown'`, `chipStyle: 'inline'`,\n * accessible label `\"command\"`.\n */\nexport function commandTrigger(opts: CommandTriggerOptions = {}): TriggerConfig {\n const { char = '/', ...rest } = opts\n return {\n char,\n position: 'start',\n mode: 'dropdown',\n chipStyle: 'inline',\n accessibilityLabel: 'command',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// #hashtag — dropdown at any position, auto-resolve on space\n// ---------------------------------------------------------------------------\n\nexport type HashtagTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'#'`. */\n char?: string\n}\n\n/**\n * Creates a **hashtag / tag** trigger (`#`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * `resolveOnSpace: true`, accessible label `\"tag\"`.\n */\nexport function hashtagTrigger(opts: HashtagTriggerOptions = {}): TriggerConfig {\n const { char = '#', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n resolveOnSpace: true,\n accessibilityLabel: 'tag',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Generic callback trigger (e.g., for file pickers, model selectors)\n// ---------------------------------------------------------------------------\n\nexport type CallbackTriggerOptions = Omit<Partial<TriggerConfig>, 'mode'> & {\n /** The trigger character. Required. */\n char: string\n}\n\n/**\n * Creates a **callback** trigger that fires `onActivate` instead of showing\n * a dropdown. Useful for opening file pickers, model selectors, etc.\n *\n * Defaults: `position: 'start'`, `mode: 'callback'`.\n */\nexport function callbackTrigger(opts: CallbackTriggerOptions): TriggerConfig {\n const { char, ...rest } = opts\n return {\n char,\n position: 'start',\n mode: 'callback',\n ...rest,\n }\n}\n"]}
@@ -1,125 +0,0 @@
1
- 'use client';
2
- import { cn } from './chunk-NF2LHZIE.js';
3
- import { useRef, useState, useCallback, useEffect } from 'react';
4
- import { jsxs, jsx } from 'react/jsx-runtime';
5
-
6
- var SHOW_THRESHOLD = 300;
7
- var HIDE_THRESHOLD = 100;
8
- function useScrollObserver() {
9
- const scrollRef = useRef(null);
10
- const [showGoToTop, setShowGoToTop] = useState(false);
11
- const [showGoToBottom, setShowGoToBottom] = useState(false);
12
- const rafId = useRef(0);
13
- const update = useCallback(() => {
14
- cancelAnimationFrame(rafId.current);
15
- rafId.current = requestAnimationFrame(() => {
16
- const el = scrollRef.current;
17
- if (!el) return;
18
- const scrollTop = el.scrollTop;
19
- const distanceFromBottom = el.scrollHeight - scrollTop - el.clientHeight;
20
- setShowGoToTop((prev) => prev ? scrollTop > HIDE_THRESHOLD : scrollTop > SHOW_THRESHOLD);
21
- setShowGoToBottom(
22
- (prev) => prev ? distanceFromBottom > HIDE_THRESHOLD : distanceFromBottom > SHOW_THRESHOLD
23
- );
24
- });
25
- }, []);
26
- useEffect(() => {
27
- const el = scrollRef.current;
28
- if (!el) return;
29
- el.addEventListener("scroll", update, { passive: true });
30
- update();
31
- return () => {
32
- el.removeEventListener("scroll", update);
33
- cancelAnimationFrame(rafId.current);
34
- };
35
- }, [update]);
36
- const scrollToTop = useCallback(() => {
37
- scrollRef.current?.scrollTo({ top: 0, behavior: "smooth" });
38
- }, []);
39
- const scrollToBottom = useCallback(() => {
40
- const el = scrollRef.current;
41
- if (!el) return;
42
- el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
43
- }, []);
44
- return { scrollRef, showGoToTop, showGoToBottom, scrollToTop, scrollToBottom };
45
- }
46
- var NAV_BUTTON_CLASS = "pointer-events-auto rounded-full border bg-background p-2 shadow-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors animate-in fade-in-0 zoom-in-95 duration-150";
47
- function Svg({ className, children }) {
48
- return /* @__PURE__ */ jsx(
49
- "svg",
50
- {
51
- xmlns: "http://www.w3.org/2000/svg",
52
- width: "24",
53
- height: "24",
54
- viewBox: "0 0 24 24",
55
- fill: "none",
56
- stroke: "currentColor",
57
- strokeWidth: "2",
58
- strokeLinecap: "round",
59
- strokeLinejoin: "round",
60
- "aria-hidden": "true",
61
- className,
62
- children
63
- }
64
- );
65
- }
66
- var ArrowUp = ({ className }) => /* @__PURE__ */ jsxs(Svg, { className, children: [
67
- /* @__PURE__ */ jsx("path", { d: "m5 12 7-7 7 7" }),
68
- /* @__PURE__ */ jsx("path", { d: "M12 19V5" })
69
- ] });
70
- var ArrowDown = ({ className }) => /* @__PURE__ */ jsxs(Svg, { className, children: [
71
- /* @__PURE__ */ jsx("path", { d: "M12 5v14" }),
72
- /* @__PURE__ */ jsx("path", { d: "m19 12-7 7-7-7" })
73
- ] });
74
- function ChatPromptLayout({
75
- children,
76
- prompt,
77
- className,
78
- "aria-label": ariaLabel,
79
- "data-test-id": dataTestId,
80
- ref
81
- }) {
82
- const { scrollRef, showGoToTop, showGoToBottom, scrollToTop, scrollToBottom } = useScrollObserver();
83
- return /* @__PURE__ */ jsxs(
84
- "div",
85
- {
86
- ref,
87
- role: "region",
88
- "aria-label": ariaLabel ?? "Chat layout",
89
- "data-test-id": dataTestId,
90
- className: cn("chat-prompt-layout", "flex h-full flex-col", className),
91
- children: [
92
- /* @__PURE__ */ jsxs("div", { ref: scrollRef, className: "relative flex-1 overflow-y-auto", children: [
93
- children,
94
- /* @__PURE__ */ jsxs("div", { className: "pointer-events-none sticky bottom-4 flex justify-end gap-2 px-4 pb-2", children: [
95
- showGoToTop && /* @__PURE__ */ jsx(
96
- "button",
97
- {
98
- type: "button",
99
- onClick: scrollToTop,
100
- className: NAV_BUTTON_CLASS,
101
- "aria-label": "Scroll to top",
102
- children: /* @__PURE__ */ jsx(ArrowUp, { className: "size-4" })
103
- }
104
- ),
105
- showGoToBottom && /* @__PURE__ */ jsx(
106
- "button",
107
- {
108
- type: "button",
109
- onClick: scrollToBottom,
110
- className: NAV_BUTTON_CLASS,
111
- "aria-label": "Scroll to bottom",
112
- children: /* @__PURE__ */ jsx(ArrowDown, { className: "size-4" })
113
- }
114
- )
115
- ] })
116
- ] }),
117
- /* @__PURE__ */ jsx("div", { className: "shrink-0", children: prompt })
118
- ]
119
- }
120
- );
121
- }
122
-
123
- export { ChatPromptLayout };
124
- //# sourceMappingURL=chunk-XDKRP7UE.js.map
125
- //# sourceMappingURL=chunk-XDKRP7UE.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/chat-prompt-layout/use-scroll-observer.ts","../src/chat-prompt-layout/chat-prompt-layout.tsx"],"names":[],"mappings":"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","file":"chunk-XDKRP7UE.js","sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n/** Distance from edge before showing a button */\nconst SHOW_THRESHOLD = 300\n/** Distance from edge before hiding a button (larger = less flicker) */\nconst HIDE_THRESHOLD = 100\n\nexport function useScrollObserver() {\n const scrollRef = useRef<HTMLDivElement>(null)\n const [showGoToTop, setShowGoToTop] = useState(false)\n const [showGoToBottom, setShowGoToBottom] = useState(false)\n const rafId = useRef(0)\n\n const update = useCallback(() => {\n // Coalesce rapid scroll events into a single rAF\n cancelAnimationFrame(rafId.current)\n rafId.current = requestAnimationFrame(() => {\n const el = scrollRef.current\n if (!el) return\n\n const scrollTop = el.scrollTop\n const distanceFromBottom = el.scrollHeight - scrollTop - el.clientHeight\n\n // Hysteresis: use a higher threshold to show, lower to hide\n setShowGoToTop((prev) => (prev ? scrollTop > HIDE_THRESHOLD : scrollTop > SHOW_THRESHOLD))\n setShowGoToBottom((prev) =>\n prev ? distanceFromBottom > HIDE_THRESHOLD : distanceFromBottom > SHOW_THRESHOLD,\n )\n })\n }, [])\n\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n el.addEventListener('scroll', update, { passive: true })\n update()\n\n return () => {\n el.removeEventListener('scroll', update)\n cancelAnimationFrame(rafId.current)\n }\n }, [update])\n\n const scrollToTop = useCallback(() => {\n scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' })\n }, [])\n\n const scrollToBottom = useCallback(() => {\n const el = scrollRef.current\n if (!el) return\n el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' })\n }, [])\n\n return { scrollRef, showGoToTop, showGoToBottom, scrollToTop, scrollToBottom }\n}\n","'use client'\n\nimport { cn } from '@/lib/utils'\nimport type { ChatPromptLayoutProps } from './types'\nimport { useScrollObserver } from './use-scroll-observer'\n\nconst NAV_BUTTON_CLASS =\n 'pointer-events-auto rounded-full border bg-background p-2 shadow-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors animate-in fade-in-0 zoom-in-95 duration-150'\n\ntype IconProps = { className?: string }\n\n/** Shared SVG wrapper matching the lucide icon defaults (no dependency). */\nfunction Svg({ className, children }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className={className}>\n {children}\n </svg>\n )\n}\n\nconst ArrowUp = ({ className }: IconProps) => (\n <Svg className={className}>\n <path d=\"m5 12 7-7 7 7\" />\n <path d=\"M12 19V5\" />\n </Svg>\n)\nconst ArrowDown = ({ className }: IconProps) => (\n <Svg className={className}>\n <path d=\"M12 5v14\" />\n <path d=\"m19 12-7 7-7-7\" />\n </Svg>\n)\n\n/**\n * ChatPromptLayout - A full-height chat layout with scrollable messages\n * and a bottom-anchored prompt slot.\n *\n * Pass chat messages as `children` and the prompt area via the `prompt`\n * prop. Contextual scroll buttons appear when the user scrolls away\n * from the top or bottom of the messages area.\n *\n * @example\n * ```tsx\n * <ChatPromptLayout\n * className=\"h-[600px]\"\n * prompt={\n * <div className=\"border-t p-4\">\n * <PromptArea ... />\n * <ActionBar ... />\n * </div>\n * }\n * >\n * {messages.map(msg => <ChatBubble key={msg.id} {...msg} />)}\n * </ChatPromptLayout>\n * ```\n */\nexport function ChatPromptLayout({\n children,\n prompt,\n className,\n 'aria-label': ariaLabel,\n 'data-test-id': dataTestId,\n ref,\n}: ChatPromptLayoutProps & { ref?: React.Ref<HTMLDivElement> }) {\n const { scrollRef, showGoToTop, showGoToBottom, scrollToTop, scrollToBottom } =\n useScrollObserver()\n\n return (\n <div\n ref={ref}\n role=\"region\"\n aria-label={ariaLabel ?? 'Chat layout'}\n data-test-id={dataTestId}\n className={cn('chat-prompt-layout', 'flex h-full flex-col', className)}>\n <div ref={scrollRef} className=\"relative flex-1 overflow-y-auto\">\n {children}\n\n <div className=\"pointer-events-none sticky bottom-4 flex justify-end gap-2 px-4 pb-2\">\n {showGoToTop && (\n <button\n type=\"button\"\n onClick={scrollToTop}\n className={NAV_BUTTON_CLASS}\n aria-label=\"Scroll to top\">\n <ArrowUp className=\"size-4\" />\n </button>\n )}\n {showGoToBottom && (\n <button\n type=\"button\"\n onClick={scrollToBottom}\n className={NAV_BUTTON_CLASS}\n aria-label=\"Scroll to bottom\">\n <ArrowDown className=\"size-4\" />\n </button>\n )}\n </div>\n </div>\n\n <div className=\"shrink-0\">{prompt}</div>\n </div>\n )\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/prompt-area/prompt-area-engine.ts","../../src/prompt-area/segment-helpers.ts","../../src/prompt-area/trigger-presets.ts"],"names":["text","matchingTrigger","chip"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Pure logic engine for the PromptArea component.\n * No DOM dependencies - fully testable in Node.\n */\nimport type { Segment, ChipSegment, TriggerConfig, TriggerPosition, ActiveTrigger } from './types'\n\n// ---------------------------------------------------------------------------\n// Serialization\n// ---------------------------------------------------------------------------\n\n/**\n * Converts an array of segments to a plain text string.\n * Chips are represented as `{trigger}{displayText}` (e.g., \"@Alice\").\n */\nexport function segmentsToPlainText(segments: Segment[]): string {\n return segments\n .map((seg) => {\n if (seg.type === 'text') return seg.text\n return `${seg.trigger}${seg.displayText}`\n })\n .join('')\n}\n\n/**\n * Converts plain text into a single text segment.\n * Used for initial value conversion from plain strings.\n */\nexport function plainTextToSegments(text: string): Segment[] {\n if (!text) return []\n return [{ type: 'text', text }]\n}\n\n// ---------------------------------------------------------------------------\n// Trigger position validation\n// ---------------------------------------------------------------------------\n\n/**\n * Checks whether a trigger character at the given position in text\n * is valid according to the position rule.\n *\n * @param text - The full text content\n * @param charIndex - The index of the trigger character in the text\n * @param position - The position rule to validate against\n */\nexport function isValidTriggerPosition(\n text: string,\n charIndex: number,\n position: TriggerPosition,\n): boolean {\n if (charIndex === 0) return true\n\n const prevChar = text[charIndex - 1]\n\n if (position === 'start') {\n return prevChar === '\\n'\n }\n\n // position === 'any': valid after any whitespace\n return prevChar === ' ' || prevChar === '\\n' || prevChar === '\\t'\n}\n\n// ---------------------------------------------------------------------------\n// Trigger detection\n// ---------------------------------------------------------------------------\n\n/**\n * Scans backwards from the cursor position to detect if the user is\n * currently typing a trigger word.\n *\n * Returns the active trigger info, or null if no trigger is active.\n *\n * @param text - The full plain text content\n * @param cursorPos - The cursor position (character offset from start)\n * @param triggers - Available trigger configurations\n */\nexport function detectActiveTrigger(\n text: string,\n cursorPos: number,\n triggers: TriggerConfig[],\n): ActiveTrigger | null {\n if (!text || cursorPos === 0 || triggers.length === 0) return null\n\n // Scan backwards from cursor to find the nearest trigger character.\n // Stop at whitespace (trigger word has ended) or start of text.\n for (let i = cursorPos - 1; i >= 0; i--) {\n const char = text[i]\n\n // If we hit whitespace before finding a trigger, check if this whitespace\n // is immediately followed by a trigger character\n if (char === ' ' || char === '\\n' || char === '\\t') {\n // The character after this whitespace could be a trigger\n if (i + 1 < cursorPos) {\n const nextChar = text[i + 1]\n const matchingTrigger = triggers.find((t) => t.char === nextChar)\n if (matchingTrigger && isValidTriggerPosition(text, i + 1, matchingTrigger.position)) {\n return {\n config: matchingTrigger,\n startOffset: i + 1,\n query: text.slice(i + 2, cursorPos),\n }\n }\n }\n // No trigger found after this whitespace, stop searching\n return null\n }\n\n // Check if this character is a trigger character\n const matchingTrigger = triggers.find((t) => t.char === char)\n if (matchingTrigger && isValidTriggerPosition(text, i, matchingTrigger.position)) {\n return {\n config: matchingTrigger,\n startOffset: i,\n query: text.slice(i + 1, cursorPos),\n }\n }\n }\n\n return null\n}\n\n// ---------------------------------------------------------------------------\n// Chip resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolves an active trigger into a chip within the segments array.\n * Replaces the trigger text (trigger char + query) with a chip segment.\n *\n * @param segments - Current document segments\n * @param activeTrigger - The active trigger to resolve\n * @param chip - The chip data (value, displayText, optional data)\n * @returns New segments array with the chip inserted, and the new cursor position\n */\nexport function resolveChip(\n segments: Segment[],\n activeTrigger: ActiveTrigger,\n chip: { value: string; displayText: string; data?: unknown; autoResolved?: boolean },\n): { segments: Segment[]; cursorOffset: number } {\n const triggerStart = activeTrigger.startOffset\n const triggerEnd = triggerStart + 1 + activeTrigger.query.length // +1 for trigger char\n\n // Build the new segments by mapping plain text positions back to segment boundaries\n const newSegments: Segment[] = []\n let offset = 0\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipText = `${seg.trigger}${seg.displayText}`\n const chipStart = offset\n const chipEnd = offset + chipText.length\n\n // If the trigger range overlaps with this chip, something is wrong.\n // Chips should not be partially replaced.\n if (chipEnd <= triggerStart || chipStart >= triggerEnd) {\n newSegments.push(seg)\n }\n offset = chipEnd\n } else {\n const textStart = offset\n const textEnd = offset + seg.text.length\n\n if (textEnd <= triggerStart) {\n // Entirely before the trigger - keep as-is\n newSegments.push(seg)\n } else if (textStart >= triggerEnd) {\n // Entirely after the trigger - keep as-is\n newSegments.push(seg)\n } else {\n // This text segment contains (part of) the trigger range\n const beforeText = seg.text.slice(0, Math.max(0, triggerStart - textStart))\n const afterText = seg.text.slice(Math.min(seg.text.length, triggerEnd - textStart))\n\n if (beforeText) {\n newSegments.push({ type: 'text', text: beforeText })\n }\n\n const newChip: ChipSegment = {\n type: 'chip',\n trigger: activeTrigger.config.char,\n value: chip.value,\n displayText: chip.displayText,\n ...(chip.data !== undefined ? { data: chip.data } : {}),\n ...(chip.autoResolved ? { autoResolved: true } : {}),\n }\n newSegments.push(newChip)\n\n // Add trailing space after chip, then any remaining text\n if (afterText) {\n newSegments.push({ type: 'text', text: ' ' + afterText.replace(/^\\s/, '') })\n } else {\n newSegments.push({ type: 'text', text: ' ' })\n }\n }\n\n offset = textEnd\n }\n }\n\n // Merge adjacent text segments\n const merged = mergeAdjacentTextSegments(newSegments)\n\n // Cursor should be placed after the chip + trailing space.\n // Find the *last* matching chip so duplicates resolve correctly.\n let lastChipEndOffset = -1\n let runningOffset = 0\n for (const seg of merged) {\n if (seg.type === 'text') {\n runningOffset += seg.text.length\n } else {\n runningOffset += seg.trigger.length + seg.displayText.length\n if (\n seg.value === chip.value &&\n seg.displayText === chip.displayText &&\n seg.trigger === activeTrigger.config.char\n ) {\n lastChipEndOffset = runningOffset\n }\n }\n }\n // +1 accounts for the trailing space after the chip\n const cursorOffset = lastChipEndOffset === -1 ? runningOffset : lastChipEndOffset + 1\n\n return { segments: merged, cursorOffset }\n}\n\n// ---------------------------------------------------------------------------\n// Chip removal\n// ---------------------------------------------------------------------------\n\n/**\n * Removes a chip at the given segment index and merges adjacent text segments.\n *\n * @param segments - Current document segments\n * @param index - The segment index to remove\n * @returns New segments array with the chip removed\n */\nexport function removeChipAtIndex(segments: Segment[], index: number): Segment[] {\n if (index < 0 || index >= segments.length) return segments\n if (segments[index].type !== 'chip') return segments\n\n const result = [...segments.slice(0, index), ...segments.slice(index + 1)]\n return mergeAdjacentTextSegments(result)\n}\n\n/**\n * Reverts an auto-resolved chip at the given segment index back to plain text.\n * The text includes the trigger character + display text (e.g., \"#readme\").\n *\n * @param segments - Current document segments\n * @param index - The segment index to revert\n * @returns New segments with the chip replaced by text, or null if not applicable\n */\nexport function revertChipAtIndex(\n segments: Segment[],\n index: number,\n): { segments: Segment[]; revertedText: string } | null {\n if (index < 0 || index >= segments.length) return null\n const seg = segments[index]\n if (seg.type !== 'chip' || !seg.autoResolved) return null\n\n const revertedText = `${seg.trigger}${seg.displayText}`\n const result = [\n ...segments.slice(0, index),\n { type: 'text' as const, text: revertedText },\n ...segments.slice(index + 1),\n ]\n return { segments: mergeAdjacentTextSegments(result), revertedText }\n}\n\n// ---------------------------------------------------------------------------\n// Paste: resolve trigger patterns in segments\n// ---------------------------------------------------------------------------\n\n/**\n * Scans text segments for trigger patterns and auto-resolves them into chips.\n * Only resolves triggers that have `resolveOnSpace: true`.\n *\n * Trigger patterns must appear at word boundaries: start of text, after\n * whitespace, or after a newline. This avoids false positives like email\n * addresses (user@example.com).\n */\nexport function resolveTriggersInSegments(\n segments: Segment[],\n triggers: TriggerConfig[],\n): Segment[] {\n const autoResolveTriggers = triggers.filter((t) => t.resolveOnSpace)\n if (autoResolveTriggers.length === 0) return segments\n\n const triggerChars = new Set(autoResolveTriggers.map((t) => t.char))\n const result: Segment[] = []\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n result.push(seg)\n continue\n }\n\n const parts = splitTextByTriggerPatterns(seg.text, autoResolveTriggers, triggerChars)\n result.push(...parts)\n }\n\n return mergeAdjacentTextSegments(result)\n}\n\n/**\n * Splits a text string into text and chip segments based on trigger patterns.\n * A trigger pattern is: (start-of-string | whitespace) + trigger_char + word_chars\n * followed by whitespace or end-of-string.\n */\nfunction splitTextByTriggerPatterns(\n text: string,\n triggers: TriggerConfig[],\n triggerChars: Set<string>,\n): Segment[] {\n if (!text) return []\n\n const segments: Segment[] = []\n let i = 0\n\n while (i < text.length) {\n const char = text[i]\n\n if (triggerChars.has(char)) {\n const isAtBoundary =\n i === 0 || text[i - 1] === ' ' || text[i - 1] === '\\n' || text[i - 1] === '\\t'\n\n if (isAtBoundary) {\n const trigger = triggers.find((t) => t.char === char)\n if (trigger && isValidTriggerPosition(text, i, trigger.position)) {\n let end = i + 1\n while (\n end < text.length &&\n text[end] !== ' ' &&\n text[end] !== '\\n' &&\n text[end] !== '\\t'\n ) {\n end++\n }\n\n const query = text.slice(i + 1, end)\n if (query.length > 0) {\n // Treat both undefined and '' from onSelect as \"no custom label\"\n // and fall back to the query — an empty displayText would render\n // a blank chip.\n const displayText = trigger.onSelect?.({ value: query, label: query }) || query\n segments.push({\n type: 'chip',\n trigger: char,\n value: query,\n displayText,\n autoResolved: true,\n })\n i = end\n continue\n }\n }\n }\n }\n\n const start = i\n i++\n while (\n i < text.length &&\n !(\n triggerChars.has(text[i]) &&\n (text[i - 1] === ' ' || text[i - 1] === '\\n' || text[i - 1] === '\\t')\n )\n ) {\n i++\n }\n segments.push({ type: 'text', text: text.slice(start, i) })\n }\n\n return segments\n}\n\n// ---------------------------------------------------------------------------\n// Text range replacement\n// ---------------------------------------------------------------------------\n\n/**\n * Replaces a range of plain text within the segments array.\n * Handles segment boundaries correctly, preserving chip segments.\n *\n * @param segments - Current document segments\n * @param start - Start offset in plain text\n * @param end - End offset in plain text\n * @param replacement - The replacement text\n * @returns New segments array with the replacement applied\n */\nexport function replaceTextRange(\n segments: Segment[],\n start: number,\n end: number,\n replacement: string,\n): Segment[] {\n const newSegments: Segment[] = []\n let offset = 0\n let inserted = false\n\n for (const seg of segments) {\n if (seg.type === 'chip') {\n const chipText = `${seg.trigger}${seg.displayText}`\n const chipStart = offset\n const chipEnd = offset + chipText.length\n\n // For insertion (start === end), insert before this chip if position matches\n if (!inserted && start === end && chipStart === start) {\n newSegments.push({ type: 'text', text: replacement })\n inserted = true\n }\n\n if (chipEnd <= start || chipStart >= end) {\n newSegments.push(seg)\n }\n // Chips within the range are removed\n offset = chipEnd\n } else {\n const textStart = offset\n const textEnd = offset + seg.text.length\n\n // Check if this segment contains the insertion/replacement point\n const isBefore = start === end ? textEnd < start : textEnd <= start\n const isAfter = start === end ? textStart > end : textStart >= end\n\n if (isBefore) {\n // Entirely before the range\n newSegments.push(seg)\n } else if (isAfter) {\n // Entirely after the range\n newSegments.push(seg)\n } else {\n // Overlaps with the range (or contains the insertion point)\n const beforeText = seg.text.slice(0, Math.max(0, start - textStart))\n const afterText = seg.text.slice(Math.min(seg.text.length, end - textStart))\n\n if (beforeText) {\n newSegments.push({ type: 'text', text: beforeText })\n }\n // Insert replacement only once (when we first enter the range)\n if (!inserted && textStart <= start) {\n newSegments.push({ type: 'text', text: replacement })\n inserted = true\n }\n if (afterText) {\n newSegments.push({ type: 'text', text: afterText })\n }\n }\n\n offset = textEnd\n }\n }\n\n // Fallback: if replacement wasn't inserted (e.g., insertion at very end)\n if (!inserted && replacement) {\n newSegments.push({ type: 'text', text: replacement })\n }\n\n return mergeAdjacentTextSegments(newSegments)\n}\n\n// ---------------------------------------------------------------------------\n// Markdown formatting shortcuts\n// ---------------------------------------------------------------------------\n\n/**\n * Toggles markdown wrap markers around a selected text range.\n * If the selection is already wrapped with the marker, unwraps it.\n * If not wrapped, wraps it.\n *\n * @param segments - Current document segments\n * @param selectionStart - Start offset in plain text\n * @param selectionEnd - End offset in plain text\n * @param marker - The markdown marker (e.g., '**' for bold, '*' for italic)\n * @returns New segments and selection offsets, or null if selection is collapsed\n */\nexport function toggleMarkdownWrap(\n segments: Segment[],\n selectionStart: number,\n selectionEnd: number,\n marker: string,\n): { segments: Segment[]; selectionStart: number; selectionEnd: number } | null {\n if (selectionStart === selectionEnd) return null\n\n const plainText = segmentsToPlainText(segments)\n const markerLen = marker.length\n\n // Check if already wrapped\n const hasOpeningMarker =\n selectionStart >= markerLen &&\n plainText.slice(selectionStart - markerLen, selectionStart) === marker\n const hasClosingMarker =\n selectionEnd + markerLen <= plainText.length &&\n plainText.slice(selectionEnd, selectionEnd + markerLen) === marker\n\n let isWrapped = hasOpeningMarker && hasClosingMarker\n\n // For single-char markers (e.g., '*'), ensure we're not matching\n // inside a multi-char marker (e.g., '**')\n if (isWrapped && markerLen === 1) {\n const charBeforeOpening =\n selectionStart > markerLen ? plainText[selectionStart - markerLen - 1] : ''\n const charAfterClosing =\n selectionEnd + markerLen < plainText.length ? plainText[selectionEnd + markerLen] : ''\n if (charBeforeOpening === marker || charAfterClosing === marker) {\n isWrapped = false\n }\n }\n\n if (isWrapped) {\n // Unwrap: remove closing marker first (preserves start offsets), then opening\n const afterClosing = replaceTextRange(segments, selectionEnd, selectionEnd + markerLen, '')\n const afterOpening = replaceTextRange(\n afterClosing,\n selectionStart - markerLen,\n selectionStart,\n '',\n )\n return {\n segments: afterOpening,\n selectionStart: selectionStart - markerLen,\n selectionEnd: selectionEnd - markerLen,\n }\n }\n\n // Wrap: insert closing marker first (preserves start offsets), then opening\n const afterClosing = replaceTextRange(segments, selectionEnd, selectionEnd, marker)\n const afterOpening = replaceTextRange(afterClosing, selectionStart, selectionStart, marker)\n return {\n segments: afterOpening,\n selectionStart: selectionStart + markerLen,\n selectionEnd: selectionEnd + markerLen,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Inline markdown parsing\n// ---------------------------------------------------------------------------\n\nexport type MarkdownToken =\n | { type: 'plain'; text: string }\n | { type: 'bold'; text: string }\n | { type: 'italic'; text: string }\n | { type: 'bold-italic'; text: string }\n | { type: 'url'; text: string }\n\n/**\n * Parses text for simple inline markdown: bold, italic, bold-italic, and URLs.\n * Does NOT handle block-level markdown (lists, headings, etc.).\n */\nexport function parseInlineMarkdown(text: string): MarkdownToken[] {\n if (!text) return []\n\n const tokens: MarkdownToken[] = []\n // Regex patterns for inline markdown elements:\n // 1. ***text*** or ___text___ -> bold-italic\n // 2. **text** or __text__ -> bold\n // 3. *text* or _text_ -> italic\n // 4. https://... or http://... -> URL\n const pattern = /(\\*{3}(.+?)\\*{3})|(\\*{2}(.+?)\\*{2})|(\\*(.+?)\\*)|(https?:\\/\\/[^\\s),]+)/g\n\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n while ((match = pattern.exec(text)) !== null) {\n // Add any plain text before this match\n if (match.index > lastIndex) {\n tokens.push({ type: 'plain', text: text.slice(lastIndex, match.index) })\n }\n\n if (match[1] && match[2]) {\n // ***bold-italic***\n tokens.push({ type: 'bold-italic', text: match[2] })\n } else if (match[3] && match[4]) {\n // **bold**\n tokens.push({ type: 'bold', text: match[4] })\n } else if (match[5] && match[6]) {\n // *italic*\n tokens.push({ type: 'italic', text: match[6] })\n } else if (match[7]) {\n // URL\n tokens.push({ type: 'url', text: match[7] })\n }\n\n lastIndex = match.index + match[0].length\n }\n\n // Add any remaining plain text\n if (lastIndex < text.length) {\n tokens.push({ type: 'plain', text: text.slice(lastIndex) })\n }\n\n return tokens\n}\n\n// ---------------------------------------------------------------------------\n// Segment comparison\n// ---------------------------------------------------------------------------\n\n/**\n * Shallow equality check for two segment arrays.\n * Compares type, text, trigger, value, displayText, and autoResolved fields.\n * Avoids JSON.stringify overhead for the common case.\n */\nexport function segmentsEqual(a: Segment[], b: Segment[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n\n for (let i = 0; i < a.length; i++) {\n const sa = a[i]\n const sb = b[i]\n if (sa.type !== sb.type) return false\n if (sa.type === 'text') {\n if (sb.type !== 'text' || sa.text !== sb.text) return false\n } else {\n if (\n sb.type !== 'chip' ||\n sa.trigger !== sb.trigger ||\n sa.value !== sb.value ||\n sa.displayText !== sb.displayText ||\n sa.autoResolved !== sb.autoResolved\n )\n return false\n }\n }\n return true\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Merges adjacent text segments into single text segments.\n * Also removes empty text segments.\n */\nexport function mergeAdjacentTextSegments(segments: Segment[]): Segment[] {\n const result: Segment[] = []\n\n for (const seg of segments) {\n if (seg.type === 'text' && seg.text === '') continue\n\n const last = result[result.length - 1]\n if (seg.type === 'text' && last?.type === 'text') {\n // Merge with previous text segment\n result[result.length - 1] = { type: 'text', text: last.text + seg.text }\n } else {\n result.push(seg)\n }\n }\n\n return result\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","/**\n * Pre-built trigger configuration factories for common AI chat patterns.\n *\n * Each factory returns a full `TriggerConfig` with sensible defaults.\n * Pass only what you need to override.\n *\n * @example\n * ```tsx\n * <PromptArea\n * triggers={[\n * mentionTrigger({ onSearch: searchUsers }),\n * commandTrigger({ onSearch: searchCommands }),\n * hashtagTrigger(),\n * ]}\n * />\n * ```\n */\n\nimport type { TriggerConfig } from './types'\n\n// ---------------------------------------------------------------------------\n// Shared option type — everything in TriggerConfig except the keys each\n// factory sets by default.\n// ---------------------------------------------------------------------------\n\ntype TriggerPresetOptions = Omit<Partial<TriggerConfig>, 'char' | 'position' | 'mode'>\n\n// ---------------------------------------------------------------------------\n// @mention — dropdown at any position\n// ---------------------------------------------------------------------------\n\nexport type MentionTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'@'`. */\n char?: string\n}\n\n/**\n * Creates a **mention** trigger (`@`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * accessible label `\"mention\"`.\n */\nexport function mentionTrigger(opts: MentionTriggerOptions = {}): TriggerConfig {\n const { char = '@', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n accessibilityLabel: 'mention',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// /command — dropdown only at line start\n// ---------------------------------------------------------------------------\n\nexport type CommandTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'/'`. */\n char?: string\n}\n\n/**\n * Creates a **command** trigger (`/`).\n *\n * Defaults: `position: 'start'`, `mode: 'dropdown'`, `chipStyle: 'inline'`,\n * accessible label `\"command\"`.\n */\nexport function commandTrigger(opts: CommandTriggerOptions = {}): TriggerConfig {\n const { char = '/', ...rest } = opts\n return {\n char,\n position: 'start',\n mode: 'dropdown',\n chipStyle: 'inline',\n accessibilityLabel: 'command',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// #hashtag — dropdown at any position, auto-resolve on space\n// ---------------------------------------------------------------------------\n\nexport type HashtagTriggerOptions = TriggerPresetOptions & {\n /** Override the trigger character. Defaults to `'#'`. */\n char?: string\n}\n\n/**\n * Creates a **hashtag / tag** trigger (`#`).\n *\n * Defaults: `position: 'any'`, `mode: 'dropdown'`, `chipStyle: 'pill'`,\n * `resolveOnSpace: true`, accessible label `\"tag\"`.\n */\nexport function hashtagTrigger(opts: HashtagTriggerOptions = {}): TriggerConfig {\n const { char = '#', ...rest } = opts\n return {\n char,\n position: 'any',\n mode: 'dropdown',\n chipStyle: 'pill',\n resolveOnSpace: true,\n accessibilityLabel: 'tag',\n ...rest,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Generic callback trigger (e.g., for file pickers, model selectors)\n// ---------------------------------------------------------------------------\n\nexport type CallbackTriggerOptions = Omit<Partial<TriggerConfig>, 'mode'> & {\n /** The trigger character. Required. */\n char: string\n}\n\n/**\n * Creates a **callback** trigger that fires `onActivate` instead of showing\n * a dropdown. Useful for opening file pickers, model selectors, etc.\n *\n * Defaults: `position: 'start'`, `mode: 'callback'`.\n */\nexport function callbackTrigger(opts: CallbackTriggerOptions): TriggerConfig {\n const { char, ...rest } = opts\n return {\n char,\n position: 'start',\n mode: 'callback',\n ...rest,\n }\n}\n"]}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}