{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "confirm-button",
  "title": "Confirm Button",
  "description": "A Button that confirms on a click, a second click or a press and hold. Supports inline undo and a pending state for an async onConfirm.",
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "components/ui/sureui/confirmation.ts",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\ntype Fill = {\n  from: number\n  to: number\n  duration: number\n  startedAt: number\n  easing?: string\n}\n\nfunction prefersReducedMotion() {\n  return (\n    typeof window.matchMedia === \"function\" &&\n    window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n  )\n}\n\nfunction keyframes(element: Element, fill: Fill) {\n  if (element instanceof SVGElement)\n    return [\n      { strokeDashoffset: `${1 - fill.from}` },\n      { strokeDashoffset: `${1 - fill.to}` },\n    ]\n  return [{ scale: `${fill.from} 1` }, { scale: `${fill.to} 1` }]\n}\n\nfunction playFill(element: Element | null, fill: Fill) {\n  if (!element || typeof element.animate !== \"function\") return null\n  const animation = element.animate(keyframes(element, fill), {\n    duration: fill.duration,\n    easing: prefersReducedMotion() ? \"step-end\" : (fill.easing ?? \"linear\"),\n    fill: \"forwards\",\n  })\n  animation.currentTime = performance.now() - fill.startedAt\n  return animation\n}\n\nfunction useFill(\n  ref: React.RefObject<Element | null>,\n  fill: Fill | null,\n  paused: boolean\n) {\n  const animationRef = React.useRef<Animation | null>(null)\n\n  React.useEffect(() => {\n    if (!fill) return\n    const animation = playFill(ref.current, fill)\n    animationRef.current = animation\n    return () => {\n      animation?.cancel()\n      animationRef.current = null\n    }\n  }, [ref, fill])\n\n  React.useEffect(() => {\n    const animation = animationRef.current\n    if (paused) animation?.pause()\n    else if (animation?.playState === \"paused\") animation.play()\n  }, [paused, fill])\n}\n\ntype UndoPauseReason = \"hover\" | \"focus\" | \"hidden\"\n\ntype UndoDuration = boolean | number | \"manual\"\n\ntype UndoWindowOptions = {\n  duration?: UndoDuration\n  onExpire: () => void\n  onPauseChange?: (paused: boolean) => void\n  within?: () => Element | null\n}\n\ntype UndoWindow = {\n  duration: number\n  manual: boolean\n  elapsed: () => number\n  paused: () => boolean\n  pause: (reason: UndoPauseReason) => void\n  resume: (reason: UndoPauseReason) => void\n  cancel: () => void\n}\n\nfunction undoDuration(value: UndoDuration | undefined) {\n  if (typeof value !== \"number\" || !Number.isFinite(value)) return 5000\n  return Math.min(Math.max(value, 4000), 60000)\n}\n\nfunction startManualWindow({\n  onExpire,\n  within,\n}: UndoWindowOptions): UndoWindow {\n  let open = true\n\n  function stop() {\n    open = false\n    document.removeEventListener(\"pointerdown\", onOutside, true)\n    document.removeEventListener(\"focusin\", onOutside, true)\n  }\n\n  function onOutside(event: Event) {\n    const container = within?.()\n    const target = event.target\n    if (!open || !container || !(target instanceof Node)) return\n    if (container.contains(target)) return\n    stop()\n    onExpire()\n  }\n\n  if (within) {\n    document.addEventListener(\"pointerdown\", onOutside, true)\n    document.addEventListener(\"focusin\", onOutside, true)\n  }\n\n  return {\n    duration: Infinity,\n    manual: true,\n    elapsed: () => 0,\n    paused: () => false,\n    pause: () => {},\n    resume: () => {},\n    cancel: stop,\n  }\n}\n\nfunction startUndoWindow(options: UndoWindowOptions): UndoWindow {\n  const { duration, onExpire, onPauseChange } = options\n  if (duration === \"manual\") return startManualWindow(options)\n  const total = undoDuration(duration)\n  const pausedBy = new Set<UndoPauseReason>()\n  let open = true\n  let remaining = total\n  let startedAt = performance.now()\n  let timer = setTimeout(expire, remaining)\n\n  function stop() {\n    open = false\n    clearTimeout(timer)\n    document.removeEventListener(\"visibilitychange\", onVisibilityChange)\n  }\n\n  function expire() {\n    stop()\n    onExpire()\n  }\n\n  function pause(reason: UndoPauseReason) {\n    if (!open || pausedBy.has(reason)) return\n    pausedBy.add(reason)\n    if (pausedBy.size > 1) return\n    clearTimeout(timer)\n    remaining -= performance.now() - startedAt\n    onPauseChange?.(true)\n  }\n\n  function resume(reason: UndoPauseReason) {\n    if (!open || !pausedBy.delete(reason) || pausedBy.size > 0) return\n    startedAt = performance.now()\n    timer = setTimeout(expire, remaining)\n    onPauseChange?.(false)\n  }\n\n  function onVisibilityChange() {\n    if (document.visibilityState === \"hidden\") pause(\"hidden\")\n    else resume(\"hidden\")\n  }\n\n  document.addEventListener(\"visibilitychange\", onVisibilityChange)\n  onVisibilityChange()\n\n  return {\n    duration: total,\n    manual: false,\n    elapsed: () =>\n      total -\n      remaining +\n      (pausedBy.size > 0 ? 0 : performance.now() - startedAt),\n    paused: () => pausedBy.size > 0,\n    pause,\n    resume,\n    cancel: stop,\n  }\n}\n\ntype ConfirmationState = \"idle\" | \"armed\" | \"holding\" | \"undo\" | \"pending\"\n\ntype PauseReason = \"hover\" | \"focus\"\n\ntype InlineUndo = {\n  window: UndoWindow\n  pausable: Set<PauseReason>\n}\n\ninterface ConfirmationOptions {\n  onConfirm: () => void | Promise<unknown>\n  onCancel?: () => void\n  onConfirmError?: (error: unknown) => void\n  undo?: boolean | number | \"manual\"\n  pauseUndoOnHover?: boolean\n  pauseUndoOnFocus?: boolean\n}\n\ninterface GestureOptions {\n  gesture?: \"click\" | \"click-again\" | \"hold\" | \"slide\"\n  timeout?: number\n  duration?: number\n  holdFallback?: \"click-again\" | \"none\"\n  armDelay?: number\n  wait?: number\n  disabled?: boolean\n}\n\ntype TriggerProps<T extends Element> = {\n  disabled?: boolean\n  onClick?(event: React.MouseEvent<T>): void\n  onBlur?(event: React.FocusEvent<T>): void\n  onFocus?(event: React.FocusEvent<T>): void\n  onPointerDown?(event: React.PointerEvent<T>): void\n  onPointerUp?(event: React.PointerEvent<T>): void\n  onPointerMove?(event: React.PointerEvent<T>): void\n  onPointerEnter?(event: React.PointerEvent<T>): void\n  onPointerLeave?(event: React.PointerEvent<T>): void\n  onPointerCancel?(event: React.PointerEvent<T>): void\n  onKeyDown?(event: React.KeyboardEvent<T>): void\n  onKeyUp?(event: React.KeyboardEvent<T>): void\n  onContextMenu?(event: React.MouseEvent<T>): void\n  onMouseDown?(event: React.MouseEvent<T>): void\n}\n\nfunction hasSelection() {\n  const selection = window.getSelection()\n  return !!selection && !selection.isCollapsed\n}\n\nfunction clearSelection() {\n  if (hasSelection()) window.getSelection()?.removeAllRanges()\n}\n\nfunction isPromise(value: unknown): value is PromiseLike<unknown> {\n  return (\n    typeof value === \"object\" &&\n    value !== null &&\n    typeof (value as PromiseLike<unknown>).then === \"function\"\n  )\n}\n\nfunction toMs(value: number, fallback: number, min: number) {\n  return Number.isFinite(value)\n    ? Math.min(Math.max(value, min), 60000)\n    : fallback\n}\n\nfunction composeHandlers<E>(\n  theirs: ((event: E) => void) | undefined,\n  ours: (event: E) => void\n) {\n  return (event: E) => {\n    theirs?.(event)\n    ours(event)\n  }\n}\n\nfunction isPressKey(event: React.KeyboardEvent) {\n  return event.key === \" \" || event.key === \"Enter\"\n}\n\nfunction isVirtualPress(event: React.PointerEvent) {\n  const { width, height, pressure, pointerType } = event.nativeEvent\n  const android = /Android/i.test(navigator.userAgent)\n  if (width < 1 && height < 1) return !android\n  return (\n    android &&\n    pointerType === \"mouse\" &&\n    width === 1 &&\n    height === 1 &&\n    pressure === 0\n  )\n}\n\nfunction useConfirmationMachine(\n  options: ConfirmationOptions,\n  triggerRef: React.RefObject<Element | null>\n) {\n  const [state, setState] = React.useState<ConfirmationState>(\"idle\")\n  const [fill, setFill] = React.useState<Fill | null>(null)\n  const [paused, setPaused] = React.useState(false)\n  const [failed, setFailed] = React.useState(false)\n  const timerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n  const holdStartRef = React.useRef(0)\n  const holdDurationRef = React.useRef(0)\n  const undoRef = React.useRef<InlineUndo | null>(null)\n  const optionsRef = React.useRef(options)\n\n  React.useEffect(() => {\n    optionsRef.current = options\n  })\n\n  const clearTimer = React.useCallback(() => {\n    undoRef.current?.window.cancel()\n    undoRef.current = null\n    if (timerRef.current !== null) {\n      clearTimeout(timerRef.current)\n      timerRef.current = null\n    }\n  }, [])\n\n  React.useEffect(() => clearTimer, [clearTimer])\n\n  const enter = React.useCallback(\n    (next: ConfirmationState, nextFill: Fill | null = null) => {\n      setState(next)\n      setFill(nextFill)\n      setPaused(false)\n      setFailed(false)\n    },\n    []\n  )\n\n  const fail = React.useCallback((error: unknown) => {\n    setFailed(true)\n    const { onConfirmError } = optionsRef.current\n    if (!onConfirmError) throw error\n    onConfirmError(error)\n  }, [])\n\n  const commit = React.useCallback(\n    (run: ConfirmationOptions[\"onConfirm\"]) => {\n      clearTimer()\n      let result: ReturnType<ConfirmationOptions[\"onConfirm\"]>\n      try {\n        result = run()\n      } catch (error) {\n        enter(\"idle\")\n        return fail(error)\n      }\n      if (isPromise(result)) {\n        enter(\"pending\")\n        ;(async () => {\n          try {\n            await result\n            setState(\"idle\")\n          } catch (error) {\n            setState(\"idle\")\n            fail(error)\n          }\n        })()\n      } else {\n        enter(\"idle\")\n      }\n    },\n    [clearTimer, enter, fail]\n  )\n\n  const confirm = React.useCallback(() => {\n    const { undo, onConfirm } = optionsRef.current\n    clearTimer()\n    if (!undo) return commit(onConfirm)\n    const undoWindow = startUndoWindow({\n      duration: undo,\n      onExpire: () => commit(onConfirm),\n      onPauseChange: setPaused,\n      within: () => triggerRef.current,\n    })\n    undoRef.current = { window: undoWindow, pausable: new Set() }\n    enter(\n      \"undo\",\n      undoWindow.manual\n        ? null\n        : {\n            from: 1,\n            to: 0,\n            duration: undoWindow.duration,\n            startedAt: performance.now(),\n          }\n    )\n    setPaused(undoWindow.paused())\n  }, [commit, clearTimer, enter, triggerRef])\n\n  const pauseUndo = React.useCallback((reason: PauseReason) => {\n    const { pauseUndoOnHover = true, pauseUndoOnFocus = true } =\n      optionsRef.current\n    if (reason === \"hover\" ? !pauseUndoOnHover : !pauseUndoOnFocus) return\n    const undo = undoRef.current\n    if (undo?.pausable.has(reason)) undo.window.pause(reason)\n  }, [])\n\n  const resumeUndo = React.useCallback((reason: PauseReason) => {\n    const undo = undoRef.current\n    if (!undo) return\n    undo.pausable.add(reason)\n    undo.window.resume(reason)\n  }, [])\n\n  const arm = React.useCallback(() => {\n    clearTimer()\n    enter(\"armed\")\n  }, [clearTimer, enter])\n\n  const hold = React.useCallback(\n    (duration: number) => {\n      const ms = toMs(duration, 1200, 800)\n      holdStartRef.current = performance.now()\n      holdDurationRef.current = ms\n      enter(\"holding\", {\n        from: 0,\n        to: 1,\n        duration: ms,\n        startedAt: holdStartRef.current,\n      })\n      timerRef.current = setTimeout(confirm, ms)\n    },\n    [confirm, enter]\n  )\n\n  const slide = React.useCallback(\n    (fraction: number) => {\n      enter(\"holding\", {\n        from: fraction,\n        to: fraction,\n        duration: 0,\n        startedAt: performance.now(),\n      })\n    },\n    [enter]\n  )\n\n  const release = React.useCallback(\n    (from?: number) => {\n      const now = performance.now()\n      const fraction =\n        from ??\n        Math.min((now - holdStartRef.current) / holdDurationRef.current, 1)\n      clearTimer()\n      enter(\"idle\", {\n        from: fraction,\n        to: 0,\n        duration: 200,\n        startedAt: now,\n        easing: \"ease-out\",\n      })\n      optionsRef.current.onCancel?.()\n    },\n    [clearTimer, enter]\n  )\n\n  const cancel = React.useCallback(() => {\n    clearTimer()\n    enter(\"idle\")\n    optionsRef.current.onCancel?.()\n  }, [clearTimer, enter])\n\n  const reset = React.useCallback(() => {\n    clearTimer()\n    enter(\"idle\")\n  }, [clearTimer, enter])\n\n  return {\n    state,\n    fill,\n    paused,\n    failed,\n    arm,\n    hold,\n    slide,\n    release,\n    confirm,\n    cancel,\n    pauseUndo,\n    resumeUndo,\n    reset,\n  }\n}\n\nfunction useConfirmation<\n  T extends Element = HTMLElement,\n  F extends Element = HTMLSpanElement,\n>({\n  gesture = \"click\",\n  timeout = 3000,\n  duration = 1200,\n  holdFallback = \"click-again\",\n  armDelay = 0,\n  wait = 0,\n  disabled = false,\n  ...options\n}: ConfirmationOptions & GestureOptions) {\n  const triggerRef = React.useRef<T | null>(null)\n  const {\n    state,\n    fill,\n    paused,\n    failed,\n    arm,\n    hold,\n    slide,\n    release,\n    confirm,\n    cancel,\n    pauseUndo,\n    resumeUndo,\n    reset,\n  } = useConfirmationMachine(options, triggerRef)\n  const fillRef = React.useRef<F>(null)\n  useFill(fillRef, fill, paused)\n  const [waiting, setWaiting] = React.useState(() => waitSeconds(wait))\n\n  React.useEffect(() => {\n    if (waiting <= 0) return\n    const timer = setTimeout(() => setWaiting((left) => left - 1), 1000)\n    return () => clearTimeout(timer)\n  }, [waiting])\n  const armDelayRef = React.useRef(armDelay)\n  const quietRef = React.useRef(false)\n  const quietTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n  const repeatRef = React.useRef(false)\n  const undoPressRef = React.useRef(false)\n  const pressRef = React.useRef<\"none\" | \"pointer\" | \"virtual\" | \"key\">(\"none\")\n  const fallbackRef = React.useRef(false)\n  const selectedRef = React.useRef(false)\n  const slideRef = React.useRef<{\n    id: number\n    startX: number\n    width: number\n    progress: number\n    moved: boolean\n  } | null>(null)\n  const slidClickRef = React.useRef(false)\n\n  React.useEffect(() => {\n    armDelayRef.current = armDelay\n  })\n\n  const quiet = React.useCallback(() => {\n    const ms = toMs(armDelayRef.current, 0, 0)\n    if (quietTimerRef.current !== null) clearTimeout(quietTimerRef.current)\n    quietTimerRef.current = null\n    quietRef.current = ms > 0\n    if (ms <= 0) return\n    quietTimerRef.current = setTimeout(() => {\n      quietTimerRef.current = null\n      quietRef.current = false\n    }, ms)\n  }, [])\n\n  React.useEffect(() => {\n    quiet()\n    return () => {\n      if (quietTimerRef.current !== null) clearTimeout(quietTimerRef.current)\n      quietTimerRef.current = null\n      quietRef.current = false\n    }\n  }, [quiet])\n\n  React.useEffect(() => {\n    if (state !== \"armed\") fallbackRef.current = false\n  }, [state])\n\n  React.useEffect(() => {\n    if (state !== \"armed\" || fallbackRef.current) return\n    const timer = setTimeout(cancel, toMs(timeout, 3000, 0))\n    return () => clearTimeout(timer)\n  }, [state, timeout, cancel])\n\n  const isHold = gesture === \"hold\"\n  const isSlide = gesture === \"slide\"\n  const isHolding = state === \"holding\"\n  const fallback = isHold && holdFallback === \"click-again\"\n  const fallbackArmed = state === \"armed\" && fallbackRef.current\n\n  function undoFromPress() {\n    if (state !== \"undo\" || !undoPressRef.current) return\n    undoPressRef.current = false\n    cancel()\n  }\n\n  function armNow() {\n    arm()\n    quiet()\n  }\n\n  function armFallback() {\n    fallbackRef.current = true\n    armNow()\n  }\n\n  function holdClick() {\n    const press = pressRef.current\n    pressRef.current = \"none\"\n    const unpressed = press === \"none\" || press === \"virtual\"\n    if (state === \"undo\") {\n      if (fallback && press === \"none\") undoPressRef.current = true\n      return undoFromPress()\n    }\n    if (!fallback) return\n    if (fallbackArmed) confirm()\n    else if (state === \"idle\" && unpressed) armFallback()\n  }\n\n  function endSlide(target: Element) {\n    const current = slideRef.current\n    if (!current) return\n    slideRef.current = null\n    if (target.hasPointerCapture?.(current.id))\n      target.releasePointerCapture(current.id)\n    if (!current.moved) return\n    slidClickRef.current = true\n    if (current.progress >= 1) confirm()\n    else release(current.progress)\n  }\n\n  const handlers: Required<Omit<TriggerProps<T>, \"disabled\">> = {\n    onClick() {\n      if (quietRef.current) return\n      if (slidClickRef.current) {\n        slidClickRef.current = false\n        return\n      }\n      if (isHold) return holdClick()\n      if (repeatRef.current) return\n      if (state === \"undo\") cancel()\n      else if (state === \"armed\") confirm()\n      else if (state === \"idle\" && gesture === \"click\") confirm()\n      else if (state === \"idle\") armNow()\n    },\n    onBlur() {\n      resumeUndo(\"focus\")\n      const sliding = slideRef.current\n      slideRef.current = null\n      if (isHolding) release(sliding?.progress)\n      else if (state === \"armed\") cancel()\n    },\n    onFocus() {\n      pauseUndo(\"focus\")\n    },\n    onPointerEnter() {\n      pauseUndo(\"hover\")\n    },\n    onPointerLeave() {\n      resumeUndo(\"hover\")\n      if (isHolding && !isSlide) release()\n    },\n    onPointerDown(event) {\n      selectedRef.current = hasSelection()\n      slidClickRef.current = false\n      if (isSlide) {\n        pressRef.current = isVirtualPress(event) ? \"virtual\" : \"pointer\"\n        if (event.button !== 0 || quietRef.current || state !== \"idle\") return\n        if (pressRef.current === \"virtual\") return\n        try {\n          event.currentTarget.setPointerCapture?.(event.pointerId)\n        } catch {}\n        slideRef.current = {\n          id: event.pointerId,\n          startX: event.clientX,\n          width: event.currentTarget.getBoundingClientRect().width,\n          progress: 0,\n          moved: false,\n        }\n        return\n      }\n      if (!isHold || event.button !== 0 || quietRef.current) return\n      pressRef.current = isVirtualPress(event) ? \"virtual\" : \"pointer\"\n      undoPressRef.current = state === \"undo\"\n      if (state !== \"idle\") return\n      const target = event.currentTarget\n      if (target.hasPointerCapture?.(event.pointerId))\n        target.releasePointerCapture(event.pointerId)\n      hold(duration)\n    },\n    onPointerMove(event) {\n      const current = slideRef.current\n      if (!current || current.id !== event.pointerId) return\n      const distance = event.clientX - current.startX\n      if (!current.moved && Math.abs(distance) < 4) return\n      current.moved = true\n      current.progress = Math.min(\n        Math.max(distance / (current.width * 0.85), 0),\n        1\n      )\n      slide(current.progress)\n    },\n    onPointerUp(event) {\n      if (event.pointerType === \"touch\" && !selectedRef.current) {\n        requestAnimationFrame(clearSelection)\n      }\n      if (isSlide) return endSlide(event.currentTarget)\n      if (state !== \"holding\") return\n      if (fallback && pressRef.current === \"virtual\") reset()\n      else release()\n    },\n    onPointerCancel() {\n      if (isSlide) {\n        const current = slideRef.current\n        slideRef.current = null\n        if (current?.moved) release(current.progress)\n        return\n      }\n      if (isHolding) release()\n    },\n    onKeyDown(event) {\n      if (!isHold) {\n        repeatRef.current = event.repeat\n        return\n      }\n      if (event.key === \"Escape\" && fallbackArmed) return cancel()\n      if (!isPressKey(event)) return\n      event.preventDefault()\n      if (event.repeat || quietRef.current) return\n      pressRef.current = \"key\"\n      undoPressRef.current = state === \"undo\"\n      if (state === \"idle\") hold(duration)\n    },\n    onKeyUp(event) {\n      if (!isHold) {\n        repeatRef.current = false\n        return\n      }\n      if (!isPressKey(event)) return\n      const pressed = pressRef.current === \"key\"\n      pressRef.current = \"none\"\n      if (state === \"holding\") release()\n      else if (fallbackArmed && pressed) confirm()\n      else undoFromPress()\n    },\n    onMouseDown(event) {\n      if (event.detail > 1) event.preventDefault()\n    },\n    onContextMenu(event) {\n      if (isHold) event.preventDefault()\n    },\n  }\n\n  function getTriggerProps<P extends TriggerProps<T>>(props: P) {\n    const composed = { ...props }\n    for (const name of Object.keys(handlers) as (keyof typeof handlers)[]) {\n      const handler = handlers[name] as (event: unknown) => void\n      const theirs = props[name] as ((event: unknown) => void) | undefined\n      const ours = (event: { currentTarget: T }) => {\n        triggerRef.current = event.currentTarget\n        handler(event)\n      }\n      Object.assign(composed, { [name]: composeHandlers(theirs, ours) })\n    }\n    return {\n      ...composed,\n      disabled:\n        (disabled && state !== \"undo\") || state === \"pending\" || waiting > 0,\n    }\n  }\n\n  return { state, failed, waiting, fillRef, getTriggerProps }\n}\n\ntype ConfirmationAnnouncements = {\n  hold?: string\n  armed?: string\n  fallback?: string\n  undo?: string\n  error?: string\n  wait?: string\n  slide?: string\n}\n\ninterface ConfirmationLabelOptions {\n  state: ConfirmationState\n  failed: boolean\n  gesture: NonNullable<GestureOptions[\"gesture\"]>\n  holdFallback: NonNullable<GestureOptions[\"holdFallback\"]>\n  undo: ConfirmationOptions[\"undo\"]\n  label: React.ReactNode\n  confirmLabel?: React.ReactNode\n  undoLabel?: React.ReactNode\n  errorLabel?: React.ReactNode\n  wait?: number\n  waiting?: number\n  waitLabel?: (seconds: number) => React.ReactNode\n  announcements?: ConfirmationAnnouncements\n  ariaLabel?: string\n  describedBy?: string\n}\n\nfunction waitSeconds(wait: number) {\n  return Math.ceil(toMs(wait, 0, 0) / 1000)\n}\n\nfunction text(node: React.ReactNode, fallback: string) {\n  return typeof node === \"string\" ? node : fallback\n}\n\nfunction useConfirmationLabels(options: ConfirmationLabelOptions) {\n  const {\n    state,\n    failed,\n    gesture,\n    holdFallback,\n    undo,\n    label,\n    confirmLabel,\n    undoLabel = \"Undo\",\n    errorLabel,\n    wait = 0,\n    waiting = 0,\n    waitLabel = (seconds: number) => `Wait ${seconds}s`,\n    announcements,\n    ariaLabel,\n    describedBy,\n  } = options\n  const hintId = React.useId()\n  const waitTotal = waitSeconds(wait)\n  const isHold = gesture === \"hold\"\n  const isSlide = gesture === \"slide\"\n  const showError = failed && state === \"idle\" && errorLabel != null\n  const shown =\n    state === \"armed\" || state === \"undo\"\n      ? state\n      : waiting > 0\n        ? \"wait\"\n        : showError\n          ? \"error\"\n          : \"idle\"\n  const labels: { state: string; node: React.ReactNode }[] = [\n    { state: \"idle\", node: label },\n  ]\n  if (\n    gesture === \"click-again\" ||\n    isSlide ||\n    (isHold && (confirmLabel != null || holdFallback !== \"none\"))\n  ) {\n    labels.push({\n      state: \"armed\",\n      node: confirmLabel ?? (isHold ? \"Confirm\" : \"Click again\"),\n    })\n  }\n  if (undo) labels.push({ state: \"undo\", node: undoLabel })\n  if (errorLabel != null) labels.push({ state: \"error\", node: errorLabel })\n  if (waitTotal > 0) {\n    labels.push({ state: \"wait\", node: waitLabel(waiting || waitTotal) })\n  }\n\n  let announcement = \"\"\n  if (shown === \"wait\") {\n    announcement = announcements?.wait ?? `Available in ${waitTotal} seconds`\n  } else if (state === \"armed\" && isHold) {\n    announcement = announcements?.fallback ?? \"Activate again to confirm\"\n  } else if (state === \"armed\") {\n    announcement =\n      announcements?.armed ?? text(confirmLabel, \"Click again to confirm\")\n  } else if (state === \"undo\") {\n    announcement = announcements?.undo ?? \"Done. Undo is available.\"\n  } else if (showError) {\n    announcement =\n      announcements?.error ??\n      text(errorLabel, \"Failed. Activate again to retry.\")\n  }\n\n  return {\n    shown,\n    labels,\n    ariaLabel:\n      ariaLabel && shown === \"undo\"\n        ? text(undoLabel, ariaLabel)\n        : ariaLabel && shown === \"error\"\n          ? text(errorLabel, ariaLabel)\n          : ariaLabel,\n    ariaDescribedBy:\n      isHold || isSlide\n        ? [hintId, describedBy].filter(Boolean).join(\" \")\n        : describedBy,\n    hint: isHold\n      ? {\n          id: hintId,\n          text:\n            announcements?.hold ??\n            (holdFallback === \"none\"\n              ? \"Press and hold to confirm\"\n              : \"Press and hold, or activate twice, to confirm\"),\n        }\n      : isSlide\n        ? {\n            id: hintId,\n            text:\n              announcements?.slide ??\n              \"Slide to the end, or activate twice, to confirm\",\n          }\n        : null,\n    announcement,\n  }\n}\n\nexport {\n  useConfirmation,\n  useConfirmationLabels,\n  composeHandlers,\n  isPromise,\n  playFill,\n  useFill,\n  startUndoWindow,\n  type ConfirmationState,\n  type ConfirmationAnnouncements,\n  type ConfirmationOptions,\n  type GestureOptions,\n  type Fill,\n  type UndoWindow,\n}\n",
      "type": "registry:lib",
      "target": "@ui/sureui/confirmation.ts"
    },
    {
      "path": "components/ui/sureui/confirm-button.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  useConfirmation,\n  useConfirmationLabels,\n  type ConfirmationAnnouncements,\n  type ConfirmationOptions,\n  type GestureOptions,\n} from \"@/components/ui/sureui/confirmation\"\n\ninterface ConfirmButtonProps\n  extends\n    React.ComponentProps<typeof Button>,\n    ConfirmationOptions,\n    GestureOptions {\n  confirmLabel?: React.ReactNode\n  undoLabel?: React.ReactNode\n  errorLabel?: React.ReactNode\n  waitLabel?: (seconds: number) => React.ReactNode\n  announcements?: ConfirmationAnnouncements\n}\n\nfunction ConfirmButton(props: ConfirmButtonProps) {\n  const {\n    onConfirm,\n    onCancel,\n    onConfirmError,\n    undo,\n    pauseUndoOnHover,\n    pauseUndoOnFocus,\n    gesture = \"click\",\n    timeout,\n    duration,\n    holdFallback = \"click-again\",\n    armDelay,\n    wait,\n    disabled,\n    confirmLabel,\n    undoLabel,\n    errorLabel,\n    waitLabel,\n    announcements,\n    className,\n    children,\n    \"aria-describedby\": describedBy,\n    ...rest\n  } = props\n  const { state, failed, waiting, fillRef, getTriggerProps } =\n    useConfirmation<HTMLButtonElement>({\n      onConfirm,\n      onCancel,\n      onConfirmError,\n      undo,\n      pauseUndoOnHover,\n      pauseUndoOnFocus,\n      gesture,\n      timeout,\n      duration,\n      holdFallback,\n      armDelay,\n      wait,\n      disabled,\n    })\n  const { shown, labels, ariaLabel, ariaDescribedBy, hint, announcement } =\n    useConfirmationLabels({\n      state,\n      failed,\n      gesture,\n      holdFallback,\n      undo,\n      label: children,\n      confirmLabel,\n      undoLabel,\n      errorLabel,\n      wait,\n      waiting,\n      waitLabel,\n      announcements,\n      ariaLabel: rest[\"aria-label\"],\n      describedBy,\n    })\n\n  return (\n    <>\n      <Button\n        {...getTriggerProps(rest)}\n        aria-label={ariaLabel}\n        aria-describedby={ariaDescribedBy}\n        data-state={state}\n        data-error={failed || undefined}\n        className={cn(\n          \"relative overflow-hidden transition-[color,background-color,border-color,box-shadow] active:not-aria-[haspopup]:translate-y-0 aria-disabled:opacity-50 motion-safe:data-[state=pending]:animate-pulse motion-safe:aria-disabled:data-[state=pending]:opacity-100\",\n          gesture === \"hold\"\n            ? \"touch-none\"\n            : gesture === \"slide\"\n              ? \"touch-pan-y\"\n              : \"touch-manipulation\",\n          className\n        )}\n        focusableWhenDisabled={\n          rest.focusableWhenDisabled || state === \"pending\"\n        }\n      >\n        <span\n          ref={fillRef}\n          aria-hidden\n          className=\"absolute inset-0 origin-left scale-x-0 bg-current opacity-20\"\n        />\n        <span className=\"grid gap-[inherit]\">\n          {labels.map((label) => (\n            <span\n              key={label.state}\n              aria-hidden={label.state !== shown || undefined}\n              className={cn(\n                \"col-start-1 row-start-1 inline-flex items-center justify-center gap-[inherit]\",\n                label.state !== shown && \"invisible\"\n              )}\n            >\n              {label.node}\n            </span>\n          ))}\n        </span>\n      </Button>\n      {hint && (\n        <span id={hint.id} className=\"sr-only\">\n          {hint.text}\n        </span>\n      )}\n      <span aria-live=\"polite\" className=\"sr-only\">\n        {announcement}\n      </span>\n    </>\n  )\n}\n\nexport { ConfirmButton, type ConfirmButtonProps }\n",
      "type": "registry:ui",
      "target": "@ui/sureui/confirm-button.tsx"
    }
  ],
  "categories": [
    "confirmation",
    "button"
  ],
  "type": "registry:ui"
}