{"version":3,"sources":["../src/hooks/use-disabled-state.ts"],"names":["useMemo","useRef","useEffect","useDisabledState","disabled","handlersOrOptions","isDisabled","configKeys","options","key","handlers","className","disabledClassName","preventDefault","stopPropagation","removeFromTabOrder","handlersRef","mergedClassName","c","disabledProps","wrappedHandlers","allowWhenDisabled","event"],"mappings":"AAAA,OAAS,WAAAA,EAAS,UAAAC,EAAQ,aAAAC,MAAiB,QA+IpC,SAASC,EACdC,EACAC,EAAoF,CAAC,EAC1D,CAE3B,IAAMC,EAAa,EAAQF,EAIrBG,EAAa,CAAC,WAAY,YAAa,oBAAqB,iBAAkB,kBAAmB,oBAAoB,EAGrHC,EAFW,OAAO,KAAKH,CAAiB,EAAE,KAAKI,GAAOF,EAAW,SAASE,CAAG,CAAC,EAG/EJ,EACD,CAAE,SAAUA,CAAuD,EAEjE,CACJ,SAAAK,EAAW,CAAC,EACZ,UAAAC,EAAY,GACZ,kBAAAC,EAAoB,cACpB,eAAAC,EAAiB,GACjB,gBAAAC,EAAkB,GAClB,mBAAAC,EAAqB,EACvB,EAAIP,EAIEQ,EAAcf,EAAOS,CAAQ,EAEnC,OAAAR,EAAU,IAAM,CACdc,EAAY,QAAUN,CACxB,EAAG,CAACA,CAAQ,CAAC,EAINV,EAAmC,IAAM,CAE9C,IAAMiB,EAAkB,CACtBX,EAAaM,EAAoB,GACjCD,CACF,EACG,OAAO,OAAO,EACd,IAAIO,GAAKA,EAAE,KAAK,CAAC,EACjB,OAAOA,GAAKA,EAAE,OAAS,CAAC,EACxB,KAAK,GAAG,EAELC,EAA+B,CACnC,gBAAiBb,EACjB,UAAWW,CACb,EAGIF,GAAsBT,IACxBa,EAAc,SAAW,IAK3B,IAAMC,EAAqD,CAAC,EAqB5D,MAfK,CACH,CAAE,IAAK,SAAU,EACjB,CAAE,IAAK,UAAW,EAClB,CAAE,IAAK,QAAS,EAChB,CAAE,IAAK,UAAW,kBAAmB,EAAK,EAC1C,CAAE,IAAK,eAAgB,EACvB,CAAE,IAAK,WAAY,EACnB,CAAE,IAAK,SAAU,EACjB,CAAE,IAAK,aAAc,EACrB,CAAE,IAAK,WAAY,EACnB,CAAE,IAAK,cAAe,EACtB,CAAE,IAAK,YAAa,CACtB,EAGe,QAAQ,CAAC,CAAE,IAAAX,EAAK,kBAAAY,EAAoB,EAAM,IAAM,CAEzDL,EAAY,QAAQP,CAAG,IAAM,SAG/BW,EAAgBX,CAAG,EAAMa,GAAe,CACtC,GAAIhB,GAAc,CAACe,EAAmB,CAChCR,GAAgBS,EAAM,eAAe,EACrCR,GAAiBQ,EAAM,gBAAgB,EAC3C,MACF,CAEAN,EAAY,QAAQP,CAAG,IAAIa,CAAK,CAElC,EAEJ,CAAC,EAEM,CACL,cAAAH,EACA,SAAUC,CACZ,CACF,EAAG,CAACd,EAAYK,EAAWC,EAAmBC,EAAgBC,EAAiBC,CAAkB,CAAC,CACpG","sourcesContent":["import { useMemo, useRef, useEffect } from 'react';\n\n/**\n * Event handler mapping type for disabled state management.\n * Maps event names to their handler functions for any HTML element.\n *\n * @template T - The HTML element type (e.g., HTMLButtonElement, HTMLInputElement)\n */\nexport type DisabledEventHandlers<T extends HTMLElement> = {\n  onClick?: (event: React.MouseEvent<T>) => void;\n  onChange?: (event: React.ChangeEvent<T>) => void;\n  onBlur?: (event: React.FocusEvent<T>) => void;\n  onFocus?: (event: React.FocusEvent<T>) => void;\n  onPointerDown?: (event: React.PointerEvent<T>) => void;\n  onKeyDown?: (event: React.KeyboardEvent<T>) => void;\n  onKeyUp?: (event: React.KeyboardEvent<T>) => void;\n  onMouseDown?: (event: React.MouseEvent<T>) => void;\n  onMouseUp?: (event: React.MouseEvent<T>) => void;\n  onTouchStart?: (event: React.TouchEvent<T>) => void;\n  onTouchEnd?: (event: React.TouchEvent<T>) => void;\n};\n\n/**\n * Props returned by the useDisabledState hook containing ARIA attributes and styling.\n */\nexport interface DisabledProps {\n  /** ARIA attribute indicating disabled state */\n  'aria-disabled': boolean;\n  /** CSS class name for disabled state styling */\n  className: string;\n  /** Optional tabIndex to remove element from tab order when disabled */\n  tabIndex?: -1;\n}\n\n/**\n * Configuration options for useDisabledState hook.\n *\n * @template T - The HTML element type\n */\nexport interface UseDisabledStateOptions<T extends HTMLElement> {\n  /** Event handlers to wrap with disabled logic */\n  handlers?: Partial<DisabledEventHandlers<T>>;\n\n  /** Existing className to merge with disabled class */\n  className?: string;\n\n  /** Custom disabled className (default: 'is-disabled') */\n  disabledClassName?: string;\n\n  /** Whether to call preventDefault on disabled events (default: true) */\n  preventDefault?: boolean;\n\n  /** Whether to call stopPropagation on disabled events (default: true) */\n  stopPropagation?: boolean;\n\n  /** Make element non-focusable when disabled via tabIndex=-1 (default: false for a11y) */\n  removeFromTabOrder?: boolean;\n}\n\n/**\n * Return type for the useDisabledState hook.\n *\n * @template T - The HTML element type\n */\nexport interface UseDisabledStateReturn<T extends HTMLElement> {\n  /** Props to spread on the element for disabled state */\n  disabledProps: DisabledProps;\n  /** Wrapped event handlers that respect disabled state */\n  handlers: Partial<DisabledEventHandlers<T>>;\n}\n\n/**\n * Manages accessible disabled state for form elements using aria-disabled pattern.\n *\n * This hook implements WCAG 2.1 Level AA compliant disabled state management by:\n * - Using `aria-disabled` instead of native `disabled` attribute (keeps elements focusable)\n * - Preventing all interaction events when disabled\n * - Applying accessible styling via `.is-disabled` class\n * - Maintaining keyboard focusability for screen reader discovery\n *\n * **Why aria-disabled instead of disabled attribute?**\n * - Elements remain in keyboard tab order (WCAG 2.1.1 - Keyboard)\n * - Screen readers can discover and announce disabled state\n * - Enables tooltips and contextual help on disabled elements\n * - Better visual styling control for WCAG contrast compliance\n *\n * **Performance Optimizations:**\n * - Single memoization pass for all handlers and props\n * - Stable handler references using refs (only recreate on disabled state change)\n * - Automatic className merging to reduce consumer boilerplate\n *\n * @template T - The HTML element type (e.g., HTMLButtonElement, HTMLInputElement)\n *\n * @param {boolean | undefined} disabled - Whether the element should be disabled. Undefined treated as false.\n * @param {Partial<DisabledEventHandlers<T>> | UseDisabledStateOptions<T>} handlersOrOptions -\n *   Event handlers to wrap OR configuration options object (for backward compatibility)\n *\n * @returns {UseDisabledStateReturn<T>} Object containing disabledProps and wrapped handlers\n *\n * @example\n * // Basic button usage (legacy API - still supported)\n * const MyButton = ({ disabled, onClick, children }) => {\n *   const { disabledProps, handlers } = useDisabledState(disabled, { onClick });\n *   return <button {...disabledProps} {...handlers}>{children}</button>;\n * };\n *\n * @example\n * // Enhanced API with className merging\n * const MyButton = ({ disabled, onClick, className, children }) => {\n *   const { disabledProps, handlers } = useDisabledState(disabled, {\n *     handlers: { onClick },\n *     className,\n *   });\n *   return <button {...disabledProps} {...handlers}>{children}</button>;\n * };\n *\n * @example\n * // Custom configuration\n * const MyInput = ({ disabled, onChange, className }) => {\n *   const { disabledProps, handlers } = useDisabledState(disabled, {\n *     handlers: { onChange },\n *     className,\n *     disabledClassName: 'custom-disabled',\n *     preventDefault: true,\n *     stopPropagation: false,\n *   });\n *   return <input {...disabledProps} {...handlers} />;\n * };\n *\n * @example\n * // Remove from tab order when disabled\n * const MyButton = ({ disabled, onClick }) => {\n *   const { disabledProps, handlers } = useDisabledState(disabled, {\n *     handlers: { onClick },\n *     removeFromTabOrder: true, // Adds tabIndex=-1 when disabled\n *   });\n *   return <button {...disabledProps} {...handlers}>Click me</button>;\n * };\n *\n * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/keyboard WCAG 2.1.1 - Keyboard}\n * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/name-role-value WCAG 4.1.2 - Name, Role, Value}\n * @see {@link https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum WCAG 1.4.3 - Contrast (Minimum)}\n */\nexport function useDisabledState<T extends HTMLElement = HTMLElement>(\n  disabled: boolean | undefined,\n  handlersOrOptions: Partial<DisabledEventHandlers<T>> | UseDisabledStateOptions<T> = {}\n): UseDisabledStateReturn<T> {\n  // Normalize disabled to boolean (treat undefined as false)\n  const isDisabled = Boolean(disabled);\n\n  // Support both legacy API (handlers directly) and new API (options object)\n  // Check if this is the new API by looking for config properties\n  const configKeys = ['handlers', 'className', 'disabledClassName', 'preventDefault', 'stopPropagation', 'removeFromTabOrder'];\n  const isNewAPI = Object.keys(handlersOrOptions).some(key => configKeys.includes(key));\n\n  const options: UseDisabledStateOptions<T> = isNewAPI\n    ? (handlersOrOptions as UseDisabledStateOptions<T>)\n    : { handlers: handlersOrOptions as Partial<DisabledEventHandlers<T>> };\n\n  const {\n    handlers = {},\n    className = '',\n    disabledClassName = 'is-disabled',\n    preventDefault = true,\n    stopPropagation = true,\n    removeFromTabOrder = false,\n  } = options;\n\n  // Store latest handlers in ref to maintain stable wrapper functions\n  // This prevents handler wrappers from being recreated on every render\n  const handlersRef = useRef(handlers);\n\n  useEffect(() => {\n    handlersRef.current = handlers;\n  }, [handlers]);\n\n  // Single memoization pass for both props and wrapped handlers\n  // Only recalculates when disabled state or configuration changes\n  return useMemo<UseDisabledStateReturn<T>>(() => {\n    // Build disabled props with merged className\n    const mergedClassName = [\n      isDisabled ? disabledClassName : '',\n      className,\n    ]\n      .filter(Boolean)\n      .map(c => c.trim())\n      .filter(c => c.length > 0)\n      .join(' ');\n\n    const disabledProps: DisabledProps = {\n      'aria-disabled': isDisabled,\n      className: mergedClassName,\n    };\n\n    // Add tabIndex=-1 when disabled if requested (removes from tab order)\n    if (removeFromTabOrder && isDisabled) {\n      disabledProps.tabIndex = -1;\n    }\n\n    // Build wrapped handlers using declarative mapping\n    // Only includes handlers that were actually provided\n    const wrappedHandlers: Partial<DisabledEventHandlers<T>> = {};\n\n    // Define which handlers to wrap and their special behaviors\n    const handlerConfigs: Array<{\n      key: keyof DisabledEventHandlers<T>;\n      allowWhenDisabled?: boolean;\n    }> = [\n      { key: 'onClick' },\n      { key: 'onChange' },\n      { key: 'onBlur' },\n      { key: 'onFocus', allowWhenDisabled: true }, // Always allow focus for a11y\n      { key: 'onPointerDown' },\n      { key: 'onKeyDown' },\n      { key: 'onKeyUp' },\n      { key: 'onMouseDown' },\n      { key: 'onMouseUp' },\n      { key: 'onTouchStart' },\n      { key: 'onTouchEnd' },\n    ];\n\n    // Wrap each provided handler\n    handlerConfigs.forEach(({ key, allowWhenDisabled = false }) => {\n      // Check if handler exists in the initial handlers object\n      if (handlersRef.current[key] !== undefined) {\n        // Create wrapper that accesses handler from ref at call-time\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        wrappedHandlers[key] = ((event: any) => {\n          if (isDisabled && !allowWhenDisabled) {\n            if (preventDefault) event.preventDefault();\n            if (stopPropagation) event.stopPropagation();\n            return;\n          }\n          // Access latest handler from ref at call-time\n          handlersRef.current[key]?.(event);\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        }) as any;\n      }\n    });\n\n    return {\n      disabledProps,\n      handlers: wrappedHandlers,\n    };\n  }, [isDisabled, className, disabledClassName, preventDefault, stopPropagation, removeFromTabOrder]);\n}\n"]}