{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "float-label",
  "title": "Float Label",
  "type": "registry:ui",
  "description": "Floating label wrapper for any input element. The label floats up when the input is focused or has a value. Wrap it around any input, select, or textarea. Supports required indicator and disabled state.",
  "categories": [
    "control",
    "form"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/float-label/float-label.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\n\nlet uid = 0\n\n/**\n * <uip-float-label> — the registry FloatLabel as a web component.\n */\nexport class UipFloatLabel extends LitElement {\n  static styles = [tailwind, css`:host { display: block; position: relative; }`]\n\n  static properties = {\n    label: { type: String },\n    required: { type: Boolean, reflect: true },\n    disabled: { type: Boolean, reflect: true },\n    isFocused: { state: true },\n    hasValue: { state: true },\n  }\n\n  label = ''\n  required = false\n  disabled = false\n  private isFocused = false\n  private hasValue = false\n  private controlId = `float-label-ctrl-${++uid}`\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'float-label')\n    this.addEventListener('focusin', this.handleFocusIn)\n    this.addEventListener('focusout', this.handleFocusOut)\n    this.addEventListener('input', this.handleInput)\n    this.addEventListener('change', this.handleInput)\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    this.removeEventListener('focusin', this.handleFocusIn)\n    this.removeEventListener('focusout', this.handleFocusOut)\n    this.removeEventListener('input', this.handleInput)\n    this.removeEventListener('change', this.handleInput)\n  }\n\n  firstUpdated() {\n    this.checkValue()\n  }\n\n  private findControl(): HTMLElement | null {\n    const slot = this.shadowRoot?.querySelector('slot')\n    const assigned = slot?.assignedElements({ flatten: true }) ?? []\n    for (const el of assigned) {\n      if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {\n        return el\n      }\n      if ('value' in el && el instanceof HTMLElement) return el\n      const inner = el.querySelector?.('input, textarea, select') as HTMLElement | null\n      if (inner) return inner\n    }\n    return (this.querySelector('input, textarea, select') as HTMLElement) ?? null\n  }\n\n  private checkValue() {\n    const el = this.findControl()\n    if (!el) {\n      this.hasValue = false\n      return\n    }\n    if ('value' in el) {\n      this.hasValue = Boolean((el as HTMLInputElement).value)\n    } else {\n      const input = el.querySelector?.('input, textarea, select') as HTMLInputElement | null\n      this.hasValue = Boolean(input?.value)\n    }\n  }\n\n  private handleFocusIn = () => {\n    this.isFocused = true\n    this.checkValue()\n  }\n\n  private handleFocusOut = () => {\n    this.isFocused = false\n    this.checkValue()\n  }\n\n  private handleInput = () => {\n    this.checkValue()\n  }\n\n  private handleSlotChange = () => {\n    this.checkValue()\n  }\n\n  private onLabelClick = () => {\n    const control = this.findControl()\n    control?.focus()\n  }\n\n  render() {\n    const isFloating = this.isFocused || this.hasValue\n    this.toggleAttribute('data-floating', isFloating)\n\n    const labelClasses = cn(\n      'text-muted-foreground pointer-events-none absolute left-3 z-10 bg-transparent px-1 text-sm transition-[color,background-color,top,translate,scale] duration-200 select-none origin-left',\n      !isFloating && 'top-1/2 -translate-y-1/2',\n      isFloating && 'top-0 -translate-y-1/2 scale-75 bg-background text-foreground',\n      this.isFocused && 'text-ring',\n      this.required && \"after:text-destructive after:ml-0.5 after:content-['*']\",\n    )\n\n    const wrapperClasses = cn('relative flex flex-col', this.disabled && 'opacity-50 cursor-not-allowed')\n\n    return html`\n      <div part=\"base\" class=${wrapperClasses}>\n        <label\n          part=\"label\"\n          for=${this.controlId}\n          class=${labelClasses}\n          @click=${this.onLabelClick}\n        >\n          ${this.label}\n        </label>\n        <slot @slotchange=${this.handleSlotChange}></slot>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-float-label') || customElements.define('uip-float-label', UipFloatLabel)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-float-label': UipFloatLabel\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/float-label/float-label.ts"
    }
  ],
  "dependencies": [
    "lit"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
