{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "number-field",
  "title": "Number Field",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/number-field/NumberField.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { NumberFieldControlsPosition, NumberFieldSize, NumberFieldStatus } from './NumberFieldContext'\n\n  export type { NumberFieldControlsPosition, NumberFieldSize, NumberFieldStatus }\n\n  export interface NumberFieldProps extends HTMLAttributes<HTMLDivElement> {\n    value?: number | undefined\n    defaultValue?: number | undefined\n    min?: number\n    max?: number\n    step?: number\n    disabled?: boolean\n    /** Primary readonly API (React `readOnly` parity). Wins over `readonly` when both are set. */\n    readOnly?: boolean\n    /** @deprecated Use `readOnly`. Kept as an alias — both stay functional. */\n    readonly?: boolean\n    required?: boolean\n    name?: string\n    /** Forwarded to the inner input (React parity). To style or anchor the root container, wrap in a styled element. */\n    id?: string\n    /** Forwarded to the inner input (React parity). */\n    placeholder?: string\n    inputMode?: 'decimal' | 'numeric'\n    formatOptions?: Intl.NumberFormatOptions\n    disableWheelChange?: boolean\n    invertWheelChange?: boolean\n    size?: NumberFieldSize\n    status?: NumberFieldStatus\n    controlsPosition?: NumberFieldControlsPosition\n    keyboard?: boolean\n    precision?: number\n    formatter?: (value: number | undefined) => string\n    parser?: (displayValue: string) => number | undefined\n    prefix?: string\n    suffix?: string\n    ref?: HTMLDivElement | null\n    onValueChange?: (value: number | undefined) => void\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { setNumberFieldContext } from './NumberFieldContext'\n\n  let {\n    class: className,\n    value = $bindable(),\n    defaultValue,\n    min,\n    max,\n    step = 1,\n    disabled = false,\n    readOnly,\n    readonly = false,\n    required = false,\n    name,\n    id,\n    placeholder,\n    inputMode = 'decimal',\n    formatOptions,\n    disableWheelChange = false,\n    invertWheelChange = false,\n    size = 'middle',\n    status,\n    controlsPosition = 'default',\n    keyboard = true,\n    precision,\n    formatter,\n    parser,\n    prefix,\n    suffix,\n    children,\n    ref = $bindable(null),\n    onValueChange,\n    ...restProps\n  }: NumberFieldProps = $props()\n\n  const effectiveReadonly = $derived(readOnly ?? readonly ?? false)\n\n  // Uncontrolled mode: seed the bindable from defaultValue on first read.\n  // ($bindable() with no parent binding is local state; assigning the default\n  // once mirrors reka's defaultValue without fighting a controlled parent —\n  // the guard only runs while the parent hasn't provided a value.)\n  let seeded = false\n  $effect.pre(() => {\n    if (!seeded && value === undefined && defaultValue !== undefined) {\n      value = defaultValue\n    }\n    seeded = true\n  })\n\n  const resolvedFormatOptions = $derived.by((): Intl.NumberFormatOptions | undefined => {\n    if (precision !== undefined) {\n      return { ...formatOptions, minimumFractionDigits: precision, maximumFractionDigits: precision }\n    }\n    return formatOptions\n  })\n\n  function clamp(v: number): number {\n    let out = v\n    if (min !== undefined) out = Math.max(min, out)\n    if (max !== undefined) out = Math.min(max, out)\n    return out\n  }\n\n  function roundToStepPrecision(v: number): number {\n    // Avoid 0.1 + 0.2 style float drift on stepped changes.\n    const decimals = Math.max(\n      (String(step).split('.')[1] ?? '').length,\n      precision ?? 0,\n      (String(min ?? '').split('.')[1] ?? '').length,\n    )\n    if (decimals === 0) return Math.round(v)\n    const f = 10 ** decimals\n    return Math.round(v * f) / f\n  }\n\n  function setValue(v: number | undefined) {\n    if (disabled || effectiveReadonly) return\n    const next = v === undefined ? undefined : clamp(v)\n    value = next\n    onValueChange?.(next)\n  }\n\n  function handleIncrease(times = 1) {\n    if (disabled || effectiveReadonly) return\n    const base = value ?? (min !== undefined ? min - step * times : 0)\n    setValue(roundToStepPrecision(clamp(base + step * times)))\n  }\n\n  function handleDecrease(times = 1) {\n    if (disabled || effectiveReadonly) return\n    const base = value ?? (max !== undefined ? max + step * times : 0)\n    setValue(roundToStepPrecision(clamp(base - step * times)))\n  }\n\n  function handleMinMaxValue(which: 'min' | 'max') {\n    if (disabled || effectiveReadonly) return\n    if (which === 'min' && min !== undefined) setValue(min)\n    if (which === 'max' && max !== undefined) setValue(max)\n  }\n\n  function applyInputValue(displayValue: string) {\n    if (disabled || effectiveReadonly) return\n    const num = Number(displayValue)\n    if (displayValue.trim() === '') setValue(undefined)\n    else if (!Number.isNaN(num)) setValue(roundToStepPrecision(num))\n  }\n\n  function formatValue(v: number | undefined): string {\n    if (v === undefined || Number.isNaN(v)) return ''\n    if (formatter) return formatter(v)\n    if (resolvedFormatOptions) {\n      try {\n        return new Intl.NumberFormat(undefined, resolvedFormatOptions).format(v)\n      } catch {\n        return String(v)\n      }\n    }\n    return String(v)\n  }\n\n  setNumberFieldContext({\n    getValue: () => value,\n    setValue,\n    handleIncrease,\n    handleDecrease,\n    handleMinMaxValue,\n    applyInputValue,\n    formatValue,\n    getMin: () => min,\n    getMax: () => max,\n    isDisabled: () => disabled,\n    isReadonly: () => effectiveReadonly,\n    getInputMode: () => inputMode,\n    isWheelChangeDisabled: () => disableWheelChange,\n    isWheelChangeInverted: () => invertWheelChange,\n    getSize: () => size,\n    getStatus: () => status,\n    getControlsPosition: () => controlsPosition,\n    isKeyboardEnabled: () => keyboard,\n    getFormatter: () => formatter,\n    getParser: () => parser,\n    getPrefix: () => prefix,\n    getSuffix: () => suffix,\n    getId: () => id,\n    getPlaceholder: () => placeholder,\n  })\n</script>\n\n<div bind:this={ref} data-uipkge=\"\" data-slot=\"number-field\" class={cn('inline-flex', className)} {...restProps}>\n  {@render children?.()}\n  {#if name}\n    <input type=\"hidden\" {name} value={value ?? ''} {required} {disabled} />\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/number-field/NumberField.svelte"
    },
    {
      "path": "packages/registry-svelte/components/number-field/NumberFieldContent.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface NumberFieldContentProps extends HTMLAttributes<HTMLDivElement> {\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { cn } from '$lib/utils'\n  import { getNumberFieldContext } from './NumberFieldContext'\n\n  let { class: className, children, ref = $bindable(null), ...restProps }: NumberFieldContentProps = $props()\n\n  const uiContext = getNumberFieldContext()\n  const isRight = $derived(uiContext.getControlsPosition() === 'right')\n</script>\n\n<div\n  bind:this={ref}\n  class={cn(\n    'relative',\n    isRight &&\n      'border-input focus-within:ring-ring inline-grid grid-cols-[1fr_auto] grid-rows-[1fr_1fr] items-stretch overflow-hidden rounded-md border focus-within:ring-1',\n    !isRight &&\n      '[&>[data-slot=input]]:has-[[data-slot=decrement]]:pl-9 [&>[data-slot=input]]:has-[[data-slot=increment]]:pr-9',\n    className,\n  )}\n  {...restProps}\n>\n  {@render children?.()}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/number-field/NumberFieldContent.svelte"
    },
    {
      "path": "packages/registry-svelte/components/number-field/NumberFieldContext.ts",
      "content": "import { getContext, setContext } from 'svelte'\n\nexport type NumberFieldSize = 'small' | 'middle' | 'large'\nexport type NumberFieldStatus = 'error' | 'warning'\nexport type NumberFieldControlsPosition = 'default' | 'right'\n\nexport interface NumberFieldContextValue {\n  getValue: () => number | undefined\n  setValue: (value: number | undefined) => void\n  handleIncrease: (times?: number) => void\n  handleDecrease: (times?: number) => void\n  handleMinMaxValue: (which: 'min' | 'max') => void\n  applyInputValue: (displayValue: string) => void\n  formatValue: (value: number | undefined) => string\n  getMin: () => number | undefined\n  getMax: () => number | undefined\n  isDisabled: () => boolean\n  isReadonly: () => boolean\n  getInputMode: () => 'decimal' | 'numeric'\n  isWheelChangeDisabled: () => boolean\n  isWheelChangeInverted: () => boolean\n  getSize: () => NumberFieldSize\n  getStatus: () => NumberFieldStatus | undefined\n  getControlsPosition: () => NumberFieldControlsPosition\n  isKeyboardEnabled: () => boolean\n  getFormatter: () => ((value: number | undefined) => string) | undefined\n  getParser: () => ((displayValue: string) => number | undefined) | undefined\n  getPrefix: () => string | undefined\n  getSuffix: () => string | undefined\n  getId: () => string | undefined\n  getPlaceholder: () => string | undefined\n}\n\nconst NumberFieldContextKey = Symbol('NumberFieldContext')\n\nexport function setNumberFieldContext(context: NumberFieldContextValue) {\n  setContext(NumberFieldContextKey, context)\n}\n\nconst fallbackContext: NumberFieldContextValue = {\n  getValue: () => undefined,\n  setValue: () => {},\n  handleIncrease: () => {},\n  handleDecrease: () => {},\n  handleMinMaxValue: () => {},\n  applyInputValue: () => {},\n  formatValue: (value) => (value === undefined ? '' : String(value)),\n  getMin: () => undefined,\n  getMax: () => undefined,\n  isDisabled: () => false,\n  isReadonly: () => false,\n  getInputMode: () => 'decimal',\n  isWheelChangeDisabled: () => false,\n  isWheelChangeInverted: () => false,\n  getSize: () => 'middle',\n  getStatus: () => undefined,\n  getControlsPosition: () => 'default',\n  isKeyboardEnabled: () => true,\n  getFormatter: () => undefined,\n  getParser: () => undefined,\n  getPrefix: () => undefined,\n  getSuffix: () => undefined,\n  getId: () => undefined,\n  getPlaceholder: () => undefined,\n}\n\nexport function getNumberFieldContext(): NumberFieldContextValue {\n  return getContext<NumberFieldContextValue>(NumberFieldContextKey) ?? fallbackContext\n}\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/number-field/NumberFieldContext.ts"
    },
    {
      "path": "packages/registry-svelte/components/number-field/NumberFieldDecrement.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  export interface NumberFieldDecrementProps extends HTMLButtonAttributes {\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Minus } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { getNumberFieldContext } from './NumberFieldContext'\n\n  let {\n    class: className,\n    disabled,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: NumberFieldDecrementProps = $props()\n\n  const uiContext = getNumberFieldContext()\n  const isRight = $derived(uiContext.getControlsPosition() === 'right')\n  const size = $derived(uiContext.getSize())\n\n  const iconSize = $derived(size === 'small' ? 'h-3 w-3' : size === 'large' ? 'h-5 w-5' : 'h-4 w-4')\n\n  const atBound = $derived.by(() => {\n    const v = uiContext.getValue()\n    const min = uiContext.getMin()\n    return v !== undefined && min !== undefined && v <= min\n  })\n  const isDisabled = $derived(Boolean(disabled) || uiContext.isDisabled() || uiContext.isReadonly() || atBound)\n</script>\n\n<button\n  bind:this={ref}\n  type=\"button\"\n  tabindex=\"-1\"\n  data-uipkge=\"\"\n  data-slot=\"decrement\"\n  aria-label=\"Decrease\"\n  disabled={isDisabled}\n  class={cn(\n    'focus-visible:ring-ring inline-flex shrink-0 items-center justify-center transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-30',\n    !isRight && 'absolute top-1/2 left-0 z-10 -translate-y-1/2',\n    !isRight && size === 'small' && 'p-1.5',\n    !isRight && size === 'middle' && 'p-3',\n    !isRight && size === 'large' && 'p-4',\n    isRight && 'hover:bg-accent col-start-2 row-start-2 h-full w-auto rounded-none border-t border-l p-0 px-2',\n    className,\n  )}\n  onclick={(e) => {\n    uiContext.handleDecrease()\n    onclick?.(e)\n  }}\n  {...restProps}\n>\n  {#if children}\n    {@render children()}\n  {:else}\n    <Minus class={iconSize} aria-hidden=\"true\" />\n  {/if}\n</button>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/number-field/NumberFieldDecrement.svelte"
    },
    {
      "path": "packages/registry-svelte/components/number-field/NumberFieldIncrement.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLButtonAttributes } from 'svelte/elements'\n\n  export interface NumberFieldIncrementProps extends HTMLButtonAttributes {\n    ref?: HTMLButtonElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Plus } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { getNumberFieldContext } from './NumberFieldContext'\n\n  let {\n    class: className,\n    disabled,\n    children,\n    ref = $bindable(null),\n    onclick,\n    ...restProps\n  }: NumberFieldIncrementProps = $props()\n\n  const uiContext = getNumberFieldContext()\n  const isRight = $derived(uiContext.getControlsPosition() === 'right')\n  const size = $derived(uiContext.getSize())\n\n  const iconSize = $derived(size === 'small' ? 'h-3 w-3' : size === 'large' ? 'h-5 w-5' : 'h-4 w-4')\n\n  const atBound = $derived.by(() => {\n    const v = uiContext.getValue()\n    const max = uiContext.getMax()\n    return v !== undefined && max !== undefined && v >= max\n  })\n  const isDisabled = $derived(Boolean(disabled) || uiContext.isDisabled() || uiContext.isReadonly() || atBound)\n</script>\n\n<button\n  bind:this={ref}\n  type=\"button\"\n  tabindex=\"-1\"\n  data-uipkge=\"\"\n  data-slot=\"increment\"\n  aria-label=\"Increase\"\n  disabled={isDisabled}\n  class={cn(\n    'focus-visible:ring-ring inline-flex shrink-0 items-center justify-center transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-30',\n    !isRight && 'absolute top-1/2 right-0 z-10 -translate-y-1/2',\n    !isRight && size === 'small' && 'p-1.5',\n    !isRight && size === 'middle' && 'p-3',\n    !isRight && size === 'large' && 'p-4',\n    isRight && 'hover:bg-accent col-start-2 row-start-1 h-full w-auto rounded-none border-l p-0 px-2',\n    className,\n  )}\n  onclick={(e) => {\n    uiContext.handleIncrease()\n    onclick?.(e)\n  }}\n  {...restProps}\n>\n  {#if children}\n    {@render children()}\n  {:else}\n    <Plus class={iconSize} aria-hidden=\"true\" />\n  {/if}\n</button>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/number-field/NumberFieldIncrement.svelte"
    },
    {
      "path": "packages/registry-svelte/components/number-field/NumberFieldInput.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface NumberFieldInputProps extends HTMLAttributes<HTMLDivElement> {\n    ref?: HTMLInputElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { tick } from 'svelte'\n  import { cn } from '$lib/utils'\n  import { getNumberFieldContext } from './NumberFieldContext'\n\n  let { class: className, ref = $bindable(null), ...restProps }: NumberFieldInputProps = $props()\n\n  const rootContext = getNumberFieldContext()\n\n  let inputEl: HTMLInputElement | null = $state(null)\n  $effect(() => {\n    ref = inputEl\n  })\n\n  let displayValue = $state('')\n  let isUserTyping = $state(false)\n\n  function updateDisplayValue(val: number | undefined) {\n    displayValue = rootContext.formatValue(val)\n  }\n\n  // Mirror the Vue watchers: external value changes repaint the input unless\n  // the user is mid-edit.\n  $effect(() => {\n    const val = rootContext.getValue()\n    rootContext.getFormatter()\n    if (!isUserTyping) updateDisplayValue(val)\n  })\n\n  function handleFocus() {\n    isUserTyping = true\n  }\n\n  function commitValue() {\n    isUserTyping = false\n    const raw = displayValue.trim()\n\n    if (raw === '') {\n      rootContext.setValue(undefined)\n    } else {\n      const parser = rootContext.getParser()\n      let num: number | undefined\n      if (parser) {\n        num = parser(raw)\n      } else {\n        num = Number(raw)\n      }\n\n      if (num !== undefined && !Number.isNaN(num)) {\n        rootContext.applyInputValue(String(num))\n      }\n    }\n\n    tick().then(() => {\n      updateDisplayValue(rootContext.getValue())\n    })\n  }\n\n  function handleBlur() {\n    commitValue()\n  }\n\n  function handleKeydown(event: KeyboardEvent) {\n    if (event.key === 'ArrowUp') {\n      if (rootContext.isKeyboardEnabled()) {\n        event.preventDefault()\n        rootContext.handleIncrease()\n      }\n    } else if (event.key === 'ArrowDown') {\n      if (rootContext.isKeyboardEnabled()) {\n        event.preventDefault()\n        rootContext.handleDecrease()\n      }\n    } else if (event.key === 'PageUp') {\n      if (rootContext.isKeyboardEnabled()) {\n        event.preventDefault()\n        rootContext.handleIncrease(10)\n      }\n    } else if (event.key === 'PageDown') {\n      if (rootContext.isKeyboardEnabled()) {\n        event.preventDefault()\n        rootContext.handleDecrease(10)\n      }\n    } else if (event.key === 'Home') {\n      if (rootContext.isKeyboardEnabled()) {\n        event.preventDefault()\n        rootContext.handleMinMaxValue('min')\n      }\n    } else if (event.key === 'End') {\n      if (rootContext.isKeyboardEnabled()) {\n        event.preventDefault()\n        rootContext.handleMinMaxValue('max')\n      }\n    } else if (event.key === 'Enter') {\n      commitValue()\n    }\n  }\n\n  function handleWheel(event: WheelEvent) {\n    if (rootContext.isWheelChangeDisabled()) return\n    if (event.target !== document.activeElement) return\n    if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return\n    event.preventDefault()\n    if (event.deltaY > 0) {\n      rootContext.isWheelChangeInverted() ? rootContext.handleDecrease() : rootContext.handleIncrease()\n    } else {\n      rootContext.isWheelChangeInverted() ? rootContext.handleIncrease() : rootContext.handleDecrease()\n    }\n  }\n\n  const isRight = $derived(rootContext.getControlsPosition() === 'right')\n  const status = $derived(rootContext.getStatus())\n  const prefix = $derived(rootContext.getPrefix())\n  const suffix = $derived(rootContext.getSuffix())\n\n  const sizeClasses = $derived.by(() => {\n    switch (rootContext.getSize()) {\n      case 'small':\n        return 'h-7 text-xs px-2 py-0.5'\n      case 'large':\n        return 'h-11 text-base px-4 py-2'\n      default:\n        return 'h-9 text-sm px-3 py-1'\n    }\n  })\n\n  const statusClasses = $derived(\n    status === 'error'\n      ? 'border-destructive focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 aria-invalid:border-destructive'\n      : status === 'warning'\n        ? 'border-warning focus-visible:ring-warning/20'\n        : '',\n  )\n\n  const modelValue = $derived(rootContext.getValue())\n</script>\n\n<div data-uipkge=\"\" data-slot=\"input\" class={cn('relative flex-1', isRight && 'col-span-1 row-span-2', className)}>\n  {#if prefix}\n    <span class=\"text-muted-foreground pointer-events-none absolute top-1/2 left-2 -translate-y-1/2 text-sm\">\n      {prefix}\n    </span>\n  {/if}\n  <input\n    bind:this={inputEl}\n    id={rootContext.getId()}\n    value={displayValue}\n    type=\"text\"\n    role=\"spinbutton\"\n    aria-valuenow={modelValue !== undefined && !Number.isNaN(modelValue) ? modelValue : undefined}\n    aria-valuemin={rootContext.getMin()}\n    aria-valuemax={rootContext.getMax()}\n    inputmode={rootContext.getInputMode()}\n    disabled={rootContext.isDisabled()}\n    readonly={rootContext.isReadonly()}\n    placeholder={rootContext.getPlaceholder()}\n    aria-invalid={status === 'error' ? true : undefined}\n    autocomplete=\"off\"\n    autocorrect=\"off\"\n    spellcheck=\"false\"\n    aria-roledescription=\"Number field\"\n    oninput={(e) => {\n      displayValue = (e.target as HTMLInputElement).value\n    }}\n    onfocus={handleFocus}\n    onblur={handleBlur}\n    onkeydown={handleKeydown}\n    onwheel={handleWheel}\n    class={cn(\n      'placeholder:text-muted-foreground w-full bg-transparent text-center shadow-sm transition-colors outline-none disabled:cursor-not-allowed disabled:opacity-50',\n      !isRight && 'border-input focus-visible:ring-ring rounded-md border focus-visible:ring-1',\n      isRight && 'rounded-none border-0 focus-visible:ring-0',\n      prefix && 'pl-6',\n      suffix && 'pr-6',\n      sizeClasses,\n      !isRight && statusClasses,\n    )}\n    {...restProps}\n  />\n  {#if suffix}\n    <span class=\"text-muted-foreground pointer-events-none absolute top-1/2 right-2 -translate-y-1/2 text-sm\">\n      {suffix}\n    </span>\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/number-field/NumberFieldInput.svelte"
    },
    {
      "path": "packages/registry-svelte/components/number-field/index.ts",
      "content": "export { default as NumberField, type NumberFieldProps } from './NumberField.svelte'\nexport { default as NumberFieldContent, type NumberFieldContentProps } from './NumberFieldContent.svelte'\nexport { default as NumberFieldDecrement, type NumberFieldDecrementProps } from './NumberFieldDecrement.svelte'\nexport { default as NumberFieldIncrement, type NumberFieldIncrementProps } from './NumberFieldIncrement.svelte'\nexport { default as NumberFieldInput, type NumberFieldInputProps } from './NumberFieldInput.svelte'\nexport { getNumberFieldContext, setNumberFieldContext } from './NumberFieldContext'\nexport type {\n  NumberFieldContextValue,\n  NumberFieldControlsPosition,\n  NumberFieldSize,\n  NumberFieldStatus,\n} from './NumberFieldContext'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/number-field/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Numeric input with stepper buttons, min/max bounds, step size, and decimal precision. Use for quantities, prices, and any field that should be a number rather than free text.",
  "categories": [
    "form"
  ]
}