{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "resizable",
  "title": "Resizable",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/resizable/resizable.component.ts",
      "content": "import {\n  type AfterContentInit,\n  Component,\n  ElementRef,\n  EventEmitter,\n  Input,\n  type OnDestroy,\n  type OnInit,\n  Output,\n  booleanAttribute,\n  computed,\n  inject,\n  signal,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { cn } from '@/lib/utils'\n\nexport type ResizableDirection = 'horizontal' | 'vertical'\n/*\n * Angular port of UIPKGE Resizable (React: react-resizable-panels v2 wrapped by\n * ResizablePanelGroup / ResizablePanel / ResizableHandle). The group owns the layout\n * (percentages summing to 100) as a signal; panels and handles register with the\n * nearest group. Drag, arrow / Home / End / Enter keyboard resizing, min / max /\n * collapsible constraints (the library's adjustLayoutByDelta cascade), aria-value*\n * on the separator, data-* attributes, inline flex styles and autoSaveId persistence\n * follow the library. Class strings are the React ones verbatim.\n */\n\nconst PRECISION = 10\nlet idCounter = 0\nconst nextId = (p: string) => `${p}-${++idCounter}`\n\nconst fuzzyCompare = (a: number, b: number, fractionDigits = PRECISION) => {\n  const x = parseFloat(a.toFixed(fractionDigits))\n  const y = parseFloat(b.toFixed(fractionDigits))\n  return x === y ? 0 : x > y ? 1 : -1\n}\nconst fuzzyEqual = (a: number, b: number, fractionDigits = PRECISION) => fuzzyCompare(a, b, fractionDigits) === 0\nconst fuzzyLayoutsEqual = (a: number[], b: number[]) => a.length === b.length && a.every((v, i) => fuzzyEqual(v, b[i]!))\n\nexport interface ResizablePanelConstraints {\n  collapsedSize: number\n  collapsible: boolean\n  defaultSize?: number\n  maxSize: number\n  minSize: number\n}\n\n/** Clamp one panel size to its constraints (collapsible panels snap below the halfway point). */\nexport function clampPanelSize(c: ResizablePanelConstraints, size: number): number {\n  if (fuzzyCompare(size, c.minSize) < 0) {\n    if (c.collapsible) size = fuzzyCompare(size, (c.collapsedSize + c.minSize) / 2) < 0 ? c.collapsedSize : c.minSize\n    else size = c.minSize\n  }\n  size = Math.min(c.maxSize, size)\n  return parseFloat(size.toFixed(PRECISION))\n}\n\n/** react-resizable-panels adjustLayoutByDelta: move `delta` % across the pivot, cascading past panels at their limits. */\nexport function adjustLayoutByDelta(\n  delta: number,\n  initialLayout: number[],\n  constraints: ResizablePanelConstraints[],\n  pivot: [number, number],\n  prevLayout: number[],\n  trigger: 'keyboard' | 'pointer' | 'imperative',\n): number[] {\n  if (fuzzyEqual(delta, 0)) return initialLayout\n  const next = [...initialLayout]\n  const [first, second] = pivot\n  let applied = 0\n\n  // Keyboard resizes a collapsible pivot panel straight between collapsed and min size.\n  if (trigger === 'keyboard') {\n    const index = delta < 0 ? second : first\n    const c = constraints[index]!\n    if (c.collapsible) {\n      const prev = initialLayout[index]!\n      if (fuzzyEqual(prev, c.collapsedSize)) {\n        const d = c.minSize - prev\n        if (fuzzyCompare(d, Math.abs(delta)) > 0) delta = delta < 0 ? -d : d\n      }\n    }\n    const other = delta < 0 ? first : second\n    const oc = constraints[other]!\n    if (oc.collapsible) {\n      const prev = initialLayout[other]!\n      if (fuzzyEqual(prev, oc.minSize)) {\n        const d = prev - oc.collapsedSize\n        if (fuzzyCompare(d, Math.abs(delta)) > 0) delta = delta < 0 ? -d : d\n      }\n    }\n  }\n\n  {\n    const inc = delta < 0 ? 1 : -1\n    let i = delta < 0 ? second : first\n    let available = 0\n    while (i >= 0 && i < constraints.length) {\n      available += clampPanelSize(constraints[i]!, 100) - initialLayout[i]!\n      i += inc\n    }\n    const abs = Math.min(Math.abs(delta), Math.abs(available))\n    delta = delta < 0 ? -abs : abs\n  }\n\n  {\n    let i = delta < 0 ? first : second\n    while (i >= 0 && i < constraints.length) {\n      const remaining = Math.abs(delta) - Math.abs(applied)\n      const prev = initialLayout[i]!\n      const safe = clampPanelSize(constraints[i]!, prev - remaining)\n      if (!fuzzyEqual(prev, safe)) {\n        applied += prev - safe\n        next[i] = safe\n        if (applied.toPrecision(3).localeCompare(Math.abs(delta).toPrecision(3), undefined, { numeric: true }) >= 0)\n          break\n      }\n      i += delta < 0 ? -1 : 1\n    }\n  }\n  if (fuzzyLayoutsEqual(prevLayout, next)) return prevLayout\n\n  {\n    const p = delta < 0 ? second : first\n    const unsafe = initialLayout[p]! + applied\n    const safe = clampPanelSize(constraints[p]!, unsafe)\n    next[p] = safe\n    if (!fuzzyEqual(safe, unsafe)) {\n      let remaining = unsafe - safe\n      let i = p\n      while (i >= 0 && i < constraints.length) {\n        const prev = next[i]!\n        const s = clampPanelSize(constraints[i]!, prev + remaining)\n        if (!fuzzyEqual(prev, s)) {\n          remaining -= s - prev\n          next[i] = s\n        }\n        if (fuzzyEqual(remaining, 0)) break\n        i += delta > 0 ? -1 : 1\n      }\n    }\n  }\n  const total = next.reduce((a, b) => a + b, 0)\n  return fuzzyEqual(total, 100) ? next : prevLayout\n}\n\n/** Initial layout: defaultSize where given, the rest shared equally, then validated to sum 100. */\nfunction initialLayout(constraints: ResizablePanelConstraints[]): number[] {\n  let remaining = 100\n  let unsized = 0\n  const layout = constraints.map((c) => {\n    if (c.defaultSize == null) {\n      unsized++\n      return NaN\n    }\n    remaining -= c.defaultSize\n    return c.defaultSize\n  })\n  const share = unsized ? remaining / unsized : 0\n  return validateLayout(\n    layout.map((v) => (Number.isNaN(v) ? share : v)),\n    constraints,\n  )\n}\n\nfunction validateLayout(layout: number[], constraints: ResizablePanelConstraints[]): number[] {\n  const next = [...layout]\n  const total = next.reduce((a, b) => a + b, 0)\n  if (next.length && !fuzzyEqual(total, 100)) for (let i = 0; i < next.length; i++) next[i] = (100 / total) * next[i]!\n  let remaining = 0\n  for (let i = 0; i < next.length; i++) {\n    const safe = clampPanelSize(constraints[i]!, next[i]!)\n    if (next[i] !== safe) {\n      remaining += next[i]! - safe\n      next[i] = safe\n    }\n  }\n  if (!fuzzyEqual(remaining, 0)) {\n    for (let i = 0; i < next.length; i++) {\n      const safe = clampPanelSize(constraints[i]!, next[i]! + remaining)\n      if (next[i] !== safe) {\n        remaining -= safe - next[i]!\n        next[i] = safe\n        if (fuzzyEqual(remaining, 0)) break\n      }\n    }\n  }\n  return next\n}\n\nconst byDomOrder = (a: { el: HTMLElement }, b: { el: HTMLElement }) =>\n  a.el.compareDocumentPosition(b.el) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1\n\nlet cursorStyle: HTMLStyleElement | null = null\nfunction setGlobalCursor(cursor: string | null): void {\n  if (!cursor) {\n    cursorStyle?.remove()\n    cursorStyle = null\n    return\n  }\n  cursorStyle ??= document.head.appendChild(document.createElement('style'))\n  cursorStyle.textContent = `*{cursor: ${cursor} !important;}`\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-resizable-panel-group, [ui-resizable-panel-group]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"resizable-panel-group\"',\n    '[class]': 'hostClass',\n    '[attr.id]': 'id ?? null',\n    '[attr.data-panel-group]': '\"\"',\n    '[attr.data-panel-group-direction]': 'direction',\n    '[attr.data-panel-group-id]': 'groupId',\n    '[style.display]': '\"flex\"',\n    '[style.flex-direction]': 'direction === \"horizontal\" ? \"row\" : \"column\"',\n    '[style.height]': '\"100%\"',\n    '[style.overflow]': '\"hidden\"',\n    '[style.width]': '\"100%\"',\n  },\n  template: `<ng-content />`,\n})\nexport class UiResizablePanelGroupComponent implements AfterContentInit, OnDestroy {\n  readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  @Input() direction: ResizableDirection = 'horizontal'\n  @Input() autoSaveId?: string | null\n  @Input() id?: string | null\n  /** Arrow-key step in percent (library default 10). */\n  @Input() keyboardResizeBy: number | null = null\n  @Input() storage: Pick<Storage, 'getItem' | 'setItem'> | null = null\n  @Input('class') className?: string\n  /** React `onLayout`: the layout (percentages) after every change. */\n  @Output() layout = new EventEmitter<number[]>()\n\n  readonly panels = signal<UiResizablePanelComponent[]>([])\n  readonly sizes = signal<number[]>([])\n  /** Handle currently being dragged (pointer) -- drives data-resize-handle-state=\"drag\". */\n  readonly dragging = signal<UiResizableHandleComponent | null>(null)\n  private ready = false\n  private saveTimer?: ReturnType<typeof setTimeout>\n\n  get groupId(): string {\n    return this.id ?? (this._gid ??= nextId('resizable-group'))\n  }\n  private _gid?: string\n\n  get hostClass(): string {\n    return cn('flex h-full w-full data-[panel-group-direction=vertical]:flex-col', this.className)\n  }\n\n  ngAfterContentInit(): void {\n    this.ready = true\n    this.reset()\n  }\n\n  register(panel: UiResizablePanelComponent): void {\n    this.panels.update((list) => [...list, panel].sort(byDomOrder))\n    if (this.ready) this.reset()\n  }\n\n  unregister(panel: UiResizablePanelComponent): void {\n    this.panels.update((list) => list.filter((p) => p !== panel))\n    if (this.ready) this.reset()\n  }\n\n  constraints(): ResizablePanelConstraints[] {\n    return this.panels().map((p) => p.constraints)\n  }\n\n  sizeOf(panel: UiResizablePanelComponent): number | undefined {\n    const i = this.panels().indexOf(panel)\n    return i < 0 ? undefined : this.sizes()[i]\n  }\n\n  /** Pivot panels either side of a handle (by DOM position). */\n  pivotFor(handle: HTMLElement): [number, number] | null {\n    const i =\n      this.panels().filter((p) => p.el.compareDocumentPosition(handle) & Node.DOCUMENT_POSITION_FOLLOWING).length - 1\n    return i < 0 || i + 1 >= this.panels().length ? null : [i, i + 1]\n  }\n\n  private reset(): void {\n    const constraints = this.constraints()\n    let next = initialLayout(constraints)\n    const saved = this.loadLayout()\n    if (saved && saved.length === next.length) next = validateLayout(saved, constraints)\n    this.commit(next, true)\n  }\n\n  /** Apply a new layout, firing panel resize / collapse / expand + group layout callbacks like the library. */\n  commit(next: number[], initial = false): void {\n    const prev = this.sizes()\n    if (!initial && fuzzyLayoutsEqual(prev, next)) return\n    this.sizes.set(next)\n    // The initial layout is set during change detection: notify once it has finished,\n    // like the library's post-render effects.\n    if (initial) queueMicrotask(() => this.notify(prev, next, true))\n    else this.notify(prev, next, false)\n  }\n\n  private notify(prev: number[], next: number[], initial: boolean): void {\n    this.panels().forEach((p, i) => {\n      const size = next[i]!\n      const before = initial ? undefined : prev[i]\n      if (before === undefined || !fuzzyEqual(before, size)) {\n        p.resize.emit(size)\n        const c = p.constraints\n        if (c.collapsible) {\n          const was = before !== undefined && fuzzyEqual(before, c.collapsedSize)\n          const is = fuzzyEqual(size, c.collapsedSize)\n          if ((before === undefined || was) && !is) p.expand.emit()\n          if ((before === undefined || !was) && is) p.collapse.emit()\n        }\n      }\n    })\n    this.layout.emit(next)\n    this.saveLayout(next)\n  }\n\n  /** Imperative resize of one panel (used by the panel API and Enter-to-collapse). */\n  resizePanel(panel: UiResizablePanelComponent, size: number): void {\n    const panels = this.panels()\n    const i = panels.indexOf(panel)\n    if (i < 0) return\n    const layout = this.sizes()\n    const isLast = i === panels.length - 1\n    const pivot: [number, number] = isLast ? [i - 1, i] : [i, i + 1]\n    if (pivot[0] < 0) return\n    const delta = isLast ? layout[i]! - size : size - layout[i]!\n    this.commit(adjustLayoutByDelta(delta, layout, this.constraints(), pivot, layout, 'imperative'))\n  }\n\n  private storageKey(): string {\n    return this.panels()\n      .map((p) =>\n        p.id ? p.id : p.order != null ? `${p.order}:${JSON.stringify(p.constraints)}` : JSON.stringify(p.constraints),\n      )\n      .sort((a, b) => a.localeCompare(b))\n      .join(',')\n  }\n\n  private store(): Pick<Storage, 'getItem' | 'setItem'> | null {\n    if (this.storage) return this.storage\n    try {\n      return typeof localStorage === 'undefined' ? null : localStorage\n    } catch {\n      return null\n    }\n  }\n\n  private loadLayout(): number[] | null {\n    if (!this.autoSaveId) return null\n    try {\n      const raw = this.store()?.getItem(`react-resizable-panels:${this.autoSaveId}`)\n      const state = raw ? JSON.parse(raw) : null\n      return state?.[this.storageKey()]?.layout ?? null\n    } catch {\n      return null\n    }\n  }\n\n  private saveLayout(layout: number[]): void {\n    if (!this.autoSaveId) return\n    clearTimeout(this.saveTimer)\n    this.saveTimer = setTimeout(() => {\n      try {\n        const store = this.store()\n        const key = `react-resizable-panels:${this.autoSaveId}`\n        const state = JSON.parse(store?.getItem(key) ?? '{}') ?? {}\n        state[this.storageKey()] = { expandToSizes: {}, layout }\n        store?.setItem(key, JSON.stringify(state))\n      } catch {\n        /* storage unavailable */\n      }\n    }, 100)\n  }\n\n  ngOnDestroy(): void {\n    clearTimeout(this.saveTimer)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-resizable-panel, [ui-resizable-panel]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"resizable-panel\"',\n    '[class]': 'className ?? \"\"',\n    '[attr.id]': 'panelId',\n    '[attr.data-panel-group-id]': 'group.groupId',\n    '[attr.data-panel]': '\"\"',\n    '[attr.data-panel-collapsible]': 'collapsible || null',\n    '[attr.data-panel-id]': 'panelId',\n    '[attr.data-panel-size]': 'sizeAttr()',\n    '[style.flex]': 'flexStyle()',\n    '[style.overflow]': '\"hidden\"',\n  },\n  template: `<ng-content />`,\n})\nexport class UiResizablePanelComponent implements OnInit, OnDestroy {\n  readonly group = inject(UiResizablePanelGroupComponent)\n  readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  @Input() defaultSize?: number\n  @Input() minSize = 0\n  @Input() maxSize = 100\n  @Input({ transform: booleanAttribute }) collapsible = false\n  @Input() collapsedSize = 0\n  @Input() id?: string | null\n  @Input() order?: number\n  @Input('class') className?: string\n  /** React `onResize`: new size in percent. */\n  @Output() resize = new EventEmitter<number>()\n  /** React `onCollapse`. */\n  @Output() collapse = new EventEmitter<void>()\n  /** React `onExpand`. */\n  @Output() expand = new EventEmitter<void>()\n\n  private readonly autoId = nextId('resizable-panel')\n  private readonly size = computed(() => this.group.sizeOf(this))\n  readonly sizeAttr = computed(() => this.size()?.toFixed(1) ?? null)\n  readonly flexStyle = computed(() => {\n    const s = this.size()\n    const grow = s == null ? (this.defaultSize != null ? this.defaultSize.toPrecision(3) : '1') : s.toPrecision(3)\n    return `${grow} 1 0px`\n  })\n  /** Size to restore when expanding a collapsed panel. */\n  private expandTo?: number\n\n  get panelId(): string {\n    return this.id ?? this.autoId\n  }\n\n  get constraints(): ResizablePanelConstraints {\n    return {\n      collapsedSize: this.collapsedSize,\n      collapsible: this.collapsible,\n      defaultSize: this.defaultSize,\n      maxSize: this.maxSize,\n      minSize: this.minSize,\n    }\n  }\n\n  ngOnInit(): void {\n    this.group.register(this)\n  }\n\n  // Imperative API (React ImperativePanelHandle). Named *Panel where the React name\n  // collides with the collapse / expand / resize outputs.\n  getSize(): number {\n    return this.size() ?? 0\n  }\n\n  isCollapsed(): boolean {\n    return this.collapsible && fuzzyEqual(this.getSize(), this.collapsedSize)\n  }\n\n  isExpanded(): boolean {\n    return !this.isCollapsed()\n  }\n\n  collapsePanel(): void {\n    if (!this.collapsible || this.isCollapsed()) return\n    this.expandTo = this.getSize()\n    this.group.resizePanel(this, this.collapsedSize)\n  }\n\n  expandPanel(minSize?: number): void {\n    if (!this.isCollapsed()) return\n    const target = Math.max(this.expandTo ?? minSize ?? this.minSize, minSize ?? this.minSize)\n    this.group.resizePanel(this, target)\n  }\n\n  resizePanel(size: number): void {\n    this.group.resizePanel(this, size)\n  }\n\n  ngOnDestroy(): void {\n    this.group.unregister(this)\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-resizable-handle, [ui-resizable-handle]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"resizable-handle\"',\n    '[class]': 'hostClass',\n    '[attr.id]': 'id ?? null',\n    '[attr.role]': '\"separator\"',\n    '[attr.tabindex]': 'tabIndex',\n    '[attr.data-panel-group-direction]': 'group.direction',\n    '[attr.data-panel-group-id]': 'group.groupId',\n    '[attr.data-resize-handle]': '\"\"',\n    '[attr.data-resize-handle-active]': 'active()',\n    '[attr.data-panel-resize-handle-enabled]': '!disabled',\n    '[attr.data-panel-resize-handle-id]': 'handleId',\n    '[attr.data-resize-handle-state]': 'state()',\n    '[attr.aria-controls]': 'aria().controls',\n    '[attr.aria-valuemax]': 'aria().max',\n    '[attr.aria-valuemin]': 'aria().min',\n    '[attr.aria-valuenow]': 'aria().now',\n    '[style.touch-action]': '\"none\"',\n    '[style.user-select]': '\"none\"',\n    '(pointerdown)': 'onPointerDown($event)',\n    '(pointerenter)': 'onPointerEnter()',\n    '(pointerleave)': 'onPointerLeave()',\n    '(keydown)': 'onKeydown($event)',\n    '(focus)': 'focused.set(true)',\n    '(blur)': 'focused.set(false)',\n  },\n  template: `\n    @if (withHandle) {\n      <div class=\"bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border\">\n        <ng-content\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-grip-vertical size-2.5\"\n            aria-hidden=\"true\"\n          >\n            <circle cx=\"9\" cy=\"12\" r=\"1\" />\n            <circle cx=\"9\" cy=\"5\" r=\"1\" />\n            <circle cx=\"9\" cy=\"19\" r=\"1\" />\n            <circle cx=\"15\" cy=\"12\" r=\"1\" />\n            <circle cx=\"15\" cy=\"5\" r=\"1\" />\n            <circle cx=\"15\" cy=\"19\" r=\"1\" /></svg\n        ></ng-content>\n      </div>\n    }\n  `,\n})\nexport class UiResizableHandleComponent implements OnDestroy {\n  readonly group = inject(UiResizablePanelGroupComponent)\n  private readonly el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement\n  @Input({ transform: booleanAttribute }) withHandle = false\n  @Input({ transform: booleanAttribute }) disabled = false\n  @Input() id?: string | null\n  @Input() tabIndex = 0\n  @Input('class') className?: string\n  /** React `onDragging`. */\n  @Output() dragging = new EventEmitter<boolean>()\n\n  readonly handleId = nextId('resizable-handle')\n  readonly focused = signal(false)\n  private readonly hovered = signal(false)\n  readonly state = computed(() =>\n    this.group.dragging() === this ? 'drag' : this.hovered() && !this.group.dragging() ? 'hover' : 'inactive',\n  )\n  readonly active = computed(() => (this.group.dragging() === this ? 'pointer' : this.focused() ? 'keyboard' : null))\n  readonly aria = computed(() => {\n    const layout = this.group.sizes()\n    const panels = this.group.panels()\n    const pivot = layout.length ? this.group.pivotFor(this.el) : null\n    if (!pivot) return { controls: null, max: null, min: null, now: null }\n    let curMin = 0,\n      curMax = 100,\n      totalMin = 0,\n      totalMax = 0\n    panels.forEach((p, i) => {\n      if (i === pivot[0]) {\n        curMin = p.minSize\n        curMax = p.maxSize\n      } else {\n        totalMin += p.minSize\n        totalMax += p.maxSize\n      }\n    })\n    return {\n      controls: panels[pivot[0]]!.panelId,\n      max: Math.min(curMax, 100 - totalMin),\n      min: Math.max(curMin, 100 - totalMax),\n      now: Math.round(layout[pivot[0]]!),\n    }\n  })\n  private stopDrag?: () => void\n\n  get hostClass(): string {\n    return cn(\n      'bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90',\n      this.className,\n    )\n  }\n\n  private get cursor(): string {\n    return this.group.direction === 'horizontal' ? 'ew-resize' : 'ns-resize'\n  }\n\n  onPointerEnter(): void {\n    this.hovered.set(true)\n    if (!this.disabled && !this.group.dragging()) setGlobalCursor(this.cursor)\n  }\n\n  onPointerLeave(): void {\n    this.hovered.set(false)\n    if (!this.group.dragging()) setGlobalCursor(null)\n  }\n\n  onPointerDown(event: PointerEvent): void {\n    if (this.disabled || (event.button !== undefined && event.button !== 0)) return\n    const pivot = this.group.pivotFor(this.el)\n    if (!pivot) return\n    event.preventDefault()\n    const horizontal = this.group.direction === 'horizontal'\n    const start = horizontal ? event.clientX : event.clientY\n    const initial = this.group.sizes()\n    const groupSize = horizontal ? this.group.el.offsetWidth : this.group.el.offsetHeight\n    this.el.focus({ preventScroll: true })\n    this.group.dragging.set(this)\n    this.dragging.emit(true)\n    setGlobalCursor(this.cursor)\n    const move = (e: PointerEvent) => {\n      const pos = horizontal ? e.clientX : e.clientY\n      const delta = groupSize ? ((pos - start) / groupSize) * 100 : 0\n      this.group.commit(\n        adjustLayoutByDelta(delta, initial, this.group.constraints(), pivot, this.group.sizes(), 'pointer'),\n      )\n    }\n    const up = () => this.stopDrag?.()\n    document.addEventListener('pointermove', move)\n    document.addEventListener('pointerup', up)\n    document.addEventListener('pointercancel', up)\n    this.stopDrag = () => {\n      document.removeEventListener('pointermove', move)\n      document.removeEventListener('pointerup', up)\n      document.removeEventListener('pointercancel', up)\n      this.stopDrag = undefined\n      this.group.dragging.set(null)\n      this.dragging.emit(false)\n      setGlobalCursor(this.hovered() ? this.cursor : null)\n    }\n  }\n\n  onKeydown(event: KeyboardEvent): void {\n    if (this.disabled || event.defaultPrevented) return\n    const pivot = this.group.pivotFor(this.el)\n    if (!pivot) return\n    const horizontal = this.group.direction === 'horizontal'\n    const step = this.group.keyboardResizeBy ?? 10\n    const layout = this.group.sizes()\n    let delta: number | null = null\n    switch (event.key) {\n      case 'ArrowLeft':\n        if (horizontal) delta = -step\n        break\n      case 'ArrowRight':\n        if (horizontal) delta = step\n        break\n      case 'ArrowUp':\n        if (!horizontal) delta = -step\n        break\n      case 'ArrowDown':\n        if (!horizontal) delta = step\n        break\n      case 'Home':\n        delta = -100\n        break\n      case 'End':\n        delta = 100\n        break\n      case 'Enter': {\n        // Toggle the collapsible panel before the handle.\n        event.preventDefault()\n        const panel = this.group.panels()[pivot[0]]!\n        if (!panel.collapsible) return\n        if (panel.isCollapsed()) panel.expandPanel()\n        else panel.collapsePanel()\n        return\n      }\n    }\n    if (delta === null) return\n    event.preventDefault()\n    this.group.commit(adjustLayoutByDelta(delta, layout, this.group.constraints(), pivot, layout, 'keyboard'))\n  }\n\n  ngOnDestroy(): void {\n    this.stopDrag?.()\n    if (this.hovered()) setGlobalCursor(null)\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/resizable/resizable.component.ts"
    },
    {
      "path": "packages/registry-angular/components/resizable/index.ts",
      "content": "export {\n  UiResizablePanelGroupComponent,\n  UiResizablePanelComponent,\n  UiResizableHandleComponent,\n  adjustLayoutByDelta,\n  clampPanelSize,\n  type ResizableDirection,\n  type ResizablePanelConstraints,\n} from './resizable.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/resizable/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Drag-to-resize panel layout (react-resizable-panels semantics): horizontal or vertical splits with min / max / collapsible constraints, keyboard resizing on the separator and optional autoSaveId persistence. Use for IDE-style sidebars, split views, and any layout the user should be able to reshape.",
  "categories": [
    "layout"
  ]
}