{
  "name": "stepper",
  "type": "registry:ui",
  "dependencies": [
    "radix-ui"
  ],
  "registryDependencies": [
    "button",
    "@diceui/use-as-ref",
    "@diceui/use-isomorphic-layout-effect",
    "@diceui/use-lazy-ref"
  ],
  "files": [
    {
      "path": "ui/stepper.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport { Check } from \"lucide-react\";\nimport {\n  Direction as DirectionPrimitive,\n  Slot as SlotPrimitive,\n} from \"radix-ui\";\nimport * as React from \"react\";\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { cn } from \"@/lib/utils\";\nimport { useAsRef } from \"@/registry/bases/radix/hooks/use-as-ref\";\nimport { useIsomorphicLayoutEffect } from \"@/registry/bases/radix/hooks/use-isomorphic-layout-effect\";\nimport { useLazyRef } from \"@/registry/bases/radix/hooks/use-lazy-ref\";\n\nconst ROOT_NAME = \"Stepper\";\nconst LIST_NAME = \"StepperList\";\nconst ITEM_NAME = \"StepperItem\";\nconst TRIGGER_NAME = \"StepperTrigger\";\nconst INDICATOR_NAME = \"StepperIndicator\";\nconst SEPARATOR_NAME = \"StepperSeparator\";\nconst TITLE_NAME = \"StepperTitle\";\nconst DESCRIPTION_NAME = \"StepperDescription\";\nconst CONTENT_NAME = \"StepperContent\";\nconst PREV_NAME = \"StepperPrev\";\nconst NEXT_NAME = \"StepperNext\";\n\nconst ENTRY_FOCUS = \"stepperFocusGroup.onEntryFocus\";\nconst EVENT_OPTIONS = { bubbles: false, cancelable: true };\nconst ARROW_KEYS = [\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"];\n\ntype Direction = \"ltr\" | \"rtl\";\ntype Orientation = \"horizontal\" | \"vertical\";\ntype NavigationDirection = \"next\" | \"prev\";\ntype ActivationMode = \"automatic\" | \"manual\";\ntype DataState = \"inactive\" | \"active\" | \"completed\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n  asChild?: boolean;\n}\ninterface ButtonProps extends React.ComponentProps<\"button\"> {\n  asChild?: boolean;\n}\n\ntype ListElement = React.ComponentRef<typeof StepperList>;\ntype TriggerElement = React.ComponentRef<typeof StepperTrigger>;\n\nfunction getId(\n  id: string,\n  variant: \"trigger\" | \"content\" | \"title\" | \"description\",\n  value: string,\n) {\n  return `${id}-${variant}-${value}`;\n}\n\ntype FocusIntent = \"first\" | \"last\" | \"prev\" | \"next\";\n\nconst MAP_KEY_TO_FOCUS_INTENT: Record<string, FocusIntent> = {\n  ArrowLeft: \"prev\",\n  ArrowUp: \"prev\",\n  ArrowRight: \"next\",\n  ArrowDown: \"next\",\n  PageUp: \"first\",\n  Home: \"first\",\n  PageDown: \"last\",\n  End: \"last\",\n};\n\nfunction getDirectionAwareKey(key: string, dir?: Direction) {\n  if (dir !== \"rtl\") return key;\n  return key === \"ArrowLeft\"\n    ? \"ArrowRight\"\n    : key === \"ArrowRight\"\n      ? \"ArrowLeft\"\n      : key;\n}\n\nfunction getFocusIntent(\n  event: React.KeyboardEvent<TriggerElement>,\n  dir?: Direction,\n  orientation?: Orientation,\n) {\n  const key = getDirectionAwareKey(event.key, dir);\n  if (orientation === \"horizontal\" && [\"ArrowUp\", \"ArrowDown\"].includes(key))\n    return undefined;\n  if (orientation === \"vertical\" && [\"ArrowLeft\", \"ArrowRight\"].includes(key))\n    return undefined;\n  return MAP_KEY_TO_FOCUS_INTENT[key];\n}\n\nfunction focusFirst(\n  candidates: React.RefObject<TriggerElement | null>[],\n  preventScroll = false,\n) {\n  const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;\n  for (const candidateRef of candidates) {\n    const candidate = candidateRef.current;\n    if (!candidate) continue;\n    if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;\n    candidate.focus({ preventScroll });\n    if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;\n  }\n}\n\nfunction wrapArray<T>(array: T[], startIndex: number) {\n  return array.map<T>(\n    (_, index) => array[(startIndex + index) % array.length] as T,\n  );\n}\n\nfunction getDataState(\n  value: string | undefined,\n  itemValue: string,\n  stepState: StepState | undefined,\n  steps: Map<string, StepState>,\n  variant: \"item\" | \"separator\" = \"item\",\n): DataState {\n  const stepKeys = Array.from(steps.keys());\n  const currentIndex = stepKeys.indexOf(itemValue);\n\n  if (stepState?.completed) return \"completed\";\n\n  if (value === itemValue) {\n    return variant === \"separator\" ? \"inactive\" : \"active\";\n  }\n\n  if (value) {\n    const activeIndex = stepKeys.indexOf(value);\n\n    if (activeIndex > currentIndex) return \"completed\";\n  }\n\n  return \"inactive\";\n}\n\ninterface StepState {\n  value: string;\n  completed: boolean;\n  disabled: boolean;\n}\n\ninterface StoreState {\n  steps: Map<string, StepState>;\n  value: string;\n}\n\ninterface Store {\n  subscribe: (callback: () => void) => () => void;\n  getState: () => StoreState;\n  setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n  setStateWithValidation: (\n    value: string,\n    direction: NavigationDirection,\n  ) => Promise<boolean>;\n  hasValidation: () => boolean;\n  notify: () => void;\n  addStep: (value: string, completed: boolean, disabled: boolean) => void;\n  removeStep: (value: string) => void;\n  setStep: (value: string, completed: boolean, disabled: boolean) => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n  const context = React.useContext(StoreContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n  }\n  return context;\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T): T {\n  const store = useStoreContext(\"useStore\");\n\n  const getSnapshot = React.useCallback(\n    () => selector(store.getState()),\n    [store, selector],\n  );\n\n  return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ItemData {\n  id: string;\n  ref: React.RefObject<TriggerElement | null>;\n  value: string;\n  active: boolean;\n  disabled: boolean;\n}\n\ninterface StepperContextValue {\n  rootId: string;\n  dir: Direction;\n  orientation: Orientation;\n  activationMode: ActivationMode;\n  disabled: boolean;\n  nonInteractive: boolean;\n  loop: boolean;\n}\n\nconst StepperContext = React.createContext<StepperContextValue | null>(null);\n\nfunction useStepperContext(consumerName: string) {\n  const context = React.useContext(StepperContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n  }\n  return context;\n}\n\ninterface StepperProps extends DivProps {\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  onValueComplete?: (value: string, completed: boolean) => void;\n  onValueAdd?: (value: string) => void;\n  onValueRemove?: (value: string) => void;\n  onValidate?: (\n    value: string,\n    direction: NavigationDirection,\n  ) => boolean | Promise<boolean>;\n  activationMode?: ActivationMode;\n  dir?: Direction;\n  orientation?: Orientation;\n  disabled?: boolean;\n  loop?: boolean;\n  nonInteractive?: boolean;\n}\n\nfunction Stepper(props: StepperProps) {\n  const {\n    value,\n    defaultValue,\n    onValueChange,\n    onValueComplete,\n    onValueAdd,\n    onValueRemove,\n    onValidate,\n    dir: dirProp,\n    orientation = \"horizontal\",\n    activationMode = \"automatic\",\n    asChild,\n    disabled = false,\n    nonInteractive = false,\n    loop = false,\n    className,\n    id,\n    ...rootProps\n  } = props;\n\n  const listenersRef = useLazyRef(() => new Set<() => void>());\n  const stateRef = useLazyRef<StoreState>(() => ({\n    steps: new Map(),\n    value: value ?? defaultValue ?? \"\",\n  }));\n\n  const propsRef = useAsRef({\n    onValueChange,\n    onValueComplete,\n    onValueAdd,\n    onValueRemove,\n    onValidate,\n  });\n\n  const store = React.useMemo<Store>(() => {\n    return {\n      subscribe: (cb) => {\n        listenersRef.current.add(cb);\n        return () => listenersRef.current.delete(cb);\n      },\n      getState: () => stateRef.current,\n      setState: (key, value) => {\n        if (Object.is(stateRef.current[key], value)) return;\n\n        if (key === \"value\" && typeof value === \"string\") {\n          stateRef.current.value = value;\n          propsRef.current.onValueChange?.(value);\n        } else {\n          stateRef.current[key] = value;\n        }\n\n        store.notify();\n      },\n      setStateWithValidation: async (value, direction) => {\n        if (!propsRef.current.onValidate) {\n          store.setState(\"value\", value);\n          return true;\n        }\n\n        try {\n          const isValid = await propsRef.current.onValidate(value, direction);\n          if (isValid) {\n            store.setState(\"value\", value);\n          }\n          return isValid;\n        } catch {\n          return false;\n        }\n      },\n      hasValidation: () => !!propsRef.current.onValidate,\n      addStep: (value, completed, disabled) => {\n        const newStep: StepState = { value, completed, disabled };\n        stateRef.current.steps.set(value, newStep);\n        propsRef.current.onValueAdd?.(value);\n        store.notify();\n      },\n      removeStep: (value) => {\n        stateRef.current.steps.delete(value);\n        propsRef.current.onValueRemove?.(value);\n        store.notify();\n      },\n      setStep: (value, completed, disabled) => {\n        const step = stateRef.current.steps.get(value);\n        if (step) {\n          const updatedStep: StepState = { ...step, completed, disabled };\n          stateRef.current.steps.set(value, updatedStep);\n\n          if (completed !== step.completed) {\n            propsRef.current.onValueComplete?.(value, completed);\n          }\n\n          store.notify();\n        }\n      },\n      notify: () => {\n        for (const cb of listenersRef.current) {\n          cb();\n        }\n      },\n    };\n  }, [listenersRef, stateRef, propsRef]);\n\n  useIsomorphicLayoutEffect(() => {\n    if (value !== undefined) {\n      store.setState(\"value\", value);\n    }\n  }, [value]);\n\n  const dir = DirectionPrimitive.useDirection(dirProp);\n\n  const instanceId = React.useId();\n  const rootId = id ?? instanceId;\n\n  const contextValue = React.useMemo<StepperContextValue>(\n    () => ({\n      rootId,\n      dir,\n      orientation,\n      activationMode,\n      disabled,\n      nonInteractive,\n      loop,\n    }),\n    [rootId, dir, orientation, activationMode, disabled, nonInteractive, loop],\n  );\n\n  const RootPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <StoreContext.Provider value={store}>\n      <StepperContext.Provider value={contextValue}>\n        <RootPrimitive\n          id={rootId}\n          data-disabled={disabled ? \"\" : undefined}\n          data-orientation={orientation}\n          data-slot=\"stepper\"\n          dir={dir}\n          {...rootProps}\n          className={cn(\n            \"flex gap-6\",\n            orientation === \"horizontal\" ? \"w-full flex-col\" : \"flex-row\",\n            className,\n          )}\n        />\n      </StepperContext.Provider>\n    </StoreContext.Provider>\n  );\n}\n\ninterface FocusContextValue {\n  tabStopId: string | null;\n  onItemFocus: (tabStopId: string) => void;\n  onItemShiftTab: () => void;\n  onFocusableItemAdd: () => void;\n  onFocusableItemRemove: () => void;\n  onItemRegister: (item: ItemData) => void;\n  onItemUnregister: (id: string) => void;\n  getItems: () => ItemData[];\n}\n\nconst FocusContext = React.createContext<FocusContextValue | null>(null);\n\nfunction useFocusContext(consumerName: string) {\n  const context = React.useContext(FocusContext);\n  if (!context) {\n    throw new Error(\n      `\\`${consumerName}\\` must be used within \\`FocusProvider\\``,\n    );\n  }\n  return context;\n}\n\nfunction StepperList(props: DivProps) {\n  const {\n    asChild,\n    onBlur: onBlurProp,\n    onFocus: onFocusProp,\n    onMouseDown: onMouseDownProp,\n    className,\n    children,\n    ref,\n    ...listProps\n  } = props;\n\n  const context = useStepperContext(LIST_NAME);\n  const orientation = context.orientation;\n  const currentValue = useStore((state) => state.value);\n\n  const propsRef = useAsRef({\n    onBlur: onBlurProp,\n    onFocus: onFocusProp,\n    onMouseDown: onMouseDownProp,\n  });\n\n  const [tabStopId, setTabStopId] = React.useState<string | null>(null);\n  const [isTabbingBackOut, setIsTabbingBackOut] = React.useState(false);\n  const [focusableItemCount, setFocusableItemCount] = React.useState(0);\n  const isClickFocusRef = React.useRef(false);\n  const itemsRef = React.useRef<Map<string, ItemData>>(new Map());\n  const listRef = React.useRef<ListElement>(null);\n  const composedRef = useComposedRefs(ref, listRef);\n\n  const onItemFocus = React.useCallback((tabStopId: string) => {\n    setTabStopId(tabStopId);\n  }, []);\n\n  const onItemShiftTab = React.useCallback(() => {\n    setIsTabbingBackOut(true);\n  }, []);\n\n  const onFocusableItemAdd = React.useCallback(() => {\n    setFocusableItemCount((prevCount) => prevCount + 1);\n  }, []);\n\n  const onFocusableItemRemove = React.useCallback(() => {\n    setFocusableItemCount((prevCount) => prevCount - 1);\n  }, []);\n\n  const onItemRegister = React.useCallback((item: ItemData) => {\n    itemsRef.current.set(item.id, item);\n  }, []);\n\n  const onItemUnregister = React.useCallback((id: string) => {\n    itemsRef.current.delete(id);\n  }, []);\n\n  const getItems = React.useCallback(() => {\n    return Array.from(itemsRef.current.values())\n      .filter((item) => item.ref.current)\n      .sort((a, b) => {\n        const elementA = a.ref.current;\n        const elementB = b.ref.current;\n        if (!elementA || !elementB) return 0;\n        const position = elementA.compareDocumentPosition(elementB);\n        if (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n          return -1;\n        }\n        if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n          return 1;\n        }\n        return 0;\n      });\n  }, []);\n\n  const onBlur = React.useCallback(\n    (event: React.FocusEvent<ListElement>) => {\n      propsRef.current.onBlur?.(event);\n      if (event.defaultPrevented) return;\n\n      setIsTabbingBackOut(false);\n    },\n    [propsRef],\n  );\n\n  const onFocus = React.useCallback(\n    (event: React.FocusEvent<ListElement>) => {\n      propsRef.current.onFocus?.(event);\n      if (event.defaultPrevented) return;\n\n      const isKeyboardFocus = !isClickFocusRef.current;\n      if (\n        event.target === event.currentTarget &&\n        isKeyboardFocus &&\n        !isTabbingBackOut\n      ) {\n        const entryFocusEvent = new CustomEvent(ENTRY_FOCUS, EVENT_OPTIONS);\n        event.currentTarget.dispatchEvent(entryFocusEvent);\n\n        if (!entryFocusEvent.defaultPrevented) {\n          const items = Array.from(itemsRef.current.values()).filter(\n            (item) => !item.disabled,\n          );\n          const selectedItem = currentValue\n            ? items.find((item) => item.value === currentValue)\n            : undefined;\n          const activeItem = items.find((item) => item.active);\n          const currentItem = items.find((item) => item.id === tabStopId);\n\n          const candidateItems = [\n            selectedItem,\n            activeItem,\n            currentItem,\n            ...items,\n          ].filter(Boolean) as ItemData[];\n          const candidateRefs = candidateItems.map((item) => item.ref);\n          focusFirst(candidateRefs, false);\n        }\n      }\n      isClickFocusRef.current = false;\n    },\n    [propsRef, isTabbingBackOut, currentValue, tabStopId],\n  );\n\n  const onMouseDown = React.useCallback(\n    (event: React.MouseEvent<ListElement>) => {\n      propsRef.current.onMouseDown?.(event);\n\n      if (event.defaultPrevented) return;\n\n      isClickFocusRef.current = true;\n    },\n    [propsRef],\n  );\n\n  const focusContextValue = React.useMemo<FocusContextValue>(\n    () => ({\n      tabStopId,\n      onItemFocus,\n      onItemShiftTab,\n      onFocusableItemAdd,\n      onFocusableItemRemove,\n      onItemRegister,\n      onItemUnregister,\n      getItems,\n    }),\n    [\n      tabStopId,\n      onItemFocus,\n      onItemShiftTab,\n      onFocusableItemAdd,\n      onFocusableItemRemove,\n      onItemRegister,\n      onItemUnregister,\n      getItems,\n    ],\n  );\n\n  const ListPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <FocusContext.Provider value={focusContextValue}>\n      <ListPrimitive\n        role=\"tablist\"\n        aria-orientation={orientation}\n        data-orientation={orientation}\n        data-slot=\"stepper-list\"\n        dir={context.dir}\n        tabIndex={isTabbingBackOut || focusableItemCount === 0 ? -1 : 0}\n        {...listProps}\n        ref={composedRef}\n        className={cn(\n          \"flex outline-none\",\n          orientation === \"horizontal\"\n            ? \"flex-row items-center\"\n            : \"flex-col items-start\",\n          className,\n        )}\n        onBlur={onBlur}\n        onFocus={onFocus}\n        onMouseDown={onMouseDown}\n      >\n        {children}\n      </ListPrimitive>\n    </FocusContext.Provider>\n  );\n}\n\ninterface StepperItemContextValue {\n  value: string;\n  stepState: StepState | undefined;\n}\n\nconst StepperItemContext = React.createContext<StepperItemContextValue | null>(\n  null,\n);\n\nfunction useStepperItemContext(consumerName: string) {\n  const context = React.useContext(StepperItemContext);\n  if (!context) {\n    throw new Error(`\\`${consumerName}\\` must be used within \\`${ITEM_NAME}\\``);\n  }\n  return context;\n}\n\ninterface StepperItemProps extends DivProps {\n  value: string;\n  completed?: boolean;\n  disabled?: boolean;\n}\n\nfunction StepperItem(props: StepperItemProps) {\n  const {\n    value: itemValue,\n    completed = false,\n    disabled = false,\n    asChild,\n    className,\n    children,\n    ref,\n    ...itemProps\n  } = props;\n\n  const context = useStepperContext(ITEM_NAME);\n  const store = useStoreContext(ITEM_NAME);\n  const orientation = context.orientation;\n  const value = useStore((state) => state.value);\n\n  useIsomorphicLayoutEffect(() => {\n    store.addStep(itemValue, completed, disabled);\n\n    return () => {\n      store.removeStep(itemValue);\n    };\n  }, [itemValue, completed, disabled]);\n\n  useIsomorphicLayoutEffect(() => {\n    store.setStep(itemValue, completed, disabled);\n  }, [itemValue, completed, disabled]);\n\n  const stepState = useStore((state) => state.steps.get(itemValue));\n  const steps = useStore((state) => state.steps);\n  const dataState = getDataState(value, itemValue, stepState, steps);\n\n  const itemContextValue = React.useMemo<StepperItemContextValue>(\n    () => ({\n      value: itemValue,\n      stepState,\n    }),\n    [itemValue, stepState],\n  );\n\n  const ItemPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <StepperItemContext.Provider value={itemContextValue}>\n      <ItemPrimitive\n        data-disabled={stepState?.disabled ? \"\" : undefined}\n        data-orientation={orientation}\n        data-state={dataState}\n        data-slot=\"stepper-item\"\n        dir={context.dir}\n        {...itemProps}\n        ref={ref}\n        className={cn(\n          \"relative flex not-last:flex-1 items-center\",\n          orientation === \"horizontal\" ? \"flex-row\" : \"flex-col\",\n          className,\n        )}\n      >\n        {children}\n      </ItemPrimitive>\n    </StepperItemContext.Provider>\n  );\n}\n\nfunction StepperTrigger(props: ButtonProps) {\n  const {\n    asChild,\n    onClick: onClickProp,\n    onFocus: onFocusProp,\n    onKeyDown: onKeyDownProp,\n    onMouseDown: onMouseDownProp,\n    disabled,\n    className,\n    ref,\n    ...triggerProps\n  } = props;\n\n  const context = useStepperContext(TRIGGER_NAME);\n  const itemContext = useStepperItemContext(TRIGGER_NAME);\n  const itemValue = itemContext.value;\n\n  const store = useStoreContext(TRIGGER_NAME);\n  const focusContext = useFocusContext(TRIGGER_NAME);\n  const value = useStore((state) => state.value);\n  const steps = useStore((state) => state.steps);\n  const stepState = useStore((state) => state.steps.get(itemValue));\n\n  const propsRef = useAsRef({\n    onClick: onClickProp,\n    onFocus: onFocusProp,\n    onKeyDown: onKeyDownProp,\n    onMouseDown: onMouseDownProp,\n  });\n\n  const activationMode = context.activationMode;\n  const orientation = context.orientation;\n  const loop = context.loop;\n\n  const stepIndex = Array.from(steps.keys()).indexOf(itemValue);\n\n  const stepPosition = stepIndex + 1;\n  const stepCount = steps.size;\n\n  const triggerId = getId(context.rootId, \"trigger\", itemValue);\n  const contentId = getId(context.rootId, \"content\", itemValue);\n  const titleId = getId(context.rootId, \"title\", itemValue);\n  const descriptionId = getId(context.rootId, \"description\", itemValue);\n\n  const isDisabled = disabled || stepState?.disabled || context.disabled;\n  const isActive = value === itemValue;\n  const isTabStop = focusContext.tabStopId === triggerId;\n  const dataState = getDataState(value, itemValue, stepState, steps);\n\n  const triggerRef = React.useRef<TriggerElement>(null);\n  const composedRef = useComposedRefs(ref, triggerRef);\n  const isArrowKeyPressedRef = React.useRef(false);\n  const isMouseClickRef = React.useRef(false);\n\n  React.useEffect(() => {\n    function onKeyDown(event: KeyboardEvent) {\n      if (ARROW_KEYS.includes(event.key)) {\n        isArrowKeyPressedRef.current = true;\n      }\n    }\n    function onKeyUp() {\n      isArrowKeyPressedRef.current = false;\n    }\n    document.addEventListener(\"keydown\", onKeyDown);\n    document.addEventListener(\"keyup\", onKeyUp);\n    return () => {\n      document.removeEventListener(\"keydown\", onKeyDown);\n      document.removeEventListener(\"keyup\", onKeyUp);\n    };\n  }, []);\n\n  useIsomorphicLayoutEffect(() => {\n    focusContext.onItemRegister({\n      id: triggerId,\n      ref: triggerRef,\n      value: itemValue,\n      active: isTabStop,\n      disabled: !!isDisabled,\n    });\n\n    if (!isDisabled) {\n      focusContext.onFocusableItemAdd();\n    }\n\n    return () => {\n      focusContext.onItemUnregister(triggerId);\n      if (!isDisabled) {\n        focusContext.onFocusableItemRemove();\n      }\n    };\n  }, [focusContext, triggerId, itemValue, isTabStop, isDisabled]);\n\n  const onClick = React.useCallback(\n    async (event: React.MouseEvent<TriggerElement>) => {\n      propsRef.current.onClick?.(event);\n      if (event.defaultPrevented) return;\n\n      if (!isDisabled && !context.nonInteractive) {\n        const currentStepIndex = Array.from(steps.keys()).indexOf(value ?? \"\");\n        const targetStepIndex = Array.from(steps.keys()).indexOf(itemValue);\n        const direction = targetStepIndex > currentStepIndex ? \"next\" : \"prev\";\n\n        await store.setStateWithValidation(itemValue, direction);\n      }\n    },\n    [\n      isDisabled,\n      context.nonInteractive,\n      store,\n      itemValue,\n      value,\n      steps,\n      propsRef,\n    ],\n  );\n\n  const onFocus = React.useCallback(\n    async (event: React.FocusEvent<TriggerElement>) => {\n      propsRef.current.onFocus?.(event);\n      if (event.defaultPrevented) return;\n\n      focusContext.onItemFocus(triggerId);\n\n      const isKeyboardFocus = !isMouseClickRef.current;\n\n      if (\n        !isActive &&\n        !isDisabled &&\n        activationMode !== \"manual\" &&\n        !context.nonInteractive &&\n        isKeyboardFocus\n      ) {\n        const currentStepIndex = Array.from(steps.keys()).indexOf(value || \"\");\n        const targetStepIndex = Array.from(steps.keys()).indexOf(itemValue);\n        const direction = targetStepIndex > currentStepIndex ? \"next\" : \"prev\";\n\n        await store.setStateWithValidation(itemValue, direction);\n      }\n\n      isMouseClickRef.current = false;\n    },\n    [\n      focusContext,\n      triggerId,\n      activationMode,\n      isActive,\n      isDisabled,\n      context.nonInteractive,\n      store,\n      itemValue,\n      value,\n      steps,\n      propsRef,\n    ],\n  );\n\n  const onKeyDown = React.useCallback(\n    async (event: React.KeyboardEvent<TriggerElement>) => {\n      propsRef.current.onKeyDown?.(event);\n      if (event.defaultPrevented) return;\n\n      if (event.key === \"Enter\" && context.nonInteractive) {\n        event.preventDefault();\n        return;\n      }\n\n      if (\n        (event.key === \"Enter\" || event.key === \" \") &&\n        activationMode === \"manual\" &&\n        !context.nonInteractive\n      ) {\n        event.preventDefault();\n        if (!isDisabled && triggerRef.current) {\n          triggerRef.current.click();\n        }\n        return;\n      }\n\n      if (event.key === \"Tab\" && event.shiftKey) {\n        focusContext.onItemShiftTab();\n        return;\n      }\n\n      if (event.target !== event.currentTarget) return;\n\n      const focusIntent = getFocusIntent(event, context.dir, orientation);\n\n      if (focusIntent !== undefined) {\n        if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey)\n          return;\n        event.preventDefault();\n\n        const items = focusContext.getItems().filter((item) => !item.disabled);\n        let candidateRefs = items.map((item) => item.ref);\n\n        if (focusIntent === \"last\") {\n          candidateRefs.reverse();\n        } else if (focusIntent === \"prev\" || focusIntent === \"next\") {\n          if (focusIntent === \"prev\") candidateRefs.reverse();\n          const currentIndex = candidateRefs.findIndex(\n            (ref) => ref.current === event.currentTarget,\n          );\n          candidateRefs = loop\n            ? wrapArray(candidateRefs, currentIndex + 1)\n            : candidateRefs.slice(currentIndex + 1);\n        }\n\n        if (store.hasValidation() && candidateRefs.length > 0) {\n          const nextRef = candidateRefs[0];\n          const nextElement = nextRef?.current;\n          const nextItem = items.find(\n            (item) => item.ref.current === nextElement,\n          );\n\n          if (nextItem && nextItem.value !== itemValue) {\n            const currentStepIndex = Array.from(steps.keys()).indexOf(\n              value || \"\",\n            );\n            const targetStepIndex = Array.from(steps.keys()).indexOf(\n              nextItem.value,\n            );\n            const direction: NavigationDirection =\n              targetStepIndex > currentStepIndex ? \"next\" : \"prev\";\n\n            if (direction === \"next\") {\n              const isValid = await store.setStateWithValidation(\n                nextItem.value,\n                direction,\n              );\n              if (!isValid) return;\n            } else {\n              store.setState(\"value\", nextItem.value);\n            }\n\n            queueMicrotask(() => nextElement?.focus());\n            return;\n          }\n        }\n\n        queueMicrotask(() => focusFirst(candidateRefs));\n      }\n    },\n    [\n      focusContext,\n      context.nonInteractive,\n      context.dir,\n      activationMode,\n      orientation,\n      loop,\n      isDisabled,\n      store,\n      propsRef,\n      itemValue,\n      value,\n      steps,\n    ],\n  );\n\n  const onMouseDown = React.useCallback(\n    (event: React.MouseEvent<TriggerElement>) => {\n      propsRef.current.onMouseDown?.(event);\n      if (event.defaultPrevented) return;\n\n      isMouseClickRef.current = true;\n\n      if (isDisabled) {\n        event.preventDefault();\n      } else {\n        focusContext.onItemFocus(triggerId);\n      }\n    },\n    [focusContext, triggerId, isDisabled, propsRef],\n  );\n\n  const TriggerPrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <TriggerPrimitive\n      id={triggerId}\n      role=\"tab\"\n      type=\"button\"\n      aria-controls={contentId}\n      aria-current={isActive ? \"step\" : undefined}\n      aria-describedby={`${titleId} ${descriptionId}`}\n      aria-posinset={stepPosition}\n      aria-selected={isActive}\n      aria-setsize={stepCount}\n      data-disabled={isDisabled ? \"\" : undefined}\n      data-state={dataState}\n      data-slot=\"stepper-trigger\"\n      disabled={isDisabled}\n      tabIndex={isTabStop ? 0 : -1}\n      {...triggerProps}\n      ref={composedRef}\n      className={cn(\n        \"inline-flex items-center justify-center gap-3 rounded-md text-left outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        \"not-has-data-[slot=description]:rounded-full not-has-data-[slot=title]:rounded-full\",\n        className,\n      )}\n      onClick={onClick}\n      onFocus={onFocus}\n      onKeyDown={onKeyDown}\n      onMouseDown={onMouseDown}\n    />\n  );\n}\n\ninterface StepperIndicatorProps extends Omit<DivProps, \"children\"> {\n  children?: React.ReactNode | ((dataState: DataState) => React.ReactNode);\n}\n\nfunction StepperIndicator(props: StepperIndicatorProps) {\n  const { className, children, asChild, ref, ...indicatorProps } = props;\n\n  const context = useStepperContext(INDICATOR_NAME);\n  const itemContext = useStepperItemContext(INDICATOR_NAME);\n\n  const value = useStore((state) => state.value);\n  const itemValue = itemContext.value;\n  const stepState = useStore((state) => state.steps.get(itemValue));\n  const steps = useStore((state) => state.steps);\n\n  const stepPosition = Array.from(steps.keys()).indexOf(itemValue) + 1;\n\n  const dataState = getDataState(value, itemValue, stepState, steps);\n\n  const IndicatorPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <IndicatorPrimitive\n      data-state={dataState}\n      data-slot=\"stepper-indicator\"\n      dir={context.dir}\n      {...indicatorProps}\n      ref={ref}\n      className={cn(\n        \"flex size-7 shrink-0 items-center justify-center rounded-full border-2 border-muted bg-background font-medium text-muted-foreground text-sm transition-colors data-[state=active]:border-primary data-[state=completed]:border-primary data-[state=active]:bg-primary data-[state=completed]:bg-primary data-[state=active]:text-primary-foreground data-[state=completed]:text-primary-foreground\",\n        className,\n      )}\n    >\n      {typeof children === \"function\" ? (\n        children(dataState)\n      ) : children ? (\n        children\n      ) : dataState === \"completed\" ? (\n        <Check className=\"size-4\" />\n      ) : (\n        stepPosition\n      )}\n    </IndicatorPrimitive>\n  );\n}\n\ninterface StepperSeparatorProps extends DivProps {\n  forceMount?: boolean;\n}\n\nfunction StepperSeparator(props: StepperSeparatorProps) {\n  const {\n    className,\n    asChild,\n    forceMount = false,\n    ref,\n    ...separatorProps\n  } = props;\n\n  const context = useStepperContext(SEPARATOR_NAME);\n  const itemContext = useStepperItemContext(SEPARATOR_NAME);\n  const value = useStore((state) => state.value);\n  const steps = useStore((state) => state.steps);\n\n  const orientation = context.orientation;\n\n  const stepIndex = Array.from(steps.keys()).indexOf(itemContext.value);\n\n  const isLastStep = stepIndex === steps.size - 1;\n\n  if (isLastStep && !forceMount) return null;\n\n  const dataState = getDataState(\n    value,\n    itemContext.value,\n    itemContext.stepState,\n    steps,\n    \"separator\",\n  );\n\n  const SeparatorPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <SeparatorPrimitive\n      role=\"separator\"\n      aria-hidden=\"true\"\n      aria-orientation={orientation}\n      data-orientation={orientation}\n      data-state={dataState}\n      data-slot=\"stepper-separator\"\n      dir={context.dir}\n      {...separatorProps}\n      ref={ref}\n      className={cn(\n        \"bg-border transition-colors data-[state=active]:bg-primary data-[state=completed]:bg-primary\",\n        orientation === \"horizontal\" ? \"h-px flex-1\" : \"h-10 w-px\",\n        className,\n      )}\n    />\n  );\n}\n\ninterface StepperTitleProps extends React.ComponentProps<\"span\"> {\n  asChild?: boolean;\n}\n\nfunction StepperTitle(props: StepperTitleProps) {\n  const { className, asChild, ref, ...titleProps } = props;\n\n  const context = useStepperContext(TITLE_NAME);\n  const itemContext = useStepperItemContext(TITLE_NAME);\n\n  const titleId = getId(context.rootId, \"title\", itemContext.value);\n\n  const TitlePrimitive = asChild ? SlotPrimitive.Slot : \"span\";\n\n  return (\n    <TitlePrimitive\n      id={titleId}\n      data-slot=\"title\"\n      dir={context.dir}\n      {...titleProps}\n      ref={ref}\n      className={cn(\"font-medium text-sm\", className)}\n    />\n  );\n}\n\ninterface StepperDescriptionProps extends React.ComponentProps<\"span\"> {\n  asChild?: boolean;\n}\n\nfunction StepperDescription(props: StepperDescriptionProps) {\n  const { className, asChild, ref, ...descriptionProps } = props;\n\n  const context = useStepperContext(DESCRIPTION_NAME);\n  const itemContext = useStepperItemContext(DESCRIPTION_NAME);\n\n  const descriptionId = getId(context.rootId, \"description\", itemContext.value);\n\n  const DescriptionPrimitive = asChild ? SlotPrimitive.Slot : \"span\";\n\n  return (\n    <DescriptionPrimitive\n      id={descriptionId}\n      data-slot=\"description\"\n      dir={context.dir}\n      {...descriptionProps}\n      ref={ref}\n      className={cn(\"text-muted-foreground text-xs\", className)}\n    />\n  );\n}\n\ninterface StepperContentProps extends DivProps {\n  value: string;\n  forceMount?: boolean;\n}\n\nfunction StepperContent(props: StepperContentProps) {\n  const {\n    value: valueProp,\n    asChild,\n    forceMount = false,\n    ref,\n    className,\n    ...contentProps\n  } = props;\n\n  const context = useStepperContext(CONTENT_NAME);\n  const value = useStore((state) => state.value);\n\n  const contentId = getId(context.rootId, \"content\", valueProp);\n  const triggerId = getId(context.rootId, \"trigger\", valueProp);\n\n  if (valueProp !== value && !forceMount) return null;\n\n  const ContentPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n  return (\n    <ContentPrimitive\n      id={contentId}\n      role=\"tabpanel\"\n      aria-labelledby={triggerId}\n      data-slot=\"stepper-content\"\n      dir={context.dir}\n      {...contentProps}\n      ref={ref}\n      className={cn(\"flex-1 outline-none\", className)}\n    />\n  );\n}\n\nfunction StepperPrev(props: ButtonProps) {\n  const { asChild, onClick: onClickProp, disabled, ...prevProps } = props;\n\n  const store = useStoreContext(PREV_NAME);\n  const value = useStore((state) => state.value);\n  const steps = useStore((state) => state.steps);\n\n  const propsRef = useAsRef({\n    onClick: onClickProp,\n  });\n\n  const stepKeys = Array.from(steps.keys());\n  const currentIndex = value ? stepKeys.indexOf(value) : -1;\n  const isDisabled = disabled || currentIndex <= 0;\n\n  const onClick = React.useCallback(\n    async (event: React.MouseEvent<HTMLButtonElement>) => {\n      propsRef.current.onClick?.(event);\n      if (event.defaultPrevented || isDisabled) return;\n\n      const prevIndex = Math.max(currentIndex - 1, 0);\n      const prevStepValue = stepKeys[prevIndex];\n\n      if (prevStepValue) {\n        store.setState(\"value\", prevStepValue);\n      }\n    },\n    [propsRef, isDisabled, currentIndex, stepKeys, store],\n  );\n\n  const PrevPrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <PrevPrimitive\n      type=\"button\"\n      data-slot=\"stepper-prev\"\n      disabled={isDisabled}\n      {...prevProps}\n      onClick={onClick}\n    />\n  );\n}\n\nfunction StepperNext(props: ButtonProps) {\n  const { asChild, onClick: onClickProp, disabled, ...nextProps } = props;\n\n  const store = useStoreContext(NEXT_NAME);\n  const value = useStore((state) => state.value);\n  const steps = useStore((state) => state.steps);\n\n  const propsRef = useAsRef({\n    onClick: onClickProp,\n  });\n\n  const stepKeys = Array.from(steps.keys());\n  const currentIndex = value ? stepKeys.indexOf(value) : -1;\n  const isDisabled = disabled || currentIndex >= stepKeys.length - 1;\n\n  const onClick = React.useCallback(\n    async (event: React.MouseEvent<HTMLButtonElement>) => {\n      propsRef.current.onClick?.(event);\n      if (event.defaultPrevented || isDisabled) return;\n\n      const nextIndex = Math.min(currentIndex + 1, stepKeys.length - 1);\n      const nextStepValue = stepKeys[nextIndex];\n\n      if (nextStepValue) {\n        await store.setStateWithValidation(nextStepValue, \"next\");\n      }\n    },\n    [propsRef, isDisabled, currentIndex, stepKeys, store],\n  );\n\n  const NextPrimitive = asChild ? SlotPrimitive.Slot : \"button\";\n\n  return (\n    <NextPrimitive\n      type=\"button\"\n      data-slot=\"stepper-next\"\n      disabled={isDisabled}\n      {...nextProps}\n      onClick={onClick}\n    />\n  );\n}\n\nexport {\n  Stepper,\n  StepperContent,\n  StepperDescription,\n  StepperIndicator,\n  StepperItem,\n  StepperList,\n  StepperNext,\n  StepperPrev,\n  type StepperProps,\n  StepperSeparator,\n  StepperTitle,\n  StepperTrigger,\n  useStore as useStepper,\n};\n",
      "target": ""
    },
    {
      "path": "lib/compose-refs.ts",
      "type": "registry:lib",
      "content": "/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\nimport * as React from \"react\";\n\ntype PossibleRef<T> = React.Ref<T> | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef<T>(ref: PossibleRef<T>, value: T) {\n  if (typeof ref === \"function\") {\n    return ref(value);\n  }\n\n  if (ref !== null && ref !== undefined) {\n    ref.current = value;\n  }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  return (node) => {\n    let hasCleanup = false;\n    const cleanups = refs.map((ref) => {\n      const cleanup = setRef(ref, node);\n      if (!hasCleanup && typeof cleanup === \"function\") {\n        hasCleanup = true;\n      }\n      return cleanup;\n    });\n\n    // React <19 will log an error to the console if a callback ref returns a\n    // value. We don't use ref cleanups internally so this will only happen if a\n    // user's ref callback returns a value, which we only expect if they are\n    // using the cleanup functionality added in React 19.\n    if (hasCleanup) {\n      return () => {\n        for (let i = 0; i < cleanups.length; i++) {\n          const cleanup = cleanups[i];\n          if (typeof cleanup === \"function\") {\n            cleanup();\n          } else {\n            setRef(refs[i], null);\n          }\n        }\n      };\n    }\n  };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\n  // biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values\n  return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n",
      "target": ""
    }
  ]
}