{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "textarea",
  "title": "Textarea",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/textarea/textarea.component.ts",
      "content": "import {\n  type AfterViewChecked,\n  type AfterViewInit,\n  ChangeDetectorRef,\n  Component,\n  ElementRef,\n  EventEmitter,\n  Input,\n  Output,\n  ViewChild,\n  booleanAttribute,\n  forwardRef,\n  inject,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { NG_VALUE_ACCESSOR, type ControlValueAccessor } from '@angular/forms'\nimport { cn } from '@/lib/utils'\nimport { uniqueId } from '@/ui/popper/popper'\nimport { UiLabelComponent } from '@/ui/label/label.component'\n\nexport type TextareaVariant = 'outlined' | 'filled' | 'solo' | 'underlined' | 'plain'\nexport type TextareaColor = 'primary' | 'secondary' | 'error' | 'warning' | 'info' | 'success'\nexport type TextareaDensity = 'compact' | 'comfortable' | 'default'\nexport type TextareaRounded = 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'pill' | 'circle' | 'full'\nexport type TextareaValidateOn = 'blur' | 'input' | 'submit' | 'lazy' | 'blurlazy' | 'inputlazy'\nexport type TextareaRule = (value: any) => true | string\nexport interface TextareaAutoSize {\n  minRows?: number\n  maxRows?: number\n}\nexport interface TextareaShowCount {\n  formatter?: (count: number, maxLength?: number) => string\n}\n\nfunction optionalBool(v: unknown): boolean | undefined {\n  return v === undefined || v === null ? undefined : booleanAttribute(v)\n}\n/** `autoSize` (bare attribute = true) or a { minRows, maxRows } object. */\nfunction autoSizeAttr(v: unknown): boolean | TextareaAutoSize | undefined {\n  return v === undefined || v === null\n    ? undefined\n    : typeof v === 'object'\n      ? (v as TextareaAutoSize)\n      : booleanAttribute(v)\n}\nfunction showCountAttr(v: unknown): boolean | TextareaShowCount | undefined {\n  return v === undefined || v === null\n    ? undefined\n    : typeof v === 'object'\n      ? (v as TextareaShowCount)\n      : booleanAttribute(v)\n}\n\n/**\n * Angular port of the React Textarea (Vuetify-style variants + Ant Design autoSize /\n * showCount / allowClear). `<ui-textarea>` is React's outer `relative space-y-2` div:\n * label, the variant control wrapper around the native <textarea>, then the\n * description block (hint / error / success / legacy counter), then projected content.\n *\n * Model is React's `value` / `defaultValue` / `valueChange` (React `onValueChange`):\n * a bound `value` is controlled, otherwise the textarea keeps its own state.\n * NG_VALUE_ACCESSOR is provided for formControl / ngModel.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-textarea',\n  standalone: true,\n  exportAs: 'uiTextarea',\n  imports: [UiLabelComponent],\n  providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => UiTextareaComponent), multi: true }],\n  host: {\n    '[class]': 'hostClass',\n    // The id / placeholder belong to the native <textarea>.\n    '[attr.id]': 'null',\n    '[attr.placeholder]': 'null',\n  },\n  template: `\n    @if (label) {\n      <label ui-label [for]=\"textareaId\" [class]=\"labelClass\"\n        >{{ label }}\n        @if (required) {\n          <span class=\"text-destructive ml-0.5\">*</span>\n        }\n      </label>\n    }\n    <div [class]=\"controlClass\" [style.background-color]=\"bgColor || null\">\n      @if (prefix) {\n        <span [class]=\"prefixClass\">{{ prefix }}</span>\n      }\n      <textarea\n        #textareaEl\n        [attr.id]=\"textareaId\"\n        [value]=\"currentValue\"\n        [attr.placeholder]=\"placeholder ?? null\"\n        [disabled]=\"disabled\"\n        [readOnly]=\"readOnly\"\n        [required]=\"required\"\n        [attr.name]=\"name ?? null\"\n        [attr.autocomplete]=\"autoComplete ?? null\"\n        [autofocus]=\"autoFocus\"\n        [attr.spellcheck]=\"spellCheck === undefined ? null : spellCheck\"\n        [attr.maxlength]=\"maxLength ?? null\"\n        [rows]=\"computedRows\"\n        [attr.aria-describedby]=\"hasError || hasSuccess || hint ? descriptionId : null\"\n        [attr.aria-invalid]=\"hasError ? 'true' : null\"\n        [class]=\"textareaClass\"\n        (input)=\"handleInput($event)\"\n        (focus)=\"handleFocus()\"\n        (blur)=\"handleBlur()\"\n        (keydown)=\"keyDown.emit($event)\"\n        (keyup)=\"keyUp.emit($event)\"\n      ></textarea>\n      @if (suffix) {\n        <span [class]=\"suffixClass\">{{ suffix }}</span>\n      }\n      @if (showClear) {\n        <button type=\"button\" tabindex=\"-1\" aria-label=\"Clear\" [class]=\"clearClass\" (click)=\"handleClear()\">\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            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            class=\"lucide lucide-x size-4\"\n            aria-hidden=\"true\"\n          >\n            <path d=\"M18 6 6 18\" />\n            <path d=\"m6 6 12 12\" />\n          </svg>\n        </button>\n      }\n      @if (loading) {\n        <div class=\"absolute top-3 right-3 flex items-center justify-center\">\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            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            class=\"lucide lucide-loader text-muted-foreground size-4 animate-spin\"\n            aria-hidden=\"true\"\n          >\n            <path d=\"M12 2v4\" />\n            <path d=\"m16.2 7.8 2.9-2.9\" />\n            <path d=\"M18 12h4\" />\n            <path d=\"m16.2 16.2 2.9 2.9\" />\n            <path d=\"M12 18v4\" />\n            <path d=\"m4.9 19.1 2.9-2.9\" />\n            <path d=\"M2 12h4\" />\n            <path d=\"m4.9 4.9 2.9 2.9\" />\n          </svg>\n        </div>\n      }\n      @if (hasSuccess && !loading) {\n        <div class=\"text-success absolute top-3 right-3 flex items-center justify-center\">\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            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            class=\"lucide lucide-check size-4\"\n            aria-hidden=\"true\"\n          >\n            <path d=\"M20 6 9 17l-5-5\" />\n          </svg>\n        </div>\n      }\n      @if (hasError && !loading) {\n        <div class=\"text-destructive absolute top-3 right-3 flex items-center justify-center\">\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            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            class=\"lucide lucide-circle-alert size-4\"\n            aria-hidden=\"true\"\n          >\n            <circle cx=\"12\" cy=\"12\" r=\"10\" />\n            <line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" />\n            <line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" />\n          </svg>\n        </div>\n      }\n      @if (showCountEnabled) {\n        <div [class]=\"countClass\">{{ countText }}</div>\n      }\n    </div>\n    <div [attr.id]=\"descriptionId\" class=\"mt-1.5\">\n      @if (hint && (!hasError || persistentHint) && !focused()) {\n        <p [class]=\"hintClass\">{{ hint }}</p>\n      }\n      @for (msg of computedErrorMessages; track $index) {\n        <p class=\"text-destructive flex items-center gap-1 text-sm\" role=\"alert\">\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            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            class=\"lucide lucide-circle-alert size-3 shrink-0\"\n            aria-hidden=\"true\"\n          >\n            <circle cx=\"12\" cy=\"12\" r=\"10\" />\n            <line x1=\"12\" x2=\"12\" y1=\"8\" y2=\"12\" />\n            <line x1=\"12\" x2=\"12.01\" y1=\"16\" y2=\"16\" /></svg\n          >{{ msg }}\n        </p>\n      }\n      @for (msg of computedSuccessMessages; track $index) {\n        <p class=\"text-success flex items-center gap-1 text-sm\">\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            stroke-width=\"2\"\n            stroke-linecap=\"round\"\n            stroke-linejoin=\"round\"\n            class=\"lucide lucide-check size-3 shrink-0\"\n            aria-hidden=\"true\"\n          >\n            <path d=\"M20 6 9 17l-5-5\" /></svg\n          >{{ msg }}\n        </p>\n      }\n      @if (computedCounter !== null) {\n        <div [class]=\"counterClass\">{{ currentLength }} / {{ computedCounter }}</div>\n      }\n    </div>\n    <ng-content />\n  `,\n})\nexport class UiTextareaComponent implements ControlValueAccessor, AfterViewInit, AfterViewChecked {\n  // Zoneless-safe: form writes happen outside template events, so schedule a repaint.\n  private readonly cdr = inject(ChangeDetectorRef)\n  @ViewChild('textareaEl', { static: true }) textareaEl!: ElementRef<HTMLTextAreaElement>\n\n  private readonly autoId = uniqueId('textarea')\n  private readonly _valueProp = signal<string | number | undefined>(undefined)\n  private readonly _internal = signal<string | number | null>(null)\n  private readonly _errors = signal<string[]>([])\n  readonly focused = signal(false)\n\n  // Core\n  /** Controlled value (React `value`). Reading it returns the resolved current value. */\n  @Input()\n  set value(v: string | number | null | undefined) {\n    this._valueProp.set(v === undefined ? undefined : (v ?? ''))\n  }\n  get value(): string | number {\n    return this.currentValue\n  }\n  @Input() defaultValue?: string | number\n  /** React `onValueChange`. */\n  @Output() valueChange = new EventEmitter<string>()\n  @Input() label?: string\n  @Input() placeholder?: string\n  @Input() hint?: string\n  @Input() error?: string\n  @Input() success?: string\n  @Input() messages?: string[]\n  @Input({ transform: booleanAttribute }) disabled = false\n  @Input({ transform: booleanAttribute }) readOnly = false\n  /** Lower-case HTML spelling of `readOnly`. */\n  @Input({ alias: 'readonly', transform: booleanAttribute }) set readonlyAlias(v: boolean) {\n    this.readOnly = v\n  }\n  @Input({ transform: booleanAttribute }) required = false\n  @Input({ transform: booleanAttribute }) autoFocus = false\n  @Input() name?: string\n  @Input() id?: string\n\n  // Variants\n  @Input() variant: TextareaVariant = 'outlined'\n  @Input() color?: TextareaColor\n  @Input() density: TextareaDensity = 'default'\n  @Input() rounded: TextareaRounded = 'none'\n\n  // Sizing\n  @Input({ transform: autoSizeAttr }) autoSize?: boolean | TextareaAutoSize\n  @Input({ transform: booleanAttribute }) autoGrow = false\n  @Input({ transform: booleanAttribute }) noResize = false\n  @Input({ transform: booleanAttribute }) autoResize = false\n  @Input() rows: number | string = 3\n  @Input() rowHeight = 24\n\n  // Adornments / counters\n  @Input() prefix?: string\n  @Input() suffix?: string\n  @Input() counter?: boolean | number\n  @Input({ transform: showCountAttr }) showCount?: boolean | TextareaShowCount\n  @Input() maxLength?: number\n  /** Lower-case HTML spelling of `maxLength`. */\n  @Input('maxlength') set maxlengthAlias(v: number | string | undefined) {\n    this.maxLength = v == null || v === '' ? undefined : Number(v)\n  }\n  @Input({ transform: booleanAttribute }) allowClear = false\n\n  // Validation\n  @Input() rules?: TextareaRule[]\n  @Input() errorMessages?: string | string[]\n  @Input() successMessages?: string | string[]\n  @Input() validateOn?: TextareaValidateOn\n\n  // States\n  @Input({ transform: booleanAttribute }) loading = false\n  @Input({ transform: booleanAttribute }) persistentHint = false\n  @Input({ transform: optionalBool }) persistentError?: boolean\n  @Input({ transform: optionalBool }) persistentPlaceholder?: boolean\n  @Input({ transform: optionalBool }) persistentPrefix?: boolean\n  @Input({ transform: optionalBool }) persistentSuffix?: boolean\n\n  // Misc\n  @Input('class') className?: string\n  @Input() inputClassName?: string\n  @Input() labelClassName?: string\n  @Input() hintClassName?: string\n  @Input() bgColor?: string\n  @Input({ transform: optionalBool }) flat?: boolean\n  @Input({ transform: optionalBool }) bordered?: boolean\n  @Input({ transform: optionalBool }) spellCheck?: boolean\n  @Input() autoComplete?: string\n  @Input() direction?: 'ltr' | 'rtl'\n\n  // Events\n  @Output() clear = new EventEmitter<void>()\n  @Output('focus') focusEvent = new EventEmitter<void>()\n  @Output('blur') blurEvent = new EventEmitter<void>()\n  @Output() keyDown = new EventEmitter<KeyboardEvent>()\n  @Output() keyUp = new EventEmitter<KeyboardEvent>()\n\n  private minHeightPx = 0\n  private maxHeightPx = Infinity\n  private lastResizedValue: string | null = null\n\n  private onChange: (v: string) => void = () => {}\n  private onTouched: () => void = () => {}\n\n  get textareaId(): string {\n    return this.id ?? this.autoId\n  }\n  get descriptionId(): string {\n    return `${this.textareaId}-description`\n  }\n  get isControlled(): boolean {\n    return this._valueProp() !== undefined\n  }\n  get currentValue(): string | number {\n    return this._valueProp() ?? this._internal() ?? this.defaultValue ?? ''\n  }\n  get currentLength(): number {\n    return String(this.currentValue ?? '').length\n  }\n  get autoSizeEnabled(): boolean {\n    return this.autoSize !== undefined\n  }\n  get anyAutoResize(): boolean {\n    return this.autoSizeEnabled || this.autoResize || this.autoGrow\n  }\n  private get autoSizeConfig(): TextareaAutoSize {\n    return typeof this.autoSize === 'object' ? this.autoSize : {}\n  }\n  get rowsNum(): number {\n    return Number(this.rows) || 3\n  }\n  /** Legacy autoGrow: grow `rows` to fit the content. */\n  get computedRows(): number {\n    if (this.autoSizeEnabled || this.autoResize) return this.rowsNum\n    if (!this.autoGrow) return this.rowsNum\n    const el = this.textareaEl?.nativeElement\n    if (!el) return this.rowsNum\n    const lineHeight = this.rowHeight\n    const newRows = Math.ceil((el.scrollHeight - lineHeight) / lineHeight) + 1\n    return Math.max(this.rowsNum, newRows)\n  }\n\n  get computedErrorMessages(): string[] {\n    if (this.errorMessages) return Array.isArray(this.errorMessages) ? this.errorMessages : [this.errorMessages]\n    if (this.error) return [this.error]\n    return this._errors()\n  }\n  get computedSuccessMessages(): string[] {\n    if (this.successMessages) return Array.isArray(this.successMessages) ? this.successMessages : [this.successMessages]\n    if (this.success) return [this.success]\n    return []\n  }\n  get hasError(): boolean {\n    return this.computedErrorMessages.length > 0\n  }\n  get hasSuccess(): boolean {\n    return this.computedSuccessMessages.length > 0\n  }\n  get computedCounter(): number | null {\n    if (typeof this.counter === 'number') return this.counter\n    if (this.counter) return this.maxLength ?? 100\n    return null\n  }\n  get showCountEnabled(): boolean {\n    return this.showCount !== undefined && this.showCount !== false\n  }\n  get countText(): string {\n    const formatter = typeof this.showCount === 'object' ? this.showCount.formatter : undefined\n    if (formatter) return formatter(this.currentLength, this.maxLength)\n    if (this.maxLength !== undefined) return `${this.currentLength} / ${this.maxLength}`\n    return `${this.currentLength}`\n  }\n  get showClear(): boolean {\n    return this.allowClear && !this.disabled && !this.readOnly && this.currentLength > 0\n  }\n\n  get hostClass(): string {\n    return cn('block relative space-y-2', this.className)\n  }\n\n  get labelClass(): string {\n    return cn(\n      'text-foreground text-sm font-medium',\n      this.labelClassName,\n      this.focused() && 'text-primary',\n      this.hasError && 'text-destructive',\n    )\n  }\n\n  private get variantClasses(): string {\n    const base = 'w-full transition-colors duration-200'\n    const focused = this.focused()\n    const hasError = this.hasError\n    switch (this.variant) {\n      case 'outlined':\n        return cn(\n          base,\n          'border-2 rounded-lg',\n          focused ? 'border-primary ring-2 ring-primary/20' : 'border-input',\n          hasError && 'border-destructive focus:border-destructive focus:ring-destructive/20',\n        )\n      case 'filled':\n        return cn(\n          base,\n          'border-b-2 bg-muted/50 rounded-t-lg',\n          focused ? 'border-primary bg-muted' : 'border-transparent',\n          hasError && 'border-destructive',\n        )\n      case 'solo':\n        return cn(\n          base,\n          'rounded-lg shadow-sm',\n          focused ? 'shadow-md' : 'shadow-sm',\n          'bg-card border border-transparent',\n        )\n      case 'underlined':\n        return cn(\n          base,\n          'border-b-2 rounded-none border-x-0 border-t-0 px-0',\n          focused ? 'border-primary' : 'border-muted-foreground/30',\n          hasError && 'border-destructive',\n        )\n      case 'plain':\n        return cn(base, 'border-0 bg-transparent')\n      default:\n        return base\n    }\n  }\n\n  private get densityClasses(): string {\n    switch (this.density) {\n      case 'compact':\n        return 'text-sm min-h-[32px]'\n      case 'comfortable':\n        return 'text-base min-h-[40px]'\n      default:\n        return 'text-base min-h-[48px]'\n    }\n  }\n\n  private get resizeClasses(): string {\n    if (this.noResize) return 'resize-none'\n    if (this.anyAutoResize) return 'resize-none'\n    return 'resize-y'\n  }\n\n  get controlClass(): string {\n    return cn(\n      'relative flex items-center',\n      this.variantClasses,\n      this.densityClasses,\n      this.disabled && 'pointer-events-none opacity-50',\n      this.readOnly && !this.disabled && 'cursor-default',\n      this.rounded !== 'none' && `rounded-${this.rounded}`,\n    )\n  }\n\n  get prefixClass(): string {\n    return cn(\n      'text-muted-foreground pointer-events-none absolute top-3 left-3 text-sm',\n      !this.persistentPrefix && !this.focused() && 'opacity-50',\n    )\n  }\n\n  get suffixClass(): string {\n    return cn(\n      'text-muted-foreground pointer-events-none absolute top-3 right-3 text-sm',\n      !this.persistentSuffix && !this.focused() && 'opacity-50',\n    )\n  }\n\n  get textareaClass(): string {\n    return cn(\n      'w-full flex-1 resize-y bg-transparent outline-none',\n      this.densityClasses,\n      this.resizeClasses,\n      this.prefix ? 'pl-16' : 'pl-3',\n      this.suffix ? 'pr-16' : this.showClear ? 'pr-10' : 'pr-3',\n      this.showCountEnabled && 'pb-6',\n      'py-2',\n      this.inputClassName,\n    )\n  }\n\n  get clearClass(): string {\n    return cn(\n      'text-muted-foreground hover:text-foreground focus-visible:ring-ring absolute top-3 flex items-center justify-center rounded-sm transition-colors focus-visible:ring-2 focus-visible:outline-none',\n      this.suffix ? 'right-10' : 'right-3',\n    )\n  }\n\n  get countClass(): string {\n    return cn(\n      'text-muted-foreground pointer-events-none absolute right-3 bottom-1.5 text-xs',\n      this.maxLength !== undefined && this.currentLength > this.maxLength && 'text-destructive',\n    )\n  }\n\n  get hintClass(): string {\n    return cn('text-muted-foreground text-sm', this.hintClassName)\n  }\n\n  get counterClass(): string {\n    return cn(\n      'text-muted-foreground mt-1 text-right text-xs',\n      this.computedCounter !== null && this.currentLength > this.computedCounter && 'text-destructive',\n    )\n  }\n\n  ngAfterViewInit(): void {\n    requestAnimationFrame(() => {\n      this.measureHeights()\n      if (this.anyAutoResize) this.autoResizeFn()\n    })\n  }\n\n  ngAfterViewChecked(): void {\n    const el = this.textareaEl?.nativeElement\n    if (!el) return\n    const current = String(this.currentValue ?? '')\n    // A controlled parent that did not take the edit wins (React semantics).\n    if (this.isControlled && el.value !== current) el.value = current\n    // Programmatic value changes re-fit the height, like React's effect on the value.\n    if (this.anyAutoResize && this.lastResizedValue !== current) {\n      this.lastResizedValue = current\n      requestAnimationFrame(() => this.autoResizeFn())\n    }\n  }\n\n  /** Fit the height to the content, clamped to the measured autoSize min / max rows. */\n  autoResizeFn(): void {\n    const el = this.textareaEl?.nativeElement\n    if (!el || !this.anyAutoResize) return\n    el.style.height = 'auto'\n    let newHeight = el.scrollHeight\n    if (this.minHeightPx && newHeight < this.minHeightPx) newHeight = this.minHeightPx\n    if (newHeight > this.maxHeightPx) {\n      newHeight = this.maxHeightPx\n      el.style.overflowY = 'auto'\n    } else {\n      el.style.overflowY = 'hidden'\n    }\n    el.style.height = `${newHeight}px`\n  }\n\n  private measureHeights(): void {\n    const el = this.textareaEl?.nativeElement\n    if (!el || !this.autoSizeEnabled) return\n    const originalValue = el.value\n    const originalRows = el.rows\n    const originalOverflow = el.style.overflowY\n    el.value = ''\n    el.style.overflowY = 'hidden'\n    const { minRows, maxRows } = this.autoSizeConfig\n    if (minRows) {\n      el.rows = minRows\n      this.minHeightPx = el.scrollHeight\n    } else {\n      this.minHeightPx = 0\n    }\n    if (maxRows) {\n      el.rows = maxRows\n      this.maxHeightPx = el.scrollHeight\n    } else {\n      this.maxHeightPx = Infinity\n    }\n    el.value = originalValue\n    el.rows = originalRows\n    el.style.overflowY = originalOverflow\n    this.autoResizeFn()\n  }\n\n  private runRules(v: unknown): string[] {\n    const out: string[] = []\n    for (const rule of this.rules ?? []) {\n      const result = rule(v)\n      if (result !== true) out.push(result as string)\n    }\n    return out\n  }\n\n  /** Run `rules` against the current value (React `validate`); true when all pass. */\n  validate(): boolean {\n    const next = this.runRules(this.currentValue)\n    this._errors.set(next)\n    return next.length === 0\n  }\n\n  private setValue(next: string): void {\n    if (!this.isControlled) this._internal.set(next)\n    this.onChange(next)\n    this.valueChange.emit(next)\n  }\n\n  handleInput(event: Event): void {\n    const next = (event.target as HTMLTextAreaElement).value\n    this.setValue(next)\n    if (this.anyAutoResize) this.autoResizeFn()\n    if (this.validateOn === 'input' || this.validateOn === 'inputlazy') this._errors.set(this.runRules(next))\n  }\n\n  handleClear(): void {\n    this.setValue('')\n    this.clear.emit()\n    requestAnimationFrame(() => {\n      this.autoResizeFn()\n      this.textareaEl?.nativeElement.focus()\n    })\n  }\n\n  handleFocus(): void {\n    this.focused.set(true)\n    this.focusEvent.emit()\n  }\n\n  handleBlur(): void {\n    this.focused.set(false)\n    if (this.validateOn === 'blur' || this.validateOn === 'blurlazy') this.validate()\n    this.onTouched()\n    this.blurEvent.emit()\n  }\n\n  writeValue(v: string | number | null): void {\n    this._internal.set(v ?? '')\n    this.cdr.markForCheck()\n  }\n\n  registerOnChange(fn: (v: string) => void): void {\n    this.onChange = fn\n  }\n\n  registerOnTouched(fn: () => void): void {\n    this.onTouched = fn\n  }\n\n  setDisabledState(isDisabled: boolean): void {\n    this.disabled = isDisabled\n    this.cdr.markForCheck()\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/textarea/textarea.component.ts"
    },
    {
      "path": "packages/registry-angular/components/textarea/index.ts",
      "content": "export {\n  UiTextareaComponent,\n  type TextareaVariant,\n  type TextareaColor,\n  type TextareaDensity,\n  type TextareaRounded,\n  type TextareaValidateOn,\n  type TextareaRule,\n  type TextareaAutoSize,\n  type TextareaShowCount,\n} from './textarea.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/textarea/index.ts"
    }
  ],
  "dependencies": [
    "@angular/forms"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/popper.json",
    "https://uipkge.dev/r/angular/label.json"
  ],
  "description": "Multi-line text input with a linked label, five variants (outlined / filled / solo / underlined / plain), density, autoSize with min / max rows, showCount (with formatter), allowClear, prefix / suffix, rule validation, hint / error / success messages and a loading spinner.",
  "categories": [
    "form"
  ]
}