{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "composer",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "@coss/composer-editor",
    "@coss/scroll-area"
  ],
  "files": [
    {
      "path": "registry/default/ui/composer.tsx",
      "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { Popover as PopoverPrimitive } from \"@base-ui/react/popover\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport type {\n  ComponentProps,\n  MouseEvent,\n  PointerEvent,\n  ReactElement,\n  ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n  type ComposerEditorOptions,\n  ComposerEditorProvider,\n  type ComposerItemValue,\n  type ComposerTokenPart,\n  focusComposerInput,\n  getComposerText,\n  useComposerAnchorContext,\n  useComposerEditorContext,\n  useComposerInputLayout,\n  useComposerItem,\n  useComposerSuggestionsContext,\n} from \"@/registry/default/lib/composer-editor\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { ScrollArea } from \"@/registry/default/ui/scroll-area\";\n\ntype ComposerPopupProps = ComponentProps<typeof PopoverPrimitive.Popup> & {\n  anchor?: ComponentProps<typeof PopoverPrimitive.Positioner>[\"anchor\"];\n  side?: ComponentProps<typeof PopoverPrimitive.Positioner>[\"side\"];\n  align?: ComponentProps<typeof PopoverPrimitive.Positioner>[\"align\"];\n  sideOffset?: ComponentProps<typeof PopoverPrimitive.Positioner>[\"sideOffset\"];\n  alignOffset?: ComponentProps<\n    typeof PopoverPrimitive.Positioner\n  >[\"alignOffset\"];\n  positionerClassName?: string;\n  portalProps?: ComponentProps<typeof PopoverPrimitive.Portal>;\n};\n\ntype ComposerFormProps = useRender.ComponentProps<\"form\">;\ntype ComposerLayout = \"stacked\" | \"adaptive\";\ntype ComposerTokenVariant = \"default\" | \"chip\";\nconst composerTokenClassName =\n  \"wrap-break-word inline cursor-default align-baseline font-normal text-primary data-[variant=default]:font-medium data-[variant=chip]:box-decoration-clone data-[variant=chip]:rounded-md data-[variant=chip]:bg-accent data-[variant=chip]:px-1.5 data-[variant=chip]:py-px data-[variant=chip]:text-accent-foreground [&_[data-slot=composer-token-icon]:empty]:hidden [&_[data-slot=composer-token-icon]>svg]:size-3.5\";\n\nfunction ComposerFrame({\n  className,\n  layout = \"stacked\",\n  render,\n  ref,\n  name,\n  children,\n  ...props\n}: ComposerFormProps & { layout?: ComposerLayout }): ReactElement {\n  const editor = useComposerEditorContext();\n  const anchorRef = useComposerAnchorContext();\n  const defaultProps = {\n    \"data-slot\": \"composer\",\n    \"data-layout\": layout,\n    \"data-base-ui-swipe-ignore\": \"\",\n    onMouseDown: (event: MouseEvent<HTMLFormElement>): void => {\n      focusComposerInput(event, true);\n    },\n    onClick: (event: MouseEvent<HTMLFormElement>): void => {\n      focusComposerInput(event, false);\n    },\n    \"data-disabled\": editor?.disabled ? \"\" : undefined,\n    \"data-readonly\": editor?.readOnly ? \"\" : undefined,\n    children: (\n      <>\n        {children}\n        {name && (\n          <input\n            type=\"hidden\"\n            name={name}\n            value={getComposerText(editor?.value ?? [])}\n            disabled={editor?.disabled}\n          />\n        )}\n      </>\n    ),\n    className: cn(\n      \"relative flex min-h-0 min-w-0 cursor-text flex-col data-disabled:cursor-default rounded-(--composer-radius) border border-input bg-background not-dark:bg-clip-padding pb-[calc(var(--composer-padding)-1px)] text-foreground shadow-xs/5 ring-ring/24 transition-shadow [--composer-radius:var(--radius-xl)] [--composer-max-rows:4] [--composer-padding:--spacing(2)] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--composer-radius)-1px)] not-data-disabled:not-has-[[data-slot=composer-input]:focus-visible]:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] has-[[data-slot=composer-input]:focus-visible]:has-aria-invalid:border-destructive/64 has-[[data-slot=composer-input]:focus-visible]:has-aria-invalid:ring-destructive/16 has-[[data-slot=composer-input]:focus-visible]:border-ring has-aria-invalid:border-destructive/36 has-[[data-slot=composer-input]:focus-visible]:shadow-none has-aria-invalid:shadow-none has-[[data-slot=composer-input]:focus-visible]:ring-[3px] aria-invalid:border-destructive/36 aria-invalid:shadow-none data-disabled:opacity-64 data-disabled:shadow-none dark:bg-input/32 dark:aria-invalid:ring-destructive/24 dark:has-aria-invalid:ring-destructive/24 dark:not-data-disabled:not-has-[[data-slot=composer-input]:focus-visible]:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)] md:[--composer-max-rows:8]\",\n      \"data-[layout=adaptive]:data-multiline:grid-rows-[minmax(0,1fr)_auto] data-[layout=adaptive]:grid data-[layout=adaptive]:grid-cols-[auto_minmax(0,1fr)_auto] data-[layout=adaptive]:items-center data-[layout=adaptive]:not-data-multiline:px-[calc(--spacing(2)-1px)] data-[layout=adaptive]:not-data-multiline:pt-[calc(var(--composer-padding)-1px)]\",\n      className,\n    ),\n  };\n  return useRender({\n    defaultTagName: \"form\",\n    render,\n    ref: [anchorRef ?? null, ref ?? null],\n    props: mergeProps<\"form\">(defaultProps, editor?.formProps ?? {}, props),\n  });\n}\n\nexport type ComposerProps = Omit<\n  ComposerFormProps,\n  \"defaultValue\" | \"onSubmit\" | \"name\"\n> &\n  ComposerEditorOptions & { layout?: ComposerLayout };\n\nexport function Composer({\n  layout,\n  value,\n  defaultValue,\n  onValueChange,\n  onSubmit,\n  onQueryChange,\n  onAction,\n  submitOnEmptySuggestions,\n  triggers,\n  disabled,\n  readOnly,\n  submitDisabled,\n  allowEmpty,\n  submitOnEnter,\n  name,\n  getSuggestionsAnnouncement,\n  ...frameProps\n}: ComposerProps): ReactElement {\n  return (\n    <ComposerEditorProvider\n      value={value}\n      defaultValue={defaultValue}\n      onValueChange={onValueChange}\n      onSubmit={onSubmit}\n      onQueryChange={onQueryChange}\n      onAction={onAction}\n      submitOnEmptySuggestions={submitOnEmptySuggestions}\n      triggers={triggers}\n      disabled={disabled}\n      readOnly={readOnly}\n      submitDisabled={submitDisabled}\n      allowEmpty={allowEmpty}\n      submitOnEnter={submitOnEnter}\n      getSuggestionsAnnouncement={getSuggestionsAnnouncement}\n    >\n      <ComposerFrame {...frameProps} layout={layout} name={name} />\n    </ComposerEditorProvider>\n  );\n}\n\nexport function ComposerInput({\n  className,\n  placeholder,\n  renderToken,\n  render,\n  ref,\n  ...props\n}: useRender.ComponentProps<\"div\"> & {\n  placeholder?: string;\n  renderToken?: (token: ComposerTokenPart) => ReactNode;\n}): ReactElement {\n  const editor = useComposerEditorContext();\n  const suggestions = useComposerSuggestionsContext();\n  const tokenHosts = editor?.tokenHosts;\n  const { scrollRef, inputRef: layoutInputRef } = useComposerInputLayout();\n  const textarea = editor?.isTextarea ?? false;\n  const { ref: editorRef, ...editorProps } = editor?.inputProps ?? {};\n  const defaultProps = {\n    \"data-slot\": \"composer-input\",\n    ...(render === undefined\n      ? {\n          contentEditable: true,\n          role: \"textbox\" as const,\n          \"aria-multiline\": true as const,\n          suppressContentEditableWarning: true,\n        }\n      : {}),\n    \"data-empty\":\n      !textarea && editor?.isEmpty && !editor.isComposing ? \"\" : undefined,\n    ...(textarea ? { placeholder } : {}),\n    \"data-placeholder\": placeholder,\n    \"aria-placeholder\": editor ? placeholder : undefined,\n    className: cn(\n      \"field-sizing-content wrap-anywhere relative block min-h-[calc(var(--composer-min-rows,1)*1lh+--spacing(2))] w-full min-w-0 resize-none whitespace-pre-wrap border-0 bg-transparent in-[[data-layout=adaptive]:not([data-multiline])]:px-1 px-[calc(--spacing(3)-1px)] py-1 outline-none placeholder:text-muted-foreground/72 data-empty:before:pointer-events-none data-empty:before:absolute data-empty:before:text-muted-foreground/72 data-empty:before:content-[attr(data-placeholder)] md:in-data-[layout=adaptive]:min-h-[calc(var(--composer-min-rows,1)*1lh+--spacing(2))] md:min-h-[calc(var(--composer-min-rows,2)*1lh+--spacing(2))]\",\n      className,\n    ),\n  };\n  const inputBehaviorProps = textarea\n    ? {\n        contentEditable: undefined,\n        disabled: editor?.disabled,\n        readOnly: editor?.readOnly,\n      }\n    : (suggestions?.inputProps ?? {});\n  const input = useRender({\n    defaultTagName: \"div\",\n    render,\n    ref: [editorRef ?? null, layoutInputRef, ref ?? null],\n    props: mergeProps<\"div\">(\n      defaultProps,\n      editorProps,\n      inputBehaviorProps,\n      props,\n    ),\n  });\n  return (\n    <>\n      {/* The drawer reserves keyboard space; its nested input scroller needs no extra bottom padding. */}\n      <ScrollArea\n        ref={scrollRef}\n        className=\"in-[[data-layout=adaptive][data-multiline]]:col-span-full in-[[data-layout=adaptive][data-multiline]]:col-start-1 in-data-[layout=adaptive]:col-start-2 in-data-[layout=adaptive]:row-start-1 flex h-auto! min-h-0 min-w-0 flex-col in-[[data-layout=adaptive][data-multiline]]:self-stretch in-[[data-layout=adaptive]:not([data-multiline])]:rounded-none rounded-t-[inherit] text-base [--composer-scroll-padding:var(--composer-padding)] in-[[data-layout=adaptive]:not([data-multiline])]:[--composer-scroll-padding:0px] **:data-[slot=scroll-area-viewport]:h-auto! **:data-[slot=scroll-area-viewport]:max-h-[min(var(--composer-max-height,calc(var(--composer-max-rows,8)*var(--composer-line-height,1lh)+var(--composer-input-padding,--spacing(2))+var(--composer-scroll-padding))),50dvh)] **:data-[slot=scroll-area-viewport]:min-h-0 **:data-[slot=scroll-area-content]:pt-[max(0px,calc(var(--composer-scroll-padding)-1px))] **:data-[slot=scroll-area-viewport]:pb-0! md:text-sm\"\n        data-slot=\"composer-scroll-area\"\n        overscrollContain\n        scrollFade\n      >\n        {input}\n      </ScrollArea>\n      {tokenHosts?.map((host, index) => {\n        const node = host.parentElement;\n        if (!node) return null;\n        const token: ComposerTokenPart = {\n          type: node.dataset.composerToken === \"action\" ? \"action\" : \"mention\",\n          id: node.dataset.id ?? \"\",\n          label: node.dataset.label ?? \"\",\n          trigger: node.dataset.trigger ?? \"@\",\n          ...(node.dataset.kind ? { kind: node.dataset.kind } : {}),\n          ...(node.dataset.data !== undefined\n            ? { data: node.dataset.data }\n            : {}),\n          ...(node.dataset.text !== undefined\n            ? { text: node.dataset.text }\n            : {}),\n        };\n        return createPortal(\n          renderToken?.(token) ?? <ComposerToken token={token} />,\n          host,\n          String(index),\n        );\n      })}\n    </>\n  );\n}\n\nexport function ComposerToken({\n  variant = \"default\",\n  token,\n  icon,\n  children,\n  className,\n  ...props\n}: Omit<ComponentProps<\"span\">, \"contentEditable\"> & {\n  variant?: ComposerTokenVariant;\n  token: ComposerTokenPart;\n  icon?: ReactNode;\n}): ReactElement {\n  return (\n    <span\n      className={cn(composerTokenClassName, className)}\n      contentEditable={false}\n      data-kind={token.kind}\n      data-slot=\"composer-token\"\n      data-type={token.type}\n      data-variant={variant}\n      {...props}\n    >\n      {variant === \"chip\" && icon && (\n        <span\n          aria-hidden=\"true\"\n          className=\"inline whitespace-nowrap opacity-80 after:content-['\\2060\\00a0'] [&>svg]:inline-block [&>svg]:align-[-0.125em]\"\n          data-slot=\"composer-token-icon\"\n        >\n          {icon}\n        </span>\n      )}\n      <span data-slot=\"composer-token-label\">\n        {variant === \"default\" && token.trigger}\n        {children ?? token.label}\n      </span>\n    </span>\n  );\n}\n\nexport function ComposerPopup({\n  className,\n  anchor,\n  side = \"top\",\n  align = \"start\",\n  sideOffset = 4,\n  alignOffset,\n  positionerClassName,\n  portalProps,\n  ...props\n}: ComposerPopupProps): ReactElement {\n  const anchorRef = useComposerAnchorContext();\n  const editor = useComposerEditorContext();\n  return (\n    <PopoverPrimitive.Portal {...portalProps}>\n      <PopoverPrimitive.Positioner\n        anchor={anchor ?? anchorRef ?? undefined}\n        side={side}\n        align={align}\n        sideOffset={sideOffset}\n        alignOffset={alignOffset}\n        className={cn(\"z-50 w-(--anchor-width)\", positionerClassName)}\n        data-slot=\"composer-positioner\"\n      >\n        <PopoverPrimitive.Popup\n          className={cn(\n            \"relative w-(--anchor-width) max-w-(--available-width) rounded-lg border bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 outline-none transition-opacity before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:invisible data-starting-style:opacity-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]\",\n            className,\n          )}\n          data-slot=\"composer-popup\"\n          role={editor ? \"presentation\" : undefined}\n          initialFocus={editor ? false : undefined}\n          finalFocus={editor ? false : undefined}\n          {...props}\n        />\n      </PopoverPrimitive.Positioner>\n    </PopoverPrimitive.Portal>\n  );\n}\n\nexport function ComposerList({\n  className,\n  children,\n  render,\n  ref,\n  ...props\n}: Omit<useRender.ComponentProps<\"div\">, \"children\"> & {\n  children?:\n    | ReactNode\n    | ((item: ComposerItemValue, index: number) => ReactNode);\n}): ReactElement {\n  const suggestions = useComposerSuggestionsContext();\n  const defaultProps = {\n    \"data-slot\": \"composer-list\",\n    id: suggestions?.listId,\n    role: suggestions ? \"listbox\" : undefined,\n    \"aria-label\": suggestions ? \"Suggestions\" : undefined,\n    children:\n      typeof children === \"function\"\n        ? (suggestions?.items as readonly ComposerItemValue[] | undefined)?.map(\n            children,\n          )\n        : children,\n    className: cn(\n      \"not-empty:scroll-py-1 not-empty:p-1 in-data-has-overflow-y:pe-3\",\n      className,\n    ),\n  };\n  const list = useRender({\n    defaultTagName: \"div\",\n    render,\n    ref: [suggestions?.registerList ?? null, ref ?? null],\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n  return (\n    <ScrollArea\n      className=\"h-auto! **:data-[slot=scroll-area-viewport]:h-auto! **:data-[slot=scroll-area-viewport]:max-h-[min(var(--available-height),16rem)]\"\n      overscrollContain\n      scrollbarGutter\n      scrollFade\n    >\n      {list}\n    </ScrollArea>\n  );\n}\n\nexport function ComposerItem({\n  className,\n  value,\n  disabled: disabledProp,\n  render,\n  ref,\n  ...props\n}: Omit<\n  useRender.ComponentProps<\n    \"button\",\n    { highlighted: boolean; disabled: boolean }\n  >,\n  \"value\"\n> & {\n  value?: ComposerItemValue;\n}): ReactElement {\n  const disabled = Boolean(disabledProp || value?.disabled);\n  const item = useComposerItem(value, disabled);\n  const { highlighted } = item;\n  const defaultProps = {\n    ...item.props,\n    type: \"button\" as const,\n    \"data-slot\": \"composer-item\",\n    disabled,\n    className: cn(\n      \"flex min-h-8 w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1 text-start text-base outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm\",\n      className,\n    ),\n  };\n  return useRender({\n    defaultTagName: \"button\",\n    render,\n    ref: [item.ref, ref ?? null],\n    state: { highlighted, disabled },\n    props: mergeProps<\"button\">(defaultProps, props),\n  });\n}\n\nexport function ComposerStatus({\n  className,\n  render,\n  ref,\n  ...props\n}: useRender.ComponentProps<\"div\">): ReactElement {\n  const defaultProps = {\n    role: \"status\",\n    \"data-slot\": \"composer-status\",\n    className: cn(\n      \"px-3 py-2 font-medium text-muted-foreground text-xs empty:m-0 empty:p-0\",\n      className,\n    ),\n  };\n  return useRender({\n    defaultTagName: \"div\",\n    render,\n    ref,\n    props: mergeProps<\"div\">(defaultProps, props),\n  });\n}\n\nexport function ComposerActions({\n  className,\n  ...props\n}: ComponentProps<\"div\">): ReactElement {\n  return (\n    <div\n      className={cn(\n        \"in-data-[layout=adaptive]:col-span-full in-data-[layout=adaptive]:row-start-2 mx-[calc(--spacing(2)-1px)] flex in-[[data-layout=adaptive]:not([data-multiline])]:contents shrink-0 items-center justify-between gap-2\",\n        className,\n      )}\n      data-slot=\"composer-actions\"\n      {...props}\n    />\n  );\n}\n\nexport function ComposerActionGroup({\n  className,\n  align = \"start\",\n  ...props\n}: ComponentProps<\"div\"> & { align?: \"start\" | \"end\" }): ReactElement {\n  return (\n    <div\n      className={cn(\n        \"in-[[data-layout=adaptive]:not([data-multiline])]:row-start-1 flex min-w-0 flex-wrap items-center gap-1\",\n        align === \"start\"\n          ? \"in-[[data-layout=adaptive]:not([data-multiline])]:col-start-1 in-[[data-layout=adaptive]:not([data-multiline])]:me-2\"\n          : \"in-[[data-layout=adaptive]:not([data-multiline])]:col-start-3 in-[[data-layout=adaptive]:not([data-multiline])]:ms-2 ms-auto justify-end\",\n        className,\n      )}\n      data-align={align}\n      data-slot=\"composer-action-group\"\n      {...props}\n    />\n  );\n}\n\nexport function ComposerSubmit({\n  disabled,\n  className,\n  style,\n  render,\n  ref,\n  ...props\n}: Omit<ComponentProps<typeof ButtonPrimitive>, \"type\">): ReactElement {\n  const editor = useComposerEditorContext();\n  const defaultProps = {\n    \"data-slot\": \"composer-submit\",\n    type: \"button\" as const,\n    disabled: disabled ?? !editor?.canSubmit,\n    onPointerDown: (event: PointerEvent<HTMLButtonElement>): void => {\n      const input = editor?.rootRef.current?.querySelector(\n        '[data-slot=\"composer-input\"]',\n      );\n      if (\n        event.button === 0 &&\n        input === event.currentTarget.ownerDocument.activeElement\n      ) {\n        event.preventDefault();\n      }\n    },\n    onClick: (event: MouseEvent<HTMLButtonElement>): void => {\n      if (event.defaultPrevented || disabled) return;\n      event.preventDefault();\n      editor?.submit();\n    },\n  };\n  return (\n    <ButtonPrimitive\n      {...mergeProps<\"button\">(defaultProps, props)}\n      className={className}\n      style={style}\n      render={render}\n      ref={ref}\n    />\n  );\n}\n\nexport {\n  type ComposerChangeDetails,\n  type ComposerItemValue,\n  type ComposerPart,\n  type ComposerQuery,\n  type ComposerTokenPart,\n  type ComposerTrigger,\n  type ComposerValue,\n  getComposerText,\n  useComposerContext,\n  useComposerFilter,\n} from \"@/registry/default/lib/composer-editor\";\nexport type { ComposerPopupProps, ComposerTokenVariant };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}