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 CHANGED
@@ -802,6 +802,7 @@ function useComposerContext() {
802
802
  }
803
803
  function EditorShell({
804
804
  placeholder,
805
+ animated,
805
806
  mode,
806
807
  variant,
807
808
  multiline,
@@ -820,7 +821,11 @@ function EditorShell({
820
821
  const editor = slotProps("editor", editorClass, classNames, sx);
821
822
  const editorResolved = resolveSx(sx?.editor);
822
823
  const placeholderBase = mirrorEditorPadding(editorResolved);
823
- const placeholderClass = isCompact ? "composer-placeholder composer-placeholder--compact" : multiline ? "composer-placeholder composer-placeholder--multiline" : "composer-placeholder composer-placeholder--inline";
824
+ const placeholderClass = cn(
825
+ isCompact ? "composer-placeholder composer-placeholder--compact" : multiline ? "composer-placeholder composer-placeholder--multiline" : "composer-placeholder composer-placeholder--inline",
826
+ // Adds the blinking caret after the typewriter text.
827
+ animated && "composer-placeholder--animated"
828
+ );
824
829
  const placeholderProps = slotProps(
825
830
  "placeholder",
826
831
  placeholderClass,
@@ -4929,6 +4934,107 @@ function useComposerHandle(ref, onSubmit) {
4929
4934
  };
4930
4935
  }, [editor, ref, onSubmit, addFiles]);
4931
4936
  }
4937
+ var DEFAULT_TIMING = {
4938
+ typeSpeed: 55,
4939
+ deleteSpeed: 28,
4940
+ holdDuration: 1800,
4941
+ pauseDuration: 450
4942
+ };
4943
+ function useAnimatedPlaceholder(phrases, enabled, options) {
4944
+ const [state, setState] = react.useState({
4945
+ text: "",
4946
+ active: true
4947
+ });
4948
+ const optsRef = react.useRef({});
4949
+ optsRef.current = options ?? {};
4950
+ const active = enabled && Array.isArray(phrases) && phrases.length > 0;
4951
+ const key = active ? phrases.join("\u241F") : "";
4952
+ const loop = !!options?.loop;
4953
+ const phrasesRef = react.useRef(phrases);
4954
+ phrasesRef.current = phrases;
4955
+ react.useEffect(() => {
4956
+ if (!active) {
4957
+ setState({ text: "", active: true });
4958
+ return;
4959
+ }
4960
+ const list = phrasesRef.current;
4961
+ const lastIdx = list.length - 1;
4962
+ const reduceMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
4963
+ if (reduceMotion) {
4964
+ setState({ text: list[0], active: false });
4965
+ return;
4966
+ }
4967
+ let timer;
4968
+ let phraseIdx = 0;
4969
+ let charIdx = 0;
4970
+ let phase = "typing";
4971
+ const tick = () => {
4972
+ const timing = { ...DEFAULT_TIMING, ...optsRef.current };
4973
+ const { typeSpeed, deleteSpeed, holdDuration, pauseDuration } = timing;
4974
+ const settleTo = optsRef.current.settleTo;
4975
+ const current = list[phraseIdx];
4976
+ const isLast = phraseIdx === lastIdx;
4977
+ switch (phase) {
4978
+ case "typing": {
4979
+ charIdx += 1;
4980
+ setState({ text: current.slice(0, charIdx), active: true });
4981
+ if (charIdx >= current.length) {
4982
+ phase = "holding";
4983
+ timer = setTimeout(tick, holdDuration);
4984
+ } else {
4985
+ timer = setTimeout(tick, typeSpeed);
4986
+ }
4987
+ break;
4988
+ }
4989
+ case "holding": {
4990
+ if (!loop && isLast) {
4991
+ if (settleTo !== void 0) {
4992
+ phase = "settling";
4993
+ timer = setTimeout(tick, deleteSpeed);
4994
+ } else {
4995
+ setState({ text: current, active: false });
4996
+ }
4997
+ } else {
4998
+ phase = "deleting";
4999
+ timer = setTimeout(tick, deleteSpeed);
5000
+ }
5001
+ break;
5002
+ }
5003
+ case "deleting": {
5004
+ charIdx -= 1;
5005
+ setState({ text: current.slice(0, Math.max(0, charIdx)), active: true });
5006
+ if (charIdx <= 0) {
5007
+ phase = "pausing";
5008
+ timer = setTimeout(tick, pauseDuration);
5009
+ } else {
5010
+ timer = setTimeout(tick, deleteSpeed);
5011
+ }
5012
+ break;
5013
+ }
5014
+ case "pausing": {
5015
+ phraseIdx = phraseIdx >= lastIdx ? 0 : phraseIdx + 1;
5016
+ charIdx = 0;
5017
+ phase = "typing";
5018
+ timer = setTimeout(tick, typeSpeed);
5019
+ break;
5020
+ }
5021
+ case "settling": {
5022
+ charIdx -= 1;
5023
+ if (charIdx <= 0) {
5024
+ setState({ text: settleTo ?? "", active: false });
5025
+ } else {
5026
+ setState({ text: current.slice(0, charIdx), active: true });
5027
+ timer = setTimeout(tick, deleteSpeed);
5028
+ }
5029
+ break;
5030
+ }
5031
+ }
5032
+ };
5033
+ timer = setTimeout(tick, DEFAULT_TIMING.typeSpeed);
5034
+ return () => clearTimeout(timer);
5035
+ }, [active, key, loop]);
5036
+ return active ? state : null;
5037
+ }
4932
5038
 
4933
5039
  // src/internal/shortcut.ts
4934
5040
  var MODIFIERS = /* @__PURE__ */ new Set([
@@ -5006,6 +5112,7 @@ function matchesShortcut(parsed, event) {
5006
5112
  var Composer = react.forwardRef(function Composer2(props, ref) {
5007
5113
  const {
5008
5114
  placeholder = "Send a message\u2026",
5115
+ animatedPlaceholder,
5009
5116
  onSend,
5010
5117
  onStop,
5011
5118
  isStreaming,
@@ -5035,6 +5142,7 @@ var Composer = react.forwardRef(function Composer2(props, ref) {
5035
5142
  attachmentOptions,
5036
5143
  dir
5037
5144
  } = props;
5145
+ const hasPlaceholder = props.placeholder != null;
5038
5146
  const tokenStyle = react.useMemo(() => {
5039
5147
  const derived = color ? deriveColorTokens(color) : null;
5040
5148
  if (!derived && !tokens) return void 0;
@@ -5078,6 +5186,8 @@ var Composer = react.forwardRef(function Composer2(props, ref) {
5078
5186
  ComposerCard,
5079
5187
  {
5080
5188
  placeholder,
5189
+ animatedPlaceholder,
5190
+ hasPlaceholder,
5081
5191
  initialValue,
5082
5192
  handleRef: ref,
5083
5193
  onSend,
@@ -5115,6 +5225,8 @@ var RICH_NODES = [
5115
5225
  var PLAIN_NODES = [MentionNode];
5116
5226
  function ComposerCard({
5117
5227
  placeholder,
5228
+ animatedPlaceholder,
5229
+ hasPlaceholder,
5118
5230
  initialValue,
5119
5231
  handleRef,
5120
5232
  onSend,
@@ -5176,6 +5288,8 @@ function ComposerCard({
5176
5288
  ComposerInner,
5177
5289
  {
5178
5290
  placeholder,
5291
+ animatedPlaceholder,
5292
+ hasPlaceholder,
5179
5293
  mode,
5180
5294
  variant,
5181
5295
  multiline,
@@ -5196,6 +5310,8 @@ function ComposerCard({
5196
5310
  }
5197
5311
  function ComposerInner({
5198
5312
  placeholder,
5313
+ animatedPlaceholder,
5314
+ hasPlaceholder,
5199
5315
  mode,
5200
5316
  variant,
5201
5317
  multiline,
@@ -5326,6 +5442,13 @@ function ComposerInner({
5326
5442
  });
5327
5443
  }, [editor, registerRunPrompt, submit]);
5328
5444
  const isCompact = variant === "compact";
5445
+ const animatedPhrases = Array.isArray(animatedPlaceholder) ? animatedPlaceholder : animatedPlaceholder?.phrases;
5446
+ const animatedLoop = Array.isArray(animatedPlaceholder) ? false : !!animatedPlaceholder?.loop;
5447
+ const animatedFrame = useAnimatedPlaceholder(animatedPhrases, !hasText, {
5448
+ loop: animatedLoop,
5449
+ settleTo: hasPlaceholder ? placeholder : void 0
5450
+ });
5451
+ const effectivePlaceholder = animatedFrame?.text ?? placeholder;
5329
5452
  const mermaidActive = multiline && mode === "markdown" && !!features.mermaid;
5330
5453
  const toolbarSlot = /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { extras: toolbarExtras, variant, submit });
5331
5454
  const sendButton = /* @__PURE__ */ jsxRuntime.jsx(
@@ -5349,7 +5472,8 @@ function ComposerInner({
5349
5472
  /* @__PURE__ */ jsxRuntime.jsx(
5350
5473
  EditorShell,
5351
5474
  {
5352
- placeholder,
5475
+ placeholder: effectivePlaceholder,
5476
+ animated: animatedFrame?.active ?? false,
5353
5477
  mode,
5354
5478
  variant,
5355
5479
  multiline,