{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "number-field",
  "title": "Number Field",
  "description": "Numeric input with stepper buttons, min/max/step, keyboard control and Intl number formatting",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@boldkit/utils"
  ],
  "files": [
    {
      "path": "registry/default/ui/number-field.tsx",
      "content": "import * as React from 'react'\nimport { Minus, Plus } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\n// ── Number helpers ───────────────────────────────────────────────────────────\n//\n// Radix has no NumberField, so this mirrors Reka UI's NumberField behaviour\n// (which the Vue version is built on): snap to the `step` grid anchored at\n// `min`, clamp to [min, max], and round-trip through the formatter so the\n// stored value never holds more precision than is displayed.\n\nconst decimalsOf = (n: number) => {\n  const [, fraction = ''] = String(n).split('.')\n  return fraction.length\n}\n\nconst clamp = (n: number, min?: number, max?: number) =>\n  Math.min(max ?? Infinity, Math.max(min ?? -Infinity, n))\n\nconst escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\ninterface NumberCodec {\n  format: (n: number) => string\n  parse: (text: string) => number\n  /** Whether `text` is something a user could be part-way through typing. */\n  isPartial: (text: string) => boolean\n  hasDecimals: boolean\n  /**\n   * Compact notation (\"1.5K\") rounds for display, so the displayed string is\n   * not a faithful copy of the value and must not be used to cap precision.\n   */\n  isLossy: boolean\n}\n\n// JS `\\s` already covers NBSP and the narrow NBSP used by fr/ru grouping.\nconst SPACES = /\\s/g\n\nfunction createCodec(locale: string | undefined, options: Intl.NumberFormatOptions | undefined): NumberCodec {\n  const formatter = new Intl.NumberFormat(locale, options)\n  const { numberingSystem } = formatter.resolvedOptions()\n  const isCompact = options?.notation === 'compact'\n  const isPercent = options?.style === 'percent'\n\n  // Separators come from a plain formatter in the same numbering system:\n  // compact output (\"-11K\") has no decimal part to read them from.\n  const baseParts = new Intl.NumberFormat(locale, { numberingSystem }).formatToParts(-11111.1)\n  const decimal = baseParts.find((p) => p.type === 'decimal')?.value ?? '.'\n  const group = baseParts.find((p) => p.type === 'group')?.value ?? ','\n\n  // Locales such as ar-EG, fa-IR and bn format with their own digits; map them\n  // back to ASCII before parsing (ASCII digits are always accepted too).\n  const digitFormatter = new Intl.NumberFormat(locale, { numberingSystem, useGrouping: false })\n  const localDigits = Array.from({ length: 10 }, (_, i) => digitFormatter.format(i))\n  const toAsciiDigits = (text: string) =>\n    [...text].map((ch) => {\n      const i = localDigits.indexOf(ch)\n      return i === -1 ? ch : String(i)\n    }).join('')\n\n  // Compact suffixes (\"K\", \"M\", \"万\"…) with the magnitude each stands for,\n  // longest first so \"Mio.\" wins over \"M\".\n  const compactSuffixes: Array<[string, number]> = []\n  if (isCompact) {\n    for (let power = 1; power <= 15; power++) {\n      const suffix = formatter.formatToParts(10 ** power).find((p) => p.type === 'compact')?.value\n      if (suffix && !compactSuffixes.some(([s]) => s === suffix)) compactSuffixes.push([suffix, 10 ** power])\n    }\n    compactSuffixes.sort((a, b) => b[0].length - a[0].length)\n  }\n\n  // Every character the formatter can emit (currency symbols, %, accounting\n  // parentheses, unit labels, bidi marks) plus digits and minus signs is\n  // acceptable while typing.\n  const emitted = [\n    ...formatter.formatToParts(-11111.1),\n    ...formatter.formatToParts(-1.5),\n  ]\n    .filter((p) => p.type !== 'integer' && p.type !== 'fraction')\n    .map((p) => p.value)\n    .join('')\n  const allowed = new Set([\n    ...'0123456789-−+',\n    ...localDigits.join(''),\n    ...emitted,\n    ...compactSuffixes.map(([s]) => s).join(''),\n    decimal,\n    group,\n    ' ',\n    String.fromCharCode(0xa0),\n    String.fromCharCode(0x202f),\n  ])\n\n  return {\n    format: (n) => formatter.format(n),\n    parse: (text) => {\n      let rest = toAsciiDigits(text)\n      let magnitude = 1\n      const suffix = compactSuffixes.find(([s]) => rest.includes(s))\n      if (suffix) {\n        rest = rest.replace(suffix[0], '')\n        magnitude = suffix[1]\n      }\n      // Accounting format writes negatives as \"($5.00)\".\n      const parenthesised = /\\(.*\\)/.test(rest)\n      const normalised = rest\n        .replace(new RegExp(escapeRegExp(group), 'g'), '')\n        .replace(SPACES, '')\n        .replace(new RegExp(escapeRegExp(decimal), 'g'), '.')\n        .replace(/−/g, '-')\n        .replace(/[^\\d.-]/g, '')\n      if (!/\\d/.test(normalised)) return NaN\n      let n = Number(normalised) * magnitude\n      if (parenthesised) n = -Math.abs(n)\n      return isPercent ? n / 100 : n\n    },\n    isPartial: (text) => [...text].every((ch) => allowed.has(ch)),\n    hasDecimals: isCompact || (formatter.resolvedOptions().maximumFractionDigits ?? 0) > 0,\n    isLossy: isCompact,\n  }\n}\n\n// ── Context ──────────────────────────────────────────────────────────────────\n\ninterface NumberFieldContextValue {\n  inputId: string\n  inputRef: React.MutableRefObject<HTMLInputElement | null>\n  registerInput: (node: HTMLInputElement | null) => void\n  value: number | undefined\n  text: string\n  setText: (text: string) => void\n  commit: (text: string) => void\n  increment: (multiplier?: number) => void\n  decrement: (multiplier?: number) => void\n  toMin: () => void\n  toMax: () => void\n  canIncrement: boolean\n  canDecrement: boolean\n  isPartial: (text: string) => boolean\n  inputMode: 'decimal' | 'numeric'\n  min?: number\n  max?: number\n  disabled: boolean\n  readOnly: boolean\n}\n\nconst NumberFieldContext = React.createContext<NumberFieldContextValue | null>(null)\n\nfunction useNumberField(part: string) {\n  const ctx = React.useContext(NumberFieldContext)\n  if (!ctx) throw new Error(`<${part}> must be used within <NumberField>`)\n  return ctx\n}\n\n// ── Root ─────────────────────────────────────────────────────────────────────\n\ninterface NumberFieldProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, 'defaultValue' | 'onChange'> {\n  /**\n   * Controlled value. Passing the prop at all makes the field controlled, so\n   * `undefined` or `null` mean \"controlled and empty\" — feeding\n   * `onValueChange`'s `undefined` straight back in keeps it controlled.\n   */\n  value?: number | null\n  /** Uncontrolled initial value */\n  defaultValue?: number\n  /** Called with the new value (`undefined` when the field is cleared) */\n  onValueChange?: (value: number | undefined) => void\n  min?: number\n  max?: number\n  /** Amount each increment/decrement changes the value (default 1). PageUp/PageDown use ×10. */\n  step?: number\n  /** Intl.NumberFormat options for the displayed value (currency, percent, unit…) */\n  formatOptions?: Intl.NumberFormatOptions\n  /** Locale used for formatting (defaults to the browser locale) */\n  locale?: string\n  disabled?: boolean\n  readOnly?: boolean\n  required?: boolean\n  /** Submits the raw number under this name in native forms */\n  name?: string\n  /** Id applied to the input, so a <Label htmlFor> can target it */\n  id?: string\n  /** Stop the mouse wheel from changing the value while the input is focused */\n  disableWheelChange?: boolean\n  /** Reverse the wheel direction */\n  invertWheelChange?: boolean\n}\n\nconst NumberField = React.forwardRef<HTMLDivElement, NumberFieldProps>(\n  (allProps, ref) => {\n    const {\n      value: controlledValue,\n      defaultValue,\n      onValueChange,\n      min,\n      max,\n      step = 1,\n      formatOptions,\n      locale,\n      disabled = false,\n      readOnly = false,\n      required,\n      name,\n      id,\n      disableWheelChange = false,\n      invertWheelChange = false,\n      className,\n      children,\n      ...props\n    } = allProps\n    const generatedId = React.useId()\n    const inputId = id ?? `number-field-${generatedId.replace(/:/g, '')}`\n    const inputRef = React.useRef<HTMLInputElement | null>(null)\n\n    // Mouse wheel steps the value while the input is focused, as in Reka.\n    // React's onWheel is passive (it can't preventDefault the page scroll), so\n    // the listener is bound natively when the input registers. It forwards to\n    // the latest render's handler.\n    const wheelHandler = React.useRef<(e: WheelEvent) => void>(() => {})\n    const onWheel = React.useCallback((e: WheelEvent) => wheelHandler.current(e), [])\n    const registerInput = React.useCallback(\n      (node: HTMLInputElement | null) => {\n        if (inputRef.current === node) return\n        inputRef.current?.removeEventListener('wheel', onWheel)\n        node?.addEventListener('wheel', onWheel, { passive: false })\n        inputRef.current = node\n      },\n      [onWheel]\n    )\n\n    // Presence, not definedness: `value={undefined}` is a controlled empty field.\n    const isControlled = 'value' in allProps\n    const [internalValue, setInternalValue] = React.useState<number | undefined>(defaultValue)\n    const value = isControlled ? (controlledValue ?? undefined) : internalValue\n\n    // Key on the serialised options so an inline object literal doesn't\n    // rebuild the formatter (and reset the typed text) on every render.\n    const formatKey = JSON.stringify(formatOptions ?? {})\n    const codec = React.useMemo(\n      () => createCodec(locale, JSON.parse(formatKey) as Intl.NumberFormatOptions),\n      [locale, formatKey]\n    )\n\n    const formatValue = React.useCallback(\n      (n: number | undefined) => (n === undefined || Number.isNaN(n) ? '' : codec.format(n)),\n      [codec]\n    )\n\n    const [text, setText] = React.useState(() => formatValue(value))\n\n    // Re-sync the visible text whenever the value (or its format) changes —\n    // adjusted during render rather than in an effect, so there is no frame\n    // showing stale text.\n    const [synced, setSynced] = React.useState({ value, formatValue })\n    if (synced.value !== value || synced.formatValue !== formatValue) {\n      setSynced({ value, formatValue })\n      setText(formatValue(value))\n    }\n\n    const precision = Math.max(decimalsOf(step), decimalsOf(min ?? 0))\n    const round = React.useCallback((n: number) => Number(n.toFixed(precision)), [precision])\n\n    /** Snap to the step grid (anchored at `min`), clamp, then round-trip the formatter. */\n    const normalise = React.useCallback(\n      (n: number) => {\n        const base = min ?? 0\n        let snapped = round(Math.round((n - base) / step) * step + base)\n        if (max !== undefined && snapped > max) snapped = round(Math.floor((max - base) / step) * step + base)\n        const clamped = clamp(snapped, min, max)\n        if (codec.isLossy) return clamped\n        // Only trust the round-trip when it really is the same displayed number.\n        const roundTripped = codec.parse(codec.format(clamped))\n        return Number.isNaN(roundTripped) || codec.format(roundTripped) !== codec.format(clamped)\n          ? clamped\n          : roundTripped\n      },\n      [min, max, step, round, codec]\n    )\n\n    const getNext = React.useCallback(\n      (direction: 1 | -1, from: number, multiplier = 1) => {\n        const base = min ?? 0\n        const snapped = round(Math.round((from - base) / step) * step + base)\n        let next: number\n        if (snapped === from) {\n          next = from + direction * step * multiplier\n        } else {\n          // Off-grid: the first tick lands on the next grid point in that direction.\n          const aligned =\n            direction === 1\n              ? snapped > from ? snapped : snapped + step\n              : snapped < from ? snapped : snapped - step\n          next = aligned + direction * step * (multiplier - 1)\n        }\n        return normalise(round(next))\n      },\n      [min, step, round, normalise]\n    )\n\n    const setValue = React.useCallback(\n      (next: number | undefined) => {\n        if (!isControlled) setInternalValue(next)\n        if (next !== value) onValueChange?.(next)\n        // Controlled: show the current value; if the parent accepts `next`,\n        // the render-time sync above re-formats it.\n        setText(formatValue(isControlled ? value : next))\n      },\n      [isControlled, value, onValueChange, formatValue]\n    )\n\n    const interactive = !disabled && !readOnly\n\n    /**\n     * The value the text stands for. Untouched display text maps to the stored\n     * value rather than being re-parsed, since formats like compact (\"1.5K\"\n     * for 1499) don't round-trip exactly.\n     */\n    const parseText = (raw: string) =>\n      value !== undefined && raw === formatValue(value) ? value : codec.parse(raw)\n\n    const commit = (raw: string) => {\n      if (!interactive) return\n      const parsed = parseText(raw)\n      setValue(Number.isNaN(parsed) ? undefined : normalise(parsed))\n    }\n\n    const change = (direction: 1 | -1, multiplier = 1) => {\n      if (!interactive) return\n      // Uncommitted typed text counts as the current value, as in Reka.\n      const current = parseText(text)\n      if (Number.isNaN(current)) {\n        setValue(normalise(min ?? 0))\n        return\n      }\n      const next = getNext(direction, current, multiplier)\n      if (direction === 1 ? next < current : next > current) return\n      setValue(next)\n    }\n\n    React.useLayoutEffect(() => {\n      wheelHandler.current = (e) => {\n        const node = inputRef.current\n        if (disableWheelChange || !interactive || !node) return\n        if (node.ownerDocument.activeElement !== node) return\n        if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return\n        e.preventDefault()\n        // Same direction as Reka: positive deltaY increases unless inverted.\n        change(e.deltaY > 0 !== invertWheelChange ? 1 : -1)\n      }\n    })\n\n    const canIncrement = value === undefined || getNext(1, value) > value\n    const canDecrement = value === undefined || getNext(-1, value) < value\n\n    const ctx: NumberFieldContextValue = {\n      inputId,\n      inputRef,\n      registerInput,\n      value,\n      text,\n      setText,\n      commit,\n      increment: (m) => change(1, m),\n      decrement: (m) => change(-1, m),\n      toMin: () => {\n        if (interactive && min !== undefined) setValue(normalise(min))\n      },\n      toMax: () => {\n        if (interactive && max !== undefined) setValue(normalise(max))\n      },\n      canIncrement,\n      canDecrement,\n      isPartial: codec.isPartial,\n      inputMode: codec.hasDecimals ? 'decimal' : 'numeric',\n      min,\n      max,\n      disabled,\n      readOnly,\n    }\n\n    return (\n      <NumberFieldContext.Provider value={ctx}>\n        <div\n          ref={ref}\n          role=\"group\"\n          data-slot=\"number-field\"\n          data-disabled={disabled ? '' : undefined}\n          data-readonly={readOnly ? '' : undefined}\n          className={cn('grid gap-1.5', className)}\n          {...props}\n        >\n          {children}\n          {name && (\n            <input\n              type=\"hidden\"\n              name={name}\n              value={value ?? ''}\n              disabled={disabled}\n              required={required}\n            />\n          )}\n        </div>\n      </NumberFieldContext.Provider>\n    )\n  }\n)\nNumberField.displayName = 'NumberField'\n\n// ── Content (the bordered box) ───────────────────────────────────────────────\n\nconst NumberFieldContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => (\n    <div\n      ref={ref}\n      data-slot=\"number-field-content\"\n      className={cn(\n        'flex h-11 w-full items-stretch border-3 border-foreground bg-background shadow-[4px_4px_0px_hsl(var(--shadow-color))] bk-interactive focus-within:translate-x-[4px] focus-within:translate-y-[4px] focus-within:shadow-none has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50',\n        className\n      )}\n      {...props}\n    />\n  )\n)\nNumberFieldContent.displayName = 'NumberFieldContent'\n\n// ── Input ────────────────────────────────────────────────────────────────────\n\ntype NumberFieldInputProps = Omit<\n  React.InputHTMLAttributes<HTMLInputElement>,\n  'value' | 'defaultValue' | 'type' | 'min' | 'max' | 'step'\n>\n\nconst NumberFieldInput = React.forwardRef<HTMLInputElement, NumberFieldInputProps>(\n  ({ className, onKeyDown, onBlur, onChange, ...props }, ref) => {\n    const ctx = useNumberField('NumberFieldInput')\n\n\n    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n      onKeyDown?.(e)\n      if (e.defaultPrevented || e.nativeEvent.isComposing) return\n      const actions: Record<string, () => void> = {\n        ArrowUp: () => ctx.increment(),\n        ArrowDown: () => ctx.decrement(),\n        PageUp: () => ctx.increment(10),\n        PageDown: () => ctx.decrement(10),\n        Home: () => ctx.toMin(),\n        End: () => ctx.toMax(),\n      }\n      if (e.key === 'Enter') {\n        ctx.commit(e.currentTarget.value)\n        return\n      }\n      const action = actions[e.key]\n      if (!action) return\n      e.preventDefault()\n      action()\n    }\n\n    return (\n      <input\n        ref={(node) => {\n          ctx.registerInput(node)\n          if (typeof ref === 'function') ref(node)\n          else if (ref) ref.current = node\n        }}\n        id={ctx.inputId}\n        type=\"text\"\n        role=\"spinbutton\"\n        inputMode={ctx.inputMode}\n        autoComplete=\"off\"\n        autoCorrect=\"off\"\n        spellCheck={false}\n        data-slot=\"number-field-input\"\n        aria-valuenow={ctx.value}\n        aria-valuemin={ctx.min}\n        aria-valuemax={ctx.max}\n        aria-valuetext={ctx.text || undefined}\n        disabled={ctx.disabled}\n        readOnly={ctx.readOnly}\n        value={ctx.text}\n        onChange={(e) => {\n          onChange?.(e)\n          // Reject characters that can never form a number in this format.\n          if (ctx.isPartial(e.target.value)) ctx.setText(e.target.value)\n        }}\n        onBlur={(e) => {\n          onBlur?.(e)\n          ctx.commit(e.currentTarget.value)\n        }}\n        onKeyDown={handleKeyDown}\n        className={cn(\n          'w-full min-w-0 flex-1 bg-transparent px-3 text-center text-base font-bold tabular-nums placeholder:font-normal placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed md:text-sm',\n          className\n        )}\n        {...props}\n      />\n    )\n  }\n)\nNumberFieldInput.displayName = 'NumberFieldInput'\n\n// ── Stepper buttons ──────────────────────────────────────────────────────────\n\nconst stepperClasses =\n  'flex w-11 shrink-0 items-center justify-center border-foreground bg-secondary text-secondary-foreground transition-[background-color,box-shadow] duration-100 hover:bg-primary hover:text-primary-foreground active:shadow-[inset_3px_3px_0px_hsl(var(--shadow-color))] focus-visible:outline-none disabled:pointer-events-none disabled:opacity-40 [&_svg]:size-4 [&_svg]:stroke-[3]'\n\ntype StepperProps = React.ButtonHTMLAttributes<HTMLButtonElement>\n\n// Press-and-hold timing, matching Reka's NumberField.\nconst HOLD_DELAY = 400\nconst REPEAT_INTERVAL = 60\nconst TOUCH_MOVE_TOLERANCE = 10\n\n/**\n * Press-and-hold auto-repeat, mirroring Reka: a mouse press steps at once and\n * then repeats after HOLD_DELAY every REPEAT_INTERVAL; a touch/pen press steps\n * on release (so a scroll gesture starting on the button doesn't step) unless\n * it was held long enough to repeat. Stops on pointerup/cancel, leaving the\n * button, hitting a bound, or unmount.\n */\nfunction useStepper(direction: 1 | -1, part: string, disabledProp?: boolean) {\n  const ctx = useNumberField(part)\n  const canMove = direction === 1 ? ctx.canIncrement : ctx.canDecrement\n  const isDisabled = Boolean(disabledProp) || ctx.disabled || ctx.readOnly || !canMove\n\n  const step = () => {\n    if (direction === 1) ctx.increment()\n    else ctx.decrement()\n    // Keep focus on the spinbutton so arrow keys keep working, as Reka does.\n    ctx.inputRef.current?.focus()\n  }\n\n  // Timers fire after re-renders, so they must call the latest closure.\n  const latest = React.useRef({ step, isDisabled })\n  React.useLayoutEffect(() => {\n    latest.current = { step, isDisabled }\n  })\n\n  const timer = React.useRef<number | undefined>(undefined)\n  const release = React.useRef<(() => void) | null>(null)\n\n  const stop = React.useCallback(() => {\n    window.clearTimeout(timer.current)\n    timer.current = undefined\n    release.current?.()\n    release.current = null\n  }, [])\n  React.useEffect(() => stop, [stop])\n\n  const fire = () => {\n    if (latest.current.isDisabled) {\n      stop()\n      return false\n    }\n    latest.current.step()\n    return true\n  }\n\n  const onPointerDown = (e: React.PointerEvent<HTMLButtonElement>) => {\n    if (e.button !== 0 || isDisabled) return\n    e.preventDefault()\n    stop()\n    const { pointerId, pointerType, clientX, clientY } = e\n    let held = false\n    const repeat = (delay: number) => {\n      timer.current = window.setTimeout(() => {\n        held = true\n        if (fire()) repeat(REPEAT_INTERVAL)\n      }, delay)\n    }\n    const onMove = (ev: PointerEvent) => {\n      if (ev.pointerId !== pointerId || pointerType === 'mouse') return\n      const dx = ev.clientX - clientX\n      const dy = ev.clientY - clientY\n      if (dx * dx + dy * dy > TOUCH_MOVE_TOLERANCE ** 2) stop()\n    }\n    const onUp = (ev: PointerEvent) => {\n      if (ev.pointerId !== pointerId) return\n      const tap = pointerType !== 'mouse' && !held\n      stop()\n      if (tap) fire()\n    }\n    const onCancel = (ev: PointerEvent) => {\n      if (ev.pointerId === pointerId) stop()\n    }\n    window.addEventListener('pointermove', onMove)\n    window.addEventListener('pointerup', onUp)\n    window.addEventListener('pointercancel', onCancel)\n    release.current = () => {\n      window.removeEventListener('pointermove', onMove)\n      window.removeEventListener('pointerup', onUp)\n      window.removeEventListener('pointercancel', onCancel)\n    }\n    if (pointerType === 'mouse') {\n      held = true\n      if (fire()) repeat(HOLD_DELAY)\n    } else {\n      repeat(HOLD_DELAY)\n    }\n  }\n\n  const onPointerLeave = (e: React.PointerEvent<HTMLButtonElement>) => {\n    if (e.pointerType === 'mouse') stop()\n  }\n\n  // Pointer presses are handled above; a click with detail 0 comes from\n  // assistive tech or a synthetic activation, which has no pointerdown.\n  const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n    if (e.detail === 0 && !isDisabled) step()\n  }\n\n  // Stop the button taking focus from the input on mouse press.\n  const onMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => e.preventDefault()\n\n  return { isDisabled, onPointerDown, onPointerLeave, onClick, onMouseDown }\n}\n\nfunction createStepper(direction: 1 | -1, part: string, label: string, slot: string, icon: React.ReactNode, border: string) {\n  const Stepper = React.forwardRef<HTMLButtonElement, StepperProps>(\n    ({ className, children, disabled, onClick, onPointerDown, onPointerLeave, onMouseDown, ...props }, ref) => {\n      const stepper = useStepper(direction, part, disabled)\n      return (\n        <button\n          ref={ref}\n          type=\"button\"\n          tabIndex={-1}\n          aria-label={label}\n          data-slot={slot}\n          disabled={stepper.isDisabled}\n          onPointerDown={(e) => {\n            onPointerDown?.(e)\n            if (!e.defaultPrevented) stepper.onPointerDown(e)\n          }}\n          onPointerLeave={(e) => {\n            onPointerLeave?.(e)\n            stepper.onPointerLeave(e)\n          }}\n          onMouseDown={(e) => {\n            onMouseDown?.(e)\n            stepper.onMouseDown(e)\n          }}\n          onClick={(e) => {\n            onClick?.(e)\n            if (!e.defaultPrevented) stepper.onClick(e)\n          }}\n          onContextMenu={(e) => e.preventDefault()}\n          className={cn(stepperClasses, border, className)}\n          {...props}\n        >\n          {children ?? icon}\n        </button>\n      )\n    }\n  )\n  Stepper.displayName = part\n  return Stepper\n}\n\nconst NumberFieldIncrement = createStepper(\n  1,\n  'NumberFieldIncrement',\n  'Increase',\n  'number-field-increment',\n  <Plus aria-hidden=\"true\" />,\n  'border-l-3'\n)\n\nconst NumberFieldDecrement = createStepper(\n  -1,\n  'NumberFieldDecrement',\n  'Decrease',\n  'number-field-decrement',\n  <Minus aria-hidden=\"true\" />,\n  'border-r-3'\n)\n\nexport {\n  NumberField,\n  NumberFieldContent,\n  NumberFieldInput,\n  NumberFieldIncrement,\n  NumberFieldDecrement,\n}\nexport type { NumberFieldProps }\n",
      "type": "registry:ui",
      "target": "components/ui/number-field.tsx"
    }
  ],
  "type": "registry:ui"
}