composeai 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +126 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -1
- package/dist/index.d.ts +55 -1
- package/dist/index.js +126 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/composer.css +31 -0
package/dist/index.d.cts
CHANGED
|
@@ -531,6 +531,20 @@ interface GhostedAutoCompleteConfig {
|
|
|
531
531
|
*/
|
|
532
532
|
minLength?: number;
|
|
533
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Object form of {@link ComposerProps.animatedPlaceholder}. Use it when you
|
|
536
|
+
* need to opt into looping; the bare `string[]` form is equivalent to
|
|
537
|
+
* `{ phrases, loop: false }`.
|
|
538
|
+
*/
|
|
539
|
+
interface AnimatedPlaceholderConfig {
|
|
540
|
+
/** Phrases the empty editor types out, in order. */
|
|
541
|
+
phrases: string[];
|
|
542
|
+
/**
|
|
543
|
+
* Cycle the list forever. Defaults to `false` — the list plays through once
|
|
544
|
+
* and then settles (see {@link ComposerProps.animatedPlaceholder}).
|
|
545
|
+
*/
|
|
546
|
+
loop?: boolean;
|
|
547
|
+
}
|
|
534
548
|
/**
|
|
535
549
|
* Editor helpers handed to a {@link CustomAction} when it's clicked, so a
|
|
536
550
|
* custom toolbar button can mutate the composer the same way a slash command
|
|
@@ -748,6 +762,46 @@ interface ComposerProps {
|
|
|
748
762
|
*/
|
|
749
763
|
focusShortcut?: string | false | null;
|
|
750
764
|
placeholder?: string;
|
|
765
|
+
/**
|
|
766
|
+
* Animated placeholder: a list of phrases the empty editor cycles through
|
|
767
|
+
* with a typewriter effect — each phrase is revealed one character at a
|
|
768
|
+
* time, held, erased, and the next begins. The animation takes precedence
|
|
769
|
+
* over the static {@link placeholder} while the editor is empty and pauses
|
|
770
|
+
* the moment the user starts typing.
|
|
771
|
+
*
|
|
772
|
+
* Pass a `string[]` for the default behaviour, or an
|
|
773
|
+
* {@link AnimatedPlaceholderConfig} to enable looping.
|
|
774
|
+
*
|
|
775
|
+
* **Looping** (`loop`, default `false`):
|
|
776
|
+
* - `loop: false` — play through the list once, then settle: on the
|
|
777
|
+
* {@link placeholder} prop if one was given, otherwise on the last phrase
|
|
778
|
+
* (left on screen).
|
|
779
|
+
* - `loop: true` — cycle the list forever.
|
|
780
|
+
*
|
|
781
|
+
* Honours `prefers-reduced-motion` by showing the first phrase statically.
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* // Play once, then rest on the last phrase (no `placeholder` given):
|
|
785
|
+
* <Composer
|
|
786
|
+
* animatedPlaceholder={[
|
|
787
|
+
* "Ask me anything…",
|
|
788
|
+
* "Summarize this thread",
|
|
789
|
+
* "Draft a reply to Alex",
|
|
790
|
+
* ]}
|
|
791
|
+
* onSend={...}
|
|
792
|
+
* />
|
|
793
|
+
*
|
|
794
|
+
* @example
|
|
795
|
+
* // Loop forever:
|
|
796
|
+
* <Composer
|
|
797
|
+
* animatedPlaceholder={{
|
|
798
|
+
* phrases: ["Ask me anything…", "Summarize this thread"],
|
|
799
|
+
* loop: true,
|
|
800
|
+
* }}
|
|
801
|
+
* onSend={...}
|
|
802
|
+
* />
|
|
803
|
+
*/
|
|
804
|
+
animatedPlaceholder?: string[] | AnimatedPlaceholderConfig;
|
|
751
805
|
/**
|
|
752
806
|
* Shorthand for `classNames.root`. Kept for back-compat; if both are set,
|
|
753
807
|
* the two are merged (`className` first, then `classNames.root`).
|
|
@@ -1016,4 +1070,4 @@ interface SuggestionRowProps {
|
|
|
1016
1070
|
}
|
|
1017
1071
|
declare function SuggestionRow({ items, onSelect, className }: SuggestionRowProps): react.JSX.Element;
|
|
1018
1072
|
|
|
1019
|
-
export { type Attachment, type AttachmentKind, type AttachmentOptions, type AttachmentStatus, type AttachmentTypeOption, type AttachmentsConfig, Composer, type ComposerFeatures, type ComposerHandle, type ComposerIcons, type ComposerPromptBehavior, type ComposerPromptsConfig, type ComposerProps, type ComposerSlot, type ComposerSlotClassNames, type ComposerSlots, type ComposerSubmitPayload, type ComposerSxMap, type ComposerSxValue, type ComposerTokens, type CustomAction, type CustomActionContext, type DiagramRenderer, type GhostedAutoCompleteConfig, type IconComponent, type IconProps, type MarkdownConfig, type MarkdownMode, type MentionConfig, type MentionItem, type MentionRef, type MermaidConfig, type SendButtonRenderProps, type SlashCommand, type SlashCommandContext, type SlashConfig, type StopButtonRenderProps, SuggestionRow, type SuggestionRowProps };
|
|
1073
|
+
export { type AnimatedPlaceholderConfig, type Attachment, type AttachmentKind, type AttachmentOptions, type AttachmentStatus, type AttachmentTypeOption, type AttachmentsConfig, Composer, type ComposerFeatures, type ComposerHandle, type ComposerIcons, type ComposerPromptBehavior, type ComposerPromptsConfig, type ComposerProps, type ComposerSlot, type ComposerSlotClassNames, type ComposerSlots, type ComposerSubmitPayload, type ComposerSxMap, type ComposerSxValue, type ComposerTokens, type CustomAction, type CustomActionContext, type DiagramRenderer, type GhostedAutoCompleteConfig, type IconComponent, type IconProps, type MarkdownConfig, type MarkdownMode, type MentionConfig, type MentionItem, type MentionRef, type MermaidConfig, type SendButtonRenderProps, type SlashCommand, type SlashCommandContext, type SlashConfig, type StopButtonRenderProps, SuggestionRow, type SuggestionRowProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -531,6 +531,20 @@ interface GhostedAutoCompleteConfig {
|
|
|
531
531
|
*/
|
|
532
532
|
minLength?: number;
|
|
533
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Object form of {@link ComposerProps.animatedPlaceholder}. Use it when you
|
|
536
|
+
* need to opt into looping; the bare `string[]` form is equivalent to
|
|
537
|
+
* `{ phrases, loop: false }`.
|
|
538
|
+
*/
|
|
539
|
+
interface AnimatedPlaceholderConfig {
|
|
540
|
+
/** Phrases the empty editor types out, in order. */
|
|
541
|
+
phrases: string[];
|
|
542
|
+
/**
|
|
543
|
+
* Cycle the list forever. Defaults to `false` — the list plays through once
|
|
544
|
+
* and then settles (see {@link ComposerProps.animatedPlaceholder}).
|
|
545
|
+
*/
|
|
546
|
+
loop?: boolean;
|
|
547
|
+
}
|
|
534
548
|
/**
|
|
535
549
|
* Editor helpers handed to a {@link CustomAction} when it's clicked, so a
|
|
536
550
|
* custom toolbar button can mutate the composer the same way a slash command
|
|
@@ -748,6 +762,46 @@ interface ComposerProps {
|
|
|
748
762
|
*/
|
|
749
763
|
focusShortcut?: string | false | null;
|
|
750
764
|
placeholder?: string;
|
|
765
|
+
/**
|
|
766
|
+
* Animated placeholder: a list of phrases the empty editor cycles through
|
|
767
|
+
* with a typewriter effect — each phrase is revealed one character at a
|
|
768
|
+
* time, held, erased, and the next begins. The animation takes precedence
|
|
769
|
+
* over the static {@link placeholder} while the editor is empty and pauses
|
|
770
|
+
* the moment the user starts typing.
|
|
771
|
+
*
|
|
772
|
+
* Pass a `string[]` for the default behaviour, or an
|
|
773
|
+
* {@link AnimatedPlaceholderConfig} to enable looping.
|
|
774
|
+
*
|
|
775
|
+
* **Looping** (`loop`, default `false`):
|
|
776
|
+
* - `loop: false` — play through the list once, then settle: on the
|
|
777
|
+
* {@link placeholder} prop if one was given, otherwise on the last phrase
|
|
778
|
+
* (left on screen).
|
|
779
|
+
* - `loop: true` — cycle the list forever.
|
|
780
|
+
*
|
|
781
|
+
* Honours `prefers-reduced-motion` by showing the first phrase statically.
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* // Play once, then rest on the last phrase (no `placeholder` given):
|
|
785
|
+
* <Composer
|
|
786
|
+
* animatedPlaceholder={[
|
|
787
|
+
* "Ask me anything…",
|
|
788
|
+
* "Summarize this thread",
|
|
789
|
+
* "Draft a reply to Alex",
|
|
790
|
+
* ]}
|
|
791
|
+
* onSend={...}
|
|
792
|
+
* />
|
|
793
|
+
*
|
|
794
|
+
* @example
|
|
795
|
+
* // Loop forever:
|
|
796
|
+
* <Composer
|
|
797
|
+
* animatedPlaceholder={{
|
|
798
|
+
* phrases: ["Ask me anything…", "Summarize this thread"],
|
|
799
|
+
* loop: true,
|
|
800
|
+
* }}
|
|
801
|
+
* onSend={...}
|
|
802
|
+
* />
|
|
803
|
+
*/
|
|
804
|
+
animatedPlaceholder?: string[] | AnimatedPlaceholderConfig;
|
|
751
805
|
/**
|
|
752
806
|
* Shorthand for `classNames.root`. Kept for back-compat; if both are set,
|
|
753
807
|
* the two are merged (`className` first, then `classNames.root`).
|
|
@@ -1016,4 +1070,4 @@ interface SuggestionRowProps {
|
|
|
1016
1070
|
}
|
|
1017
1071
|
declare function SuggestionRow({ items, onSelect, className }: SuggestionRowProps): react.JSX.Element;
|
|
1018
1072
|
|
|
1019
|
-
export { type Attachment, type AttachmentKind, type AttachmentOptions, type AttachmentStatus, type AttachmentTypeOption, type AttachmentsConfig, Composer, type ComposerFeatures, type ComposerHandle, type ComposerIcons, type ComposerPromptBehavior, type ComposerPromptsConfig, type ComposerProps, type ComposerSlot, type ComposerSlotClassNames, type ComposerSlots, type ComposerSubmitPayload, type ComposerSxMap, type ComposerSxValue, type ComposerTokens, type CustomAction, type CustomActionContext, type DiagramRenderer, type GhostedAutoCompleteConfig, type IconComponent, type IconProps, type MarkdownConfig, type MarkdownMode, type MentionConfig, type MentionItem, type MentionRef, type MermaidConfig, type SendButtonRenderProps, type SlashCommand, type SlashCommandContext, type SlashConfig, type StopButtonRenderProps, SuggestionRow, type SuggestionRowProps };
|
|
1073
|
+
export { type AnimatedPlaceholderConfig, type Attachment, type AttachmentKind, type AttachmentOptions, type AttachmentStatus, type AttachmentTypeOption, type AttachmentsConfig, Composer, type ComposerFeatures, type ComposerHandle, type ComposerIcons, type ComposerPromptBehavior, type ComposerPromptsConfig, type ComposerProps, type ComposerSlot, type ComposerSlotClassNames, type ComposerSlots, type ComposerSubmitPayload, type ComposerSxMap, type ComposerSxValue, type ComposerTokens, type CustomAction, type CustomActionContext, type DiagramRenderer, type GhostedAutoCompleteConfig, type IconComponent, type IconProps, type MarkdownConfig, type MarkdownMode, type MentionConfig, type MentionItem, type MentionRef, type MermaidConfig, type SendButtonRenderProps, type SlashCommand, type SlashCommandContext, type SlashConfig, type StopButtonRenderProps, SuggestionRow, type SuggestionRowProps };
|
package/dist/index.js
CHANGED
|
@@ -800,6 +800,7 @@ function useComposerContext() {
|
|
|
800
800
|
}
|
|
801
801
|
function EditorShell({
|
|
802
802
|
placeholder,
|
|
803
|
+
animated,
|
|
803
804
|
mode,
|
|
804
805
|
variant,
|
|
805
806
|
multiline,
|
|
@@ -818,7 +819,11 @@ function EditorShell({
|
|
|
818
819
|
const editor = slotProps("editor", editorClass, classNames, sx);
|
|
819
820
|
const editorResolved = resolveSx(sx?.editor);
|
|
820
821
|
const placeholderBase = mirrorEditorPadding(editorResolved);
|
|
821
|
-
const placeholderClass =
|
|
822
|
+
const placeholderClass = cn(
|
|
823
|
+
isCompact ? "composer-placeholder composer-placeholder--compact" : multiline ? "composer-placeholder composer-placeholder--multiline" : "composer-placeholder composer-placeholder--inline",
|
|
824
|
+
// Adds the blinking caret after the typewriter text.
|
|
825
|
+
animated && "composer-placeholder--animated"
|
|
826
|
+
);
|
|
822
827
|
const placeholderProps = slotProps(
|
|
823
828
|
"placeholder",
|
|
824
829
|
placeholderClass,
|
|
@@ -4927,6 +4932,107 @@ function useComposerHandle(ref, onSubmit) {
|
|
|
4927
4932
|
};
|
|
4928
4933
|
}, [editor, ref, onSubmit, addFiles]);
|
|
4929
4934
|
}
|
|
4935
|
+
var DEFAULT_TIMING = {
|
|
4936
|
+
typeSpeed: 55,
|
|
4937
|
+
deleteSpeed: 28,
|
|
4938
|
+
holdDuration: 1800,
|
|
4939
|
+
pauseDuration: 450
|
|
4940
|
+
};
|
|
4941
|
+
function useAnimatedPlaceholder(phrases, enabled, options) {
|
|
4942
|
+
const [state, setState] = useState({
|
|
4943
|
+
text: "",
|
|
4944
|
+
active: true
|
|
4945
|
+
});
|
|
4946
|
+
const optsRef = useRef({});
|
|
4947
|
+
optsRef.current = options ?? {};
|
|
4948
|
+
const active = enabled && Array.isArray(phrases) && phrases.length > 0;
|
|
4949
|
+
const key = active ? phrases.join("\u241F") : "";
|
|
4950
|
+
const loop = !!options?.loop;
|
|
4951
|
+
const phrasesRef = useRef(phrases);
|
|
4952
|
+
phrasesRef.current = phrases;
|
|
4953
|
+
useEffect(() => {
|
|
4954
|
+
if (!active) {
|
|
4955
|
+
setState({ text: "", active: true });
|
|
4956
|
+
return;
|
|
4957
|
+
}
|
|
4958
|
+
const list = phrasesRef.current;
|
|
4959
|
+
const lastIdx = list.length - 1;
|
|
4960
|
+
const reduceMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
4961
|
+
if (reduceMotion) {
|
|
4962
|
+
setState({ text: list[0], active: false });
|
|
4963
|
+
return;
|
|
4964
|
+
}
|
|
4965
|
+
let timer;
|
|
4966
|
+
let phraseIdx = 0;
|
|
4967
|
+
let charIdx = 0;
|
|
4968
|
+
let phase = "typing";
|
|
4969
|
+
const tick = () => {
|
|
4970
|
+
const timing = { ...DEFAULT_TIMING, ...optsRef.current };
|
|
4971
|
+
const { typeSpeed, deleteSpeed, holdDuration, pauseDuration } = timing;
|
|
4972
|
+
const settleTo = optsRef.current.settleTo;
|
|
4973
|
+
const current = list[phraseIdx];
|
|
4974
|
+
const isLast = phraseIdx === lastIdx;
|
|
4975
|
+
switch (phase) {
|
|
4976
|
+
case "typing": {
|
|
4977
|
+
charIdx += 1;
|
|
4978
|
+
setState({ text: current.slice(0, charIdx), active: true });
|
|
4979
|
+
if (charIdx >= current.length) {
|
|
4980
|
+
phase = "holding";
|
|
4981
|
+
timer = setTimeout(tick, holdDuration);
|
|
4982
|
+
} else {
|
|
4983
|
+
timer = setTimeout(tick, typeSpeed);
|
|
4984
|
+
}
|
|
4985
|
+
break;
|
|
4986
|
+
}
|
|
4987
|
+
case "holding": {
|
|
4988
|
+
if (!loop && isLast) {
|
|
4989
|
+
if (settleTo !== void 0) {
|
|
4990
|
+
phase = "settling";
|
|
4991
|
+
timer = setTimeout(tick, deleteSpeed);
|
|
4992
|
+
} else {
|
|
4993
|
+
setState({ text: current, active: false });
|
|
4994
|
+
}
|
|
4995
|
+
} else {
|
|
4996
|
+
phase = "deleting";
|
|
4997
|
+
timer = setTimeout(tick, deleteSpeed);
|
|
4998
|
+
}
|
|
4999
|
+
break;
|
|
5000
|
+
}
|
|
5001
|
+
case "deleting": {
|
|
5002
|
+
charIdx -= 1;
|
|
5003
|
+
setState({ text: current.slice(0, Math.max(0, charIdx)), active: true });
|
|
5004
|
+
if (charIdx <= 0) {
|
|
5005
|
+
phase = "pausing";
|
|
5006
|
+
timer = setTimeout(tick, pauseDuration);
|
|
5007
|
+
} else {
|
|
5008
|
+
timer = setTimeout(tick, deleteSpeed);
|
|
5009
|
+
}
|
|
5010
|
+
break;
|
|
5011
|
+
}
|
|
5012
|
+
case "pausing": {
|
|
5013
|
+
phraseIdx = phraseIdx >= lastIdx ? 0 : phraseIdx + 1;
|
|
5014
|
+
charIdx = 0;
|
|
5015
|
+
phase = "typing";
|
|
5016
|
+
timer = setTimeout(tick, typeSpeed);
|
|
5017
|
+
break;
|
|
5018
|
+
}
|
|
5019
|
+
case "settling": {
|
|
5020
|
+
charIdx -= 1;
|
|
5021
|
+
if (charIdx <= 0) {
|
|
5022
|
+
setState({ text: settleTo ?? "", active: false });
|
|
5023
|
+
} else {
|
|
5024
|
+
setState({ text: current.slice(0, charIdx), active: true });
|
|
5025
|
+
timer = setTimeout(tick, deleteSpeed);
|
|
5026
|
+
}
|
|
5027
|
+
break;
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
};
|
|
5031
|
+
timer = setTimeout(tick, DEFAULT_TIMING.typeSpeed);
|
|
5032
|
+
return () => clearTimeout(timer);
|
|
5033
|
+
}, [active, key, loop]);
|
|
5034
|
+
return active ? state : null;
|
|
5035
|
+
}
|
|
4930
5036
|
|
|
4931
5037
|
// src/internal/shortcut.ts
|
|
4932
5038
|
var MODIFIERS = /* @__PURE__ */ new Set([
|
|
@@ -5004,6 +5110,7 @@ function matchesShortcut(parsed, event) {
|
|
|
5004
5110
|
var Composer = forwardRef(function Composer2(props, ref) {
|
|
5005
5111
|
const {
|
|
5006
5112
|
placeholder = "Send a message\u2026",
|
|
5113
|
+
animatedPlaceholder,
|
|
5007
5114
|
onSend,
|
|
5008
5115
|
onStop,
|
|
5009
5116
|
isStreaming,
|
|
@@ -5033,6 +5140,7 @@ var Composer = forwardRef(function Composer2(props, ref) {
|
|
|
5033
5140
|
attachmentOptions,
|
|
5034
5141
|
dir
|
|
5035
5142
|
} = props;
|
|
5143
|
+
const hasPlaceholder = props.placeholder != null;
|
|
5036
5144
|
const tokenStyle = useMemo(() => {
|
|
5037
5145
|
const derived = color ? deriveColorTokens(color) : null;
|
|
5038
5146
|
if (!derived && !tokens) return void 0;
|
|
@@ -5076,6 +5184,8 @@ var Composer = forwardRef(function Composer2(props, ref) {
|
|
|
5076
5184
|
ComposerCard,
|
|
5077
5185
|
{
|
|
5078
5186
|
placeholder,
|
|
5187
|
+
animatedPlaceholder,
|
|
5188
|
+
hasPlaceholder,
|
|
5079
5189
|
initialValue,
|
|
5080
5190
|
handleRef: ref,
|
|
5081
5191
|
onSend,
|
|
@@ -5113,6 +5223,8 @@ var RICH_NODES = [
|
|
|
5113
5223
|
var PLAIN_NODES = [MentionNode];
|
|
5114
5224
|
function ComposerCard({
|
|
5115
5225
|
placeholder,
|
|
5226
|
+
animatedPlaceholder,
|
|
5227
|
+
hasPlaceholder,
|
|
5116
5228
|
initialValue,
|
|
5117
5229
|
handleRef,
|
|
5118
5230
|
onSend,
|
|
@@ -5174,6 +5286,8 @@ function ComposerCard({
|
|
|
5174
5286
|
ComposerInner,
|
|
5175
5287
|
{
|
|
5176
5288
|
placeholder,
|
|
5289
|
+
animatedPlaceholder,
|
|
5290
|
+
hasPlaceholder,
|
|
5177
5291
|
mode,
|
|
5178
5292
|
variant,
|
|
5179
5293
|
multiline,
|
|
@@ -5194,6 +5308,8 @@ function ComposerCard({
|
|
|
5194
5308
|
}
|
|
5195
5309
|
function ComposerInner({
|
|
5196
5310
|
placeholder,
|
|
5311
|
+
animatedPlaceholder,
|
|
5312
|
+
hasPlaceholder,
|
|
5197
5313
|
mode,
|
|
5198
5314
|
variant,
|
|
5199
5315
|
multiline,
|
|
@@ -5324,6 +5440,13 @@ function ComposerInner({
|
|
|
5324
5440
|
});
|
|
5325
5441
|
}, [editor, registerRunPrompt, submit]);
|
|
5326
5442
|
const isCompact = variant === "compact";
|
|
5443
|
+
const animatedPhrases = Array.isArray(animatedPlaceholder) ? animatedPlaceholder : animatedPlaceholder?.phrases;
|
|
5444
|
+
const animatedLoop = Array.isArray(animatedPlaceholder) ? false : !!animatedPlaceholder?.loop;
|
|
5445
|
+
const animatedFrame = useAnimatedPlaceholder(animatedPhrases, !hasText, {
|
|
5446
|
+
loop: animatedLoop,
|
|
5447
|
+
settleTo: hasPlaceholder ? placeholder : void 0
|
|
5448
|
+
});
|
|
5449
|
+
const effectivePlaceholder = animatedFrame?.text ?? placeholder;
|
|
5327
5450
|
const mermaidActive = multiline && mode === "markdown" && !!features.mermaid;
|
|
5328
5451
|
const toolbarSlot = /* @__PURE__ */ jsx(Toolbar, { extras: toolbarExtras, variant, submit });
|
|
5329
5452
|
const sendButton = /* @__PURE__ */ jsx(
|
|
@@ -5347,7 +5470,8 @@ function ComposerInner({
|
|
|
5347
5470
|
/* @__PURE__ */ jsx(
|
|
5348
5471
|
EditorShell,
|
|
5349
5472
|
{
|
|
5350
|
-
placeholder,
|
|
5473
|
+
placeholder: effectivePlaceholder,
|
|
5474
|
+
animated: animatedFrame?.active ?? false,
|
|
5351
5475
|
mode,
|
|
5352
5476
|
variant,
|
|
5353
5477
|
multiline,
|