@spark-ui/components 17.4.2 → 17.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/dist/accordion/index.js.map +1 -1
  2. package/dist/accordion/index.mjs.map +1 -1
  3. package/dist/alert-dialog/index.js.map +1 -1
  4. package/dist/alert-dialog/index.mjs.map +1 -1
  5. package/dist/avatar/index.js +1 -1
  6. package/dist/avatar/index.js.map +1 -1
  7. package/dist/avatar/index.mjs +8 -2
  8. package/dist/avatar/index.mjs.map +1 -1
  9. package/dist/badge/index.js.map +1 -1
  10. package/dist/badge/index.mjs.map +1 -1
  11. package/dist/breadcrumb/index.js.map +1 -1
  12. package/dist/breadcrumb/index.mjs.map +1 -1
  13. package/dist/button-B-sMnDc_.js.map +1 -1
  14. package/dist/button-C6nlNPdv.mjs.map +1 -1
  15. package/dist/card/index.js.map +1 -1
  16. package/dist/card/index.mjs.map +1 -1
  17. package/dist/carousel/index.js.map +1 -1
  18. package/dist/carousel/index.mjs.map +1 -1
  19. package/dist/checkbox-DjwbAH09.js.map +1 -1
  20. package/dist/checkbox-xsURzANi.mjs.map +1 -1
  21. package/dist/chip/index.js.map +1 -1
  22. package/dist/chip/index.mjs.map +1 -1
  23. package/dist/circular-meter/index.js.map +1 -1
  24. package/dist/circular-meter/index.mjs.map +1 -1
  25. package/dist/collapsible/index.js.map +1 -1
  26. package/dist/collapsible/index.mjs.map +1 -1
  27. package/dist/combobox/index.js.map +1 -1
  28. package/dist/combobox/index.mjs.map +1 -1
  29. package/dist/dialog/index.js.map +1 -1
  30. package/dist/dialog/index.mjs.map +1 -1
  31. package/dist/divider/index.js.map +1 -1
  32. package/dist/divider/index.mjs.map +1 -1
  33. package/dist/drawer/index.js.map +1 -1
  34. package/dist/drawer/index.mjs.map +1 -1
  35. package/dist/dropdown/index.js.map +1 -1
  36. package/dist/dropdown/index.mjs.map +1 -1
  37. package/dist/file-upload/index.js.map +1 -1
  38. package/dist/file-upload/index.mjs.map +1 -1
  39. package/dist/form-field-81wzFxM0.js.map +1 -1
  40. package/dist/form-field-GTAuK_nO.mjs.map +1 -1
  41. package/dist/icon-CRPcdgYp.js.map +1 -1
  42. package/dist/icon-D05Uqh8_.mjs.map +1 -1
  43. package/dist/icon-button-CYz_Fitz.js.map +1 -1
  44. package/dist/icon-button-DpucUC_L.mjs.map +1 -1
  45. package/dist/input-BUSYZ_VO.js.map +1 -1
  46. package/dist/input-CiWFuTs_.mjs.map +1 -1
  47. package/dist/input-otp/index.js.map +1 -1
  48. package/dist/input-otp/index.mjs.map +1 -1
  49. package/dist/kbd/index.js.map +1 -1
  50. package/dist/kbd/index.mjs.map +1 -1
  51. package/dist/label-BCSEss4U.js.map +1 -1
  52. package/dist/label-DDBRKLUX.mjs.map +1 -1
  53. package/dist/link-box/index.js.map +1 -1
  54. package/dist/link-box/index.mjs.map +1 -1
  55. package/dist/meter/index.js.map +1 -1
  56. package/dist/meter/index.mjs.map +1 -1
  57. package/dist/pagination/index.js.map +1 -1
  58. package/dist/pagination/index.mjs.map +1 -1
  59. package/dist/popover-CrKp_TKk.js.map +1 -1
  60. package/dist/popover-DsBY8eYl.mjs.map +1 -1
  61. package/dist/portal/index.js.map +1 -1
  62. package/dist/portal/index.mjs.map +1 -1
  63. package/dist/progress-BjqJSRnK.js.map +1 -1
  64. package/dist/progress-C3w4PmxY.mjs.map +1 -1
  65. package/dist/progress-tracker/index.js.map +1 -1
  66. package/dist/progress-tracker/index.mjs.map +1 -1
  67. package/dist/radio-group/index.js.map +1 -1
  68. package/dist/radio-group/index.mjs.map +1 -1
  69. package/dist/rating/index.js.map +1 -1
  70. package/dist/rating/index.mjs.map +1 -1
  71. package/dist/rating-display/index.js.map +1 -1
  72. package/dist/rating-display/index.mjs.map +1 -1
  73. package/dist/scrolling-list/index.js.map +1 -1
  74. package/dist/scrolling-list/index.mjs.map +1 -1
  75. package/dist/segmented-control/index.js.map +1 -1
  76. package/dist/segmented-control/index.mjs.map +1 -1
  77. package/dist/segmented-gauge/index.js.map +1 -1
  78. package/dist/segmented-gauge/index.mjs.map +1 -1
  79. package/dist/select/index.js.map +1 -1
  80. package/dist/select/index.mjs.map +1 -1
  81. package/dist/skeleton/index.js.map +1 -1
  82. package/dist/skeleton/index.mjs.map +1 -1
  83. package/dist/slider/index.js.map +1 -1
  84. package/dist/slider/index.mjs.map +1 -1
  85. package/dist/slot/index.js.map +1 -1
  86. package/dist/slot/index.mjs.map +1 -1
  87. package/dist/spinner-DFUoYvmm.js.map +1 -1
  88. package/dist/spinner-DULLiM6a.mjs.map +1 -1
  89. package/dist/src/accordion/index.d.mts +3 -0
  90. package/dist/src/accordion/index.d.ts +3 -0
  91. package/dist/src/alert-dialog/index.d.mts +3 -0
  92. package/dist/src/alert-dialog/index.d.ts +3 -0
  93. package/dist/src/avatar/index.d.mts +7 -5
  94. package/dist/src/avatar/index.d.ts +7 -5
  95. package/dist/src/badge/Badge.d.ts +3 -0
  96. package/dist/src/breadcrumb/index.d.mts +3 -0
  97. package/dist/src/breadcrumb/index.d.ts +3 -0
  98. package/dist/src/button/Button.d.ts +3 -0
  99. package/dist/src/card/index.d.mts +3 -0
  100. package/dist/src/card/index.d.ts +3 -0
  101. package/dist/src/carousel/index.d.mts +3 -0
  102. package/dist/src/carousel/index.d.ts +3 -0
  103. package/dist/src/checkbox/Checkbox.d.ts +3 -0
  104. package/dist/src/checkbox/CheckboxGroup.d.ts +4 -0
  105. package/dist/src/chip/index.d.mts +3 -0
  106. package/dist/src/chip/index.d.ts +3 -0
  107. package/dist/src/circular-meter/index.d.mts +3 -0
  108. package/dist/src/circular-meter/index.d.ts +3 -0
  109. package/dist/src/collapsible/index.d.mts +3 -0
  110. package/dist/src/collapsible/index.d.ts +3 -0
  111. package/dist/src/combobox/index.d.mts +3 -0
  112. package/dist/src/combobox/index.d.ts +3 -0
  113. package/dist/src/dialog/index.d.mts +3 -0
  114. package/dist/src/dialog/index.d.ts +3 -0
  115. package/dist/src/divider/index.d.mts +3 -0
  116. package/dist/src/divider/index.d.ts +3 -0
  117. package/dist/src/drawer/index.d.mts +3 -0
  118. package/dist/src/drawer/index.d.ts +3 -0
  119. package/dist/src/dropdown/index.d.mts +3 -0
  120. package/dist/src/dropdown/index.d.ts +3 -0
  121. package/dist/src/file-upload/index.d.mts +3 -0
  122. package/dist/src/file-upload/index.d.ts +3 -0
  123. package/dist/src/form-field/index.d.mts +3 -0
  124. package/dist/src/form-field/index.d.ts +3 -0
  125. package/dist/src/icon/Icon.d.ts +3 -0
  126. package/dist/src/icon-button/IconButton.d.ts +3 -0
  127. package/dist/src/input/Input.d.ts +3 -0
  128. package/dist/src/input/index.d.mts +4 -0
  129. package/dist/src/input/index.d.ts +4 -0
  130. package/dist/src/input-otp/index.d.mts +3 -0
  131. package/dist/src/input-otp/index.d.ts +3 -0
  132. package/dist/src/kbd/Kbd.d.ts +3 -0
  133. package/dist/src/label/index.d.mts +3 -0
  134. package/dist/src/label/index.d.ts +3 -0
  135. package/dist/src/link-box/index.d.mts +3 -0
  136. package/dist/src/link-box/index.d.ts +3 -0
  137. package/dist/src/meter/index.d.mts +3 -0
  138. package/dist/src/meter/index.d.ts +3 -0
  139. package/dist/src/pagination/index.d.mts +3 -0
  140. package/dist/src/pagination/index.d.ts +3 -0
  141. package/dist/src/popover/index.d.mts +3 -0
  142. package/dist/src/popover/index.d.ts +3 -0
  143. package/dist/src/portal/Portal.d.ts +3 -0
  144. package/dist/src/progress/index.d.mts +3 -0
  145. package/dist/src/progress/index.d.ts +3 -0
  146. package/dist/src/progress-tracker/index.d.mts +3 -0
  147. package/dist/src/progress-tracker/index.d.ts +3 -0
  148. package/dist/src/radio-group/index.d.mts +3 -0
  149. package/dist/src/radio-group/index.d.ts +3 -0
  150. package/dist/src/rating/Rating.d.ts +3 -0
  151. package/dist/src/rating-display/index.d.mts +3 -0
  152. package/dist/src/rating-display/index.d.ts +3 -0
  153. package/dist/src/scrolling-list/index.d.mts +3 -0
  154. package/dist/src/scrolling-list/index.d.ts +3 -0
  155. package/dist/src/segmented-control/index.d.mts +3 -0
  156. package/dist/src/segmented-control/index.d.ts +3 -0
  157. package/dist/src/segmented-gauge/index.d.mts +3 -0
  158. package/dist/src/segmented-gauge/index.d.ts +3 -0
  159. package/dist/src/select/index.d.mts +3 -0
  160. package/dist/src/select/index.d.ts +3 -0
  161. package/dist/src/skeleton/index.d.mts +3 -0
  162. package/dist/src/skeleton/index.d.ts +3 -0
  163. package/dist/src/slider/index.d.mts +3 -0
  164. package/dist/src/slider/index.d.ts +3 -0
  165. package/dist/src/slot/Slot.d.ts +4 -0
  166. package/dist/src/spinner/Spinner.d.ts +3 -0
  167. package/dist/src/stepper/index.d.mts +3 -0
  168. package/dist/src/stepper/index.d.ts +3 -0
  169. package/dist/src/switch/Switch.d.ts +3 -0
  170. package/dist/src/table/index.d.mts +6 -2
  171. package/dist/src/table/index.d.ts +6 -2
  172. package/dist/src/table/internal/TableRootWrapper.d.ts +3 -0
  173. package/dist/src/tabs/index.d.mts +3 -0
  174. package/dist/src/tabs/index.d.ts +3 -0
  175. package/dist/src/tag/Tag.d.ts +3 -0
  176. package/dist/src/text-link/TextLink.d.ts +3 -0
  177. package/dist/src/textarea/Textarea.d.ts +3 -0
  178. package/dist/src/textarea/index.d.mts +4 -0
  179. package/dist/src/textarea/index.d.ts +4 -0
  180. package/dist/src/toast/index.d.mts +3 -0
  181. package/dist/src/toast/index.d.ts +3 -0
  182. package/dist/src/visually-hidden/VisuallyHidden.d.ts +3 -0
  183. package/dist/stepper/index.js.map +1 -1
  184. package/dist/stepper/index.mjs.map +1 -1
  185. package/dist/switch/index.js.map +1 -1
  186. package/dist/switch/index.mjs.map +1 -1
  187. package/dist/table/index.js +1 -1
  188. package/dist/table/index.js.map +1 -1
  189. package/dist/table/index.mjs +1 -1
  190. package/dist/table/index.mjs.map +1 -1
  191. package/dist/tabs/index.js.map +1 -1
  192. package/dist/tabs/index.mjs.map +1 -1
  193. package/dist/tag/index.js.map +1 -1
  194. package/dist/tag/index.mjs.map +1 -1
  195. package/dist/text-link/index.js.map +1 -1
  196. package/dist/text-link/index.mjs.map +1 -1
  197. package/dist/textarea/index.js.map +1 -1
  198. package/dist/textarea/index.mjs.map +1 -1
  199. package/dist/toast/index.js.map +1 -1
  200. package/dist/toast/index.mjs.map +1 -1
  201. package/dist/visually-hidden/index.js.map +1 -1
  202. package/dist/visually-hidden/index.mjs.map +1 -1
  203. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/dropdown/useDropdown.ts","../../src/dropdown/utils.ts","../../src/dropdown/DropdownContext.tsx","../../src/dropdown/Dropdown.tsx","../../src/dropdown/DropdownDivider.tsx","../../src/dropdown/DropdownItemsGroupContext.tsx","../../src/dropdown/DropdownGroup.tsx","../../src/dropdown/DropdownItemContext.tsx","../../src/dropdown/DropdownItem.tsx","../../src/dropdown/DropdownItemIndicator.tsx","../../src/dropdown/DropdownItems.tsx","../../src/dropdown/DropdownItemText.tsx","../../src/dropdown/DropdownLabel.tsx","../../src/dropdown/DropdownLeadingIcon.tsx","../../src/dropdown/DropdownPopover.tsx","../../src/dropdown/DropdownPortal.tsx","../../src/dropdown/DropdownTrigger.styles.tsx","../../src/dropdown/DropdownTrigger.tsx","../../src/dropdown/DropdownValue.tsx","../../src/dropdown/index.ts"],"sourcesContent":["import { useMultipleSelection, useSelect, UseSelectProps } from 'downshift'\n\nimport { type DropdownItem, type ItemsMap } from './types'\n\ntype OnChangeValueType = string & string[]\n\nexport interface DownshiftProps {\n itemsMap: ItemsMap\n value: string | string[] | undefined\n defaultValue: string | string[] | undefined\n onValueChange: ((value: string) => void) | ((value: string[]) => void) | undefined\n open: boolean | undefined\n onOpenChange: ((isOpen: boolean) => void) | undefined\n defaultOpen: boolean | undefined\n multiple: boolean | undefined\n id: string\n labelId: string\n}\n\n/**\n * This hook abstract the complexity of using downshift with both single and multiple selection.\n */\nexport const useDropdown = ({\n itemsMap,\n defaultValue,\n value,\n onValueChange,\n open,\n onOpenChange,\n defaultOpen,\n multiple,\n id,\n labelId,\n}: DownshiftProps) => {\n const items = [...itemsMap.values()]\n\n const downshiftMultipleSelection = useMultipleSelection<DropdownItem>({\n selectedItems:\n value != null && multiple\n ? items.filter(item =>\n multiple ? (value as string[]).includes(item.value) : value === item.value\n )\n : undefined,\n initialSelectedItems:\n defaultValue != null && multiple\n ? items.filter(item =>\n multiple ? (defaultValue as string[]).includes(item.value) : defaultValue === item.value\n )\n : undefined,\n\n onSelectedItemsChange: ({ selectedItems }) => {\n if (selectedItems != null && multiple) {\n onValueChange?.(selectedItems.map(item => item.value) as OnChangeValueType)\n }\n },\n })\n\n /**\n * Custom state reducer for multiple selection behaviour:\n * - keeps the component opened when the user selects an item\n * - preserves the higlighted index when the user select an item\n * - selected items can be unselected, even the last selected item (as opposed to single selection behaviour)\n */\n const stateReducer: UseSelectProps<DropdownItem>['stateReducer'] = (state, { changes, type }) => {\n if (!multiple) return changes\n\n const { selectedItems, removeSelectedItem, addSelectedItem } = downshiftMultipleSelection\n\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check\n switch (type) {\n case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:\n case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton:\n case useSelect.stateChangeTypes.ItemClick:\n if (changes.selectedItem != null) {\n const isAlreadySelected = selectedItems.some(\n selectedItem => selectedItem.value === changes.selectedItem?.value\n )\n\n if (isAlreadySelected) removeSelectedItem(changes.selectedItem)\n else addSelectedItem(changes.selectedItem)\n }\n\n return {\n ...changes,\n isOpen: true, // keep the menu open after selection.\n highlightedIndex: state.highlightedIndex, // preserve highlighted index position\n }\n default:\n return changes\n }\n }\n\n const downshift = useSelect<DropdownItem>({\n items,\n isItemDisabled: item => item.disabled,\n itemToString: item => (item ? item.text : ''),\n // a11y attributes\n id,\n labelId,\n // Controlled open state\n isOpen: open, // undefined must be passed for stateful behaviour (uncontrolled)\n onIsOpenChange: ({ isOpen }) => {\n if (isOpen != null) onOpenChange?.(isOpen)\n },\n initialIsOpen: defaultOpen ?? false,\n stateReducer,\n // Controlled mode (single selection)\n selectedItem: value != null && !multiple ? itemsMap.get(value as string) || null : undefined,\n initialSelectedItem:\n (defaultValue != null || value != null) && !multiple\n ? itemsMap.get(defaultValue as string) || null\n : undefined,\n onSelectedItemChange: ({ selectedItem }) => {\n if (selectedItem?.value != null && !multiple) {\n onValueChange?.(selectedItem?.value as OnChangeValueType)\n }\n },\n /**\n * 1. Downshift default behaviour is to scroll into view the highlighted item when the dropdown opens. This behaviour is not stable and scrolls the dropdown to the bottom of the screen.\n */\n scrollIntoView: node => {\n if (node) {\n node.scrollIntoView({ block: 'nearest' })\n }\n\n return undefined\n },\n })\n\n return {\n ...downshift,\n ...downshiftMultipleSelection,\n /** There is a Downshift bug in React 19, it duplicates some selectedItems */\n selectedItems: [...new Set(downshiftMultipleSelection.selectedItems)],\n }\n}\n","import { type FC, isValidElement, type ReactElement, type ReactNode, Children } from 'react'\n\nimport { type ItemProps } from './DropdownItem'\nimport { ItemTextProps } from './DropdownItemText'\nimport { type DropdownItem, type ItemsMap } from './types'\n\nexport function getIndexByKey(map: ItemsMap, targetKey: string) {\n let index = 0\n for (const [key] of map.entries()) {\n if (key === targetKey) {\n return index\n }\n index++\n }\n\n return -1\n}\n\nconst getKeyAtIndex = (map: ItemsMap, index: number) => {\n let i = 0\n for (const key of map.keys()) {\n if (i === index) return key\n i++\n }\n\n return undefined\n}\n\nexport const getElementByIndex = (map: ItemsMap, index: number) => {\n const key = getKeyAtIndex(map, index)\n\n return key !== undefined ? map.get(key) : undefined\n}\n\nconst getElementDisplayName = (element?: ReactElement) => {\n return element ? (element.type as FC & { displayName?: string }).displayName : ''\n}\n\nexport const getOrderedItems = (\n children: ReactNode,\n result: DropdownItem[] = []\n): DropdownItem[] => {\n Children.forEach(children, child => {\n if (!isValidElement(child)) return\n\n if (getElementDisplayName(child) === 'Dropdown.Item') {\n const childProps = child.props as ItemProps\n result.push({\n value: childProps.value,\n disabled: !!childProps.disabled,\n text: getItemText(childProps.children),\n })\n }\n\n if ((child.props as { children: ReactNode }).children) {\n getOrderedItems((child.props as { children: ReactNode }).children, result)\n }\n })\n\n return result\n}\n\n/**\n * If Dropdown.Item children:\n * - is a string, then the string is used.\n * - is JSX markup, then we look for Dropdown.ItemText to get its string value.\n */\nexport const getItemText = (children: ReactNode, itemText = ''): string => {\n if (typeof children === 'string') {\n return children\n }\n\n Children.forEach(children, child => {\n if (!isValidElement(child)) return\n\n if (getElementDisplayName(child) === 'Dropdown.ItemText') {\n itemText = (child.props as ItemTextProps).children\n }\n\n if ((child.props as { children: ReactNode }).children) {\n getItemText((child.props as { children: ReactNode }).children, itemText)\n }\n })\n\n return itemText\n}\n\nexport const getItemsFromChildren = (children: ReactNode): ItemsMap => {\n const newMap: ItemsMap = new Map()\n\n getOrderedItems(children).forEach(itemData => {\n newMap.set(itemData.value, itemData)\n })\n\n return newMap\n}\n\nexport const hasChildComponent = (children: ReactNode, displayName: string): boolean => {\n return Children.toArray(children).some(child => {\n if (!isValidElement(child)) return false\n\n if (getElementDisplayName(child) === displayName) {\n return true\n } else if ((child.props as { children: ReactNode }).children) {\n return hasChildComponent((child.props as { children: ReactNode }).children, displayName)\n }\n\n return false\n })\n}\n","import { useFormFieldControl } from '@spark-ui/components/form-field'\nimport {\n createContext,\n Dispatch,\n Fragment,\n PropsWithChildren,\n SetStateAction,\n useContext,\n useEffect,\n useId,\n useState,\n} from 'react'\n\nimport { Popover } from '../popover'\nimport { type DownshiftState, type DropdownItem, type ItemsMap } from './types'\nimport { useDropdown } from './useDropdown'\nimport { getElementByIndex, getItemsFromChildren, hasChildComponent } from './utils'\n\nexport interface DropdownContextState extends DownshiftState {\n itemsMap: ItemsMap\n highlightedItem: DropdownItem | undefined\n hasPopover: boolean\n setHasPopover: Dispatch<SetStateAction<boolean>>\n multiple: boolean\n disabled: boolean\n readOnly: boolean\n state?: 'error' | 'alert' | 'success'\n lastInteractionType: 'mouse' | 'keyboard'\n setLastInteractionType: (type: 'mouse' | 'keyboard') => void\n}\n\nexport type DropdownContextCommonProps = PropsWithChildren<{\n /**\n * The controlled open state of the select. Must be used in conjunction with `onOpenChange`.\n */\n open?: boolean\n /**\n * Event handler called when the open state of the select changes.\n */\n onOpenChange?: (isOpen: boolean) => void\n /**\n * The open state of the select when it is initially rendered. Use when you do not need to control its open state.\n */\n defaultOpen?: boolean\n /**\n * Use `state` prop to assign a specific state to the dropdown, choosing from: `error`, `alert` and `success`. By doing so, the outline styles will be updated.\n */\n state?: 'error' | 'alert' | 'success'\n /**\n * When true, prevents the user from interacting with the dropdown.\n */\n disabled?: boolean\n /**\n * Sets the dropdown as interactive or not.\n */\n readOnly?: boolean\n}>\n\ninterface DropdownPropsSingle {\n /**\n * Prop 'multiple' indicating whether multiple values are allowed.\n */\n multiple?: false\n /**\n * The value of the select when initially rendered. Use when you do not need to control the state of the select.\n */\n defaultValue?: string\n /**\n * The controlled value of the select. Should be used in conjunction with `onValueChange`.\n */\n value?: string\n /**\n * Event handler called when the value changes.\n */\n onValueChange?: (value: string) => void\n}\n\ninterface DropdownPropsMultiple {\n /**\n * Prop 'multiple' indicating whether multiple values are allowed.\n */\n multiple: true\n /**\n * The value of the select when initially rendered. Use when you do not need to control the state of the select.\n */\n defaultValue?: string[]\n /**\n * The controlled value of the select. Should be used in conjunction with `onValueChange`.\n */\n value?: string[]\n /**\n * Event handler called when the value changes.\n */\n onValueChange?: (value: string[]) => void\n}\n\nexport type DropdownContextProps = DropdownContextCommonProps &\n (DropdownPropsSingle | DropdownPropsMultiple)\n\nconst DropdownContext = createContext<DropdownContextState | null>(null)\n\nexport const ID_PREFIX = ':dropdown'\n\nexport const DropdownProvider = ({\n children,\n defaultValue,\n value,\n onValueChange,\n open,\n onOpenChange,\n defaultOpen,\n multiple = false,\n disabled: disabledProp = false,\n readOnly: readOnlyProp = false,\n state: stateProp,\n}: DropdownContextProps) => {\n const [itemsMap, setItemsMap] = useState<ItemsMap>(getItemsFromChildren(children))\n const [hasPopover, setHasPopover] = useState<boolean>(\n hasChildComponent(children, 'Dropdown.Popover')\n )\n const [lastInteractionType, setLastInteractionType] = useState<'mouse' | 'keyboard'>('mouse')\n\n const field = useFormFieldControl()\n\n const state = field.state || stateProp\n\n const internalFieldLabelID = `${ID_PREFIX}-label-${useId()}`\n const internalFieldID = `${ID_PREFIX}-input-${useId()}`\n const id = field.id || internalFieldID\n const labelId = field.labelId || internalFieldLabelID\n\n const disabled = field.disabled ?? disabledProp\n const readOnly = field.readOnly ?? readOnlyProp\n\n const dropdownState = useDropdown({\n itemsMap,\n defaultValue,\n value,\n onValueChange,\n open,\n onOpenChange,\n defaultOpen,\n multiple,\n id,\n labelId,\n })\n\n /**\n * Indices in a Map are set when an element is added to the Map.\n * If for some reason, in the Dropdown:\n * - items order changes\n * - items are added\n * - items are removed\n *\n * The Map must be rebuilt from the new children in order to preserve logical indices.\n *\n * Downshift is heavily indices based for keyboard navigation, so it it important.\n */\n useEffect(() => {\n const newMap = getItemsFromChildren(children)\n\n const previousItems = [...itemsMap.values()]\n const newItems = [...newMap.values()]\n\n const hasItemsChanges =\n previousItems.length !== newItems.length ||\n previousItems.some((item, index) => {\n const hasUpdatedValue = item.value !== newItems[index]?.value\n const hasUpdatedText = item.text !== newItems[index]?.text\n\n return hasUpdatedValue || hasUpdatedText\n })\n\n if (hasItemsChanges) {\n setItemsMap(newMap)\n }\n }, [children])\n\n /**\n * Warning:\n * Downshift is expecting the items list to always be rendered, as per a11y guidelines.\n * This is why the `Popover` is always opened in this component, but visually hidden instead from Dropdown.Popover.\n */\n const [WrapperComponent, wrapperProps] = hasPopover ? [Popover, { open: true }] : [Fragment, {}]\n\n return (\n <DropdownContext.Provider\n value={{\n multiple,\n disabled,\n readOnly,\n ...dropdownState,\n itemsMap,\n highlightedItem: getElementByIndex(itemsMap, dropdownState.highlightedIndex),\n hasPopover,\n setHasPopover,\n state,\n lastInteractionType,\n setLastInteractionType,\n }}\n >\n <WrapperComponent {...wrapperProps}>{children}</WrapperComponent>\n </DropdownContext.Provider>\n )\n}\n\nexport const useDropdownContext = () => {\n const context = useContext(DropdownContext)\n\n if (!context) {\n throw Error('useDropdownContext must be used within a Dropdown provider')\n }\n\n return context\n}\n","import { type DropdownContextProps, DropdownProvider } from './DropdownContext'\n\nexport type DropdownProps = DropdownContextProps\n\nexport const Dropdown = ({ children, ...props }: DropdownProps) => {\n return <DropdownProvider {...props}>{children}</DropdownProvider>\n}\n\nDropdown.displayName = 'Dropdown'\n","import { cx } from 'class-variance-authority'\nimport { Ref } from 'react'\n\ninterface DividerProps {\n className?: string\n ref?: Ref<HTMLDivElement>\n}\n\nexport const Divider = ({ className, ref: forwardedRef }: DividerProps) => {\n return <div ref={forwardedRef} className={cx('my-md border-b-sm border-outline', className)} />\n}\n\nDivider.displayName = 'Dropdown.Divider'\n","import { createContext, type PropsWithChildren, useContext, useId } from 'react'\n\nimport { ID_PREFIX } from './DropdownContext'\n\nexport interface DropdownContextState {\n labelId: string\n}\n\ntype DropdownContextProps = PropsWithChildren\n\nconst DropdownGroupContext = createContext<DropdownContextState | null>(null)\n\nexport const DropdownGroupProvider = ({ children }: DropdownContextProps) => {\n const labelId = `${ID_PREFIX}-group-label-${useId()}`\n\n return (\n <DropdownGroupContext.Provider value={{ labelId }}>{children}</DropdownGroupContext.Provider>\n )\n}\n\nexport const useDropdownGroupContext = () => {\n const context = useContext(DropdownGroupContext)\n\n if (!context) {\n throw Error('useDropdownGroupContext must be used within a DropdownGroup provider')\n }\n\n return context\n}\n","import { cx } from 'class-variance-authority'\nimport { ReactNode, Ref } from 'react'\n\nimport { DropdownGroupProvider, useDropdownGroupContext } from './DropdownItemsGroupContext'\n\ninterface GroupProps {\n children: ReactNode\n className?: string\n ref?: Ref<HTMLDivElement>\n}\n\nexport const Group = ({ children, ref: forwardedRef, ...props }: GroupProps) => {\n return (\n <DropdownGroupProvider>\n <GroupContent ref={forwardedRef} {...props}>\n {children}\n </GroupContent>\n </DropdownGroupProvider>\n )\n}\n\nconst GroupContent = ({ children, className, ref: forwardedRef }: GroupProps) => {\n const { labelId } = useDropdownGroupContext()\n\n return (\n <div ref={forwardedRef} role=\"group\" aria-labelledby={labelId} className={cx(className)}>\n {children}\n </div>\n )\n}\n\nGroup.displayName = 'Dropdown.Group'\n","import {\n createContext,\n Dispatch,\n type PropsWithChildren,\n SetStateAction,\n useContext,\n useState,\n} from 'react'\n\nimport { useDropdownContext } from './DropdownContext'\nimport { DropdownItem } from './types'\nimport { getIndexByKey, getItemText } from './utils'\n\ntype ItemTextId = string | undefined\n\ninterface DropdownItemContextState {\n textId: ItemTextId\n setTextId: Dispatch<SetStateAction<ItemTextId>>\n isSelected: boolean\n itemData: DropdownItem\n index: number\n disabled: boolean\n}\n\nconst DropdownItemContext = createContext<DropdownItemContextState | null>(null)\n\nexport const DropdownItemProvider = ({\n value,\n disabled = false,\n children,\n}: PropsWithChildren<{ value: string; disabled?: boolean }>) => {\n const { multiple, itemsMap, selectedItem, selectedItems } = useDropdownContext()\n\n const [textId, setTextId] = useState<ItemTextId>(undefined)\n\n const index = getIndexByKey(itemsMap, value)\n const itemData: DropdownItem = { disabled, value, text: getItemText(children) }\n\n const isSelected = multiple\n ? selectedItems.some(selectedItem => selectedItem.value === value)\n : selectedItem?.value === value\n\n return (\n <DropdownItemContext.Provider\n value={{ textId, setTextId, isSelected, itemData, index, disabled }}\n >\n {children}\n </DropdownItemContext.Provider>\n )\n}\n\nexport const useDropdownItemContext = () => {\n const context = useContext(DropdownItemContext)\n\n if (!context) {\n throw Error('useDropdownItemContext must be used within a DropdownItem provider')\n }\n\n return context\n}\n","import { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport { cva, cx } from 'class-variance-authority'\nimport { type HTMLAttributes, type ReactNode, Ref } from 'react'\n\nimport { useDropdownContext } from './DropdownContext'\nimport { DropdownItemProvider, useDropdownItemContext } from './DropdownItemContext'\n\nexport interface ItemProps extends HTMLAttributes<HTMLLIElement> {\n disabled?: boolean\n value: string\n children: ReactNode\n className?: string\n ref?: Ref<HTMLLIElement>\n}\n\nexport const Item = ({ children, ref: forwardedRef, ...props }: ItemProps) => {\n const { value, disabled } = props\n\n return (\n <DropdownItemProvider value={value} disabled={disabled}>\n <ItemContent ref={forwardedRef} {...props}>\n {children}\n </ItemContent>\n </DropdownItemProvider>\n )\n}\n\nconst styles = cva('px-lg py-md text-body-1', {\n variants: {\n selected: {\n true: 'font-bold',\n },\n disabled: {\n true: 'opacity-dim-3 cursor-not-allowed',\n false: 'cursor-pointer',\n },\n highlighted: {\n true: '',\n },\n interactionType: {\n mouse: '',\n keyboard: '',\n },\n },\n compoundVariants: [\n {\n highlighted: true,\n interactionType: 'mouse',\n class: 'bg-surface-hovered',\n },\n {\n highlighted: true,\n interactionType: 'keyboard',\n class: 'u-outline',\n },\n ],\n})\n\nconst ItemContent = ({\n className,\n disabled = false,\n value,\n children,\n ref: forwardedRef,\n}: ItemProps) => {\n const { getItemProps, highlightedItem, lastInteractionType } = useDropdownContext()\n const { textId, index, itemData, isSelected } = useDropdownItemContext()\n\n const isHighlighted = highlightedItem?.value === value\n\n const { ref: downshiftRef, ...downshiftItemProps } = getItemProps({ item: itemData, index })\n const ref = useMergeRefs(forwardedRef, downshiftRef)\n\n return (\n <li\n ref={ref}\n className={cx(\n styles({\n selected: isSelected,\n disabled,\n highlighted: isHighlighted,\n interactionType: lastInteractionType,\n className,\n })\n )}\n key={value}\n {...downshiftItemProps}\n aria-selected={isSelected}\n aria-labelledby={textId}\n >\n {children}\n </li>\n )\n}\n\nItem.displayName = 'Dropdown.Item'\n","import { Check } from '@spark-ui/icons/Check'\nimport { cx } from 'class-variance-authority'\nimport { ReactNode, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport { useDropdownItemContext } from './DropdownItemContext'\n\nexport interface ItemIndicatorProps {\n children?: ReactNode\n className?: string\n label?: string\n ref?: Ref<HTMLSpanElement>\n}\n\nexport const ItemIndicator = ({\n className,\n children,\n label,\n ref: forwardedRef,\n}: ItemIndicatorProps) => {\n const { disabled, isSelected } = useDropdownItemContext()\n\n const childElement = children || (\n <Icon size=\"sm\">\n <Check aria-label={label} />\n </Icon>\n )\n\n return (\n <span\n ref={forwardedRef}\n className={cx('min-h-sz-16 min-w-sz-16 flex', disabled && 'opacity-dim-3', className)}\n >\n {isSelected && childElement}\n </span>\n )\n}\n\nItemIndicator.displayName = 'Dropdown.ItemIndicator'\n","import { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport { cx } from 'class-variance-authority'\nimport { ReactNode, Ref, useLayoutEffect, useRef } from 'react'\n\nimport { useDropdownContext } from './DropdownContext'\n\ninterface ItemsProps {\n children: ReactNode\n className?: string\n ref?: Ref<HTMLUListElement>\n}\n\n/**\n * BUGFIX\n *\n * 1. The !pointer-events-auto class is needed to prevent a bug\n * which cannot be reproduced when running Storybook locally,\n * in scenarios such as when a Dropdown is nested within a Dialog,\n * the \"props\" object, containing styles computed by Radix\n * may erroneously contain \"pointerEvents = 'none'\", while the Dropdown is open,\n * making it impossible to select a value using a pointer device\n *\n * 2. Closed + absolute still grows scrollable overflow of ancestors (e.g. Table.Grid).\n * max-h-0 overflow-hidden collapses that box; p-lg only applies when open.\n */\n\nexport const Items = ({ children, className, ref: forwardedRef, ...props }: ItemsProps) => {\n const { isOpen, getMenuProps, hasPopover, setLastInteractionType } = useDropdownContext()\n\n const { ref: downshiftRef, ...downshiftMenuProps } = getMenuProps({\n onMouseMove: () => {\n setLastInteractionType('mouse')\n },\n })\n\n const innerRef = useRef<HTMLElement>(null)\n\n const ref = useMergeRefs(forwardedRef, downshiftRef, innerRef)\n\n useLayoutEffect(() => {\n if (!hasPopover) return\n if (!innerRef.current) return\n\n if (innerRef.current.parentElement) {\n innerRef.current.parentElement.style.pointerEvents = isOpen ? '' : 'none'\n innerRef.current.style.pointerEvents = isOpen ? '' : 'none'\n }\n }, [isOpen, hasPopover])\n\n return (\n <ul\n ref={ref}\n className={cx(\n className,\n 'flex flex-col',\n isOpen\n ? 'pointer-events-auto! block' /* 1 */\n : 'pointer-events-none invisible absolute max-h-0 min-h-0 overflow-hidden opacity-0',\n hasPopover && isOpen && 'p-lg'\n )}\n {...props}\n {...downshiftMenuProps}\n /**\n * When used inside a Radix dialog/drawer, the focus trap behaviour of Radix prevent scrolling and hovering inside absolutely positioned elements in the dialog.\n * It does programatically in JS using the `style` attribute.\n *\n * Issue is known but there is no clear fix in sight: https://github.com/radix-ui/primitives/issues/1159\n *\n * A solution would be to make an abstraction of `Dialog.Overlay` instead of using the radix one, but that would mean managing body scroll freeze and scrollbar shifting ourselves.\n *\n */\n data-spark-component=\"dropdown-items\"\n >\n {children}\n </ul>\n )\n}\n\nItems.displayName = 'Dropdown.Items'\n","import { cx } from 'class-variance-authority'\nimport { Ref, useEffect, useId } from 'react'\n\nimport { ID_PREFIX } from './DropdownContext'\nimport { useDropdownItemContext } from './DropdownItemContext'\n\nexport interface ItemTextProps {\n children: string\n ref?: Ref<HTMLSpanElement>\n}\n\nexport const ItemText = ({ children, ref: forwardedRef }: ItemTextProps) => {\n const id = `${ID_PREFIX}-item-text-${useId()}`\n\n const { setTextId } = useDropdownItemContext()\n\n useEffect(() => {\n setTextId(id)\n\n return () => setTextId(undefined)\n })\n\n return (\n <span id={id} className={cx('inline')} ref={forwardedRef}>\n {children}\n </span>\n )\n}\n\nItemText.displayName = 'Dropdown.ItemText'\n","import { cx } from 'class-variance-authority'\nimport { Ref } from 'react'\n\nimport { useDropdownGroupContext } from './DropdownItemsGroupContext'\n\ninterface LabelProps {\n children: string\n className?: string\n ref?: Ref<HTMLDivElement>\n}\n\nexport const Label = ({ children, className, ref: forwardedRef }: LabelProps) => {\n const { labelId } = useDropdownGroupContext()\n\n return (\n <div\n ref={forwardedRef}\n id={labelId}\n className={cx('px-md py-sm text-body-2 text-neutral italic', className)}\n >\n {children}\n </div>\n )\n}\n\nLabel.displayName = 'Dropdown.Label'\n","import { ReactElement } from 'react'\n\nimport { Icon } from '../icon'\n\nexport const LeadingIcon = ({ children }: { children: ReactElement }) => {\n return (\n <Icon size={'sm'} className=\"shrink-0\">\n {children}\n </Icon>\n )\n}\n\nLeadingIcon.displayName = 'Dropdown.LeadingIcon'\n","import { cx } from 'class-variance-authority'\nimport { ComponentProps, useEffect } from 'react'\n\nimport { Popover as SparkPopover } from '../popover'\nimport { useDropdownContext } from './DropdownContext'\n\nexport const Popover = ({\n children,\n matchTriggerWidth = true,\n sideOffset = 4,\n className,\n elevation = 'dropdown',\n ref: forwardedRef,\n ...props\n}: ComponentProps<typeof SparkPopover.Content>) => {\n const ctx = useDropdownContext()\n\n useEffect(() => {\n ctx.setHasPopover(true)\n\n return () => ctx.setHasPopover(false)\n }, [])\n\n return (\n <SparkPopover.Content\n ref={forwardedRef}\n inset\n asChild\n matchTriggerWidth={matchTriggerWidth}\n elevation={elevation}\n className={cx('relative', className)}\n sideOffset={sideOffset}\n onOpenAutoFocus={e => {\n /**\n * With a combobox pattern, the focus should remain on the trigger at all times.\n * Passing the focus to the dropdown popover would break keyboard navigation.\n */\n e.preventDefault()\n }}\n {...props}\n data-spark-component=\"dropdown-popover\"\n >\n {children}\n </SparkPopover.Content>\n )\n}\n\nPopover.displayName = 'Dropdown.Popover'\n","import { ReactElement } from 'react'\n\nimport { Popover as SparkPopover } from '../popover'\n\nexport const Portal: typeof SparkPopover.Portal = ({ children, ...rest }): ReactElement => (\n <SparkPopover.Portal {...rest}>{children}</SparkPopover.Portal>\n)\n\nPortal.displayName = 'Dropdown.Portal'\n","import { cva } from 'class-variance-authority'\n\nexport const styles = cva(\n [\n 'flex w-full items-center justify-between',\n 'min-h-sz-44 rounded-lg bg-surface text-on-surface px-lg',\n 'text-body-1',\n // outline styles\n 'ring-1 outline-hidden ring-inset focus:ring-2 focus:ring-focus',\n ],\n {\n variants: {\n state: {\n undefined: 'ring-outline',\n error: 'ring-error',\n alert: 'ring-alert',\n success: 'ring-success',\n },\n disabled: {\n true: 'disabled:bg-on-surface/dim-5 cursor-not-allowed text-on-surface/dim-3',\n },\n readOnly: {\n true: 'disabled:bg-on-surface/dim-5 cursor-not-allowed text-on-surface/dim-3',\n },\n },\n compoundVariants: [\n {\n disabled: false,\n state: undefined,\n class: 'default:hover:ring-outline-high',\n },\n ],\n }\n)\n","import { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport { ArrowHorizontalDown } from '@spark-ui/icons/ArrowHorizontalDown'\nimport { cx } from 'class-variance-authority'\nimport { Fragment, ReactNode, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport { Popover } from '../popover'\nimport { VisuallyHidden } from '../visually-hidden'\nimport { useDropdownContext } from './DropdownContext'\nimport { styles } from './DropdownTrigger.styles'\n\ninterface TriggerProps {\n 'aria-label'?: string\n children: ReactNode\n className?: string\n ref?: Ref<HTMLButtonElement>\n}\n\nexport const Trigger = ({\n 'aria-label': ariaLabel,\n children,\n className,\n ref: forwardedRef,\n}: TriggerProps) => {\n const {\n getToggleButtonProps,\n getDropdownProps,\n getLabelProps,\n hasPopover,\n disabled,\n readOnly,\n state,\n setLastInteractionType,\n isOpen,\n } = useDropdownContext()\n\n const [WrapperComponent, wrapperProps] = hasPopover\n ? [Popover.Trigger, { asChild: true }]\n : [Fragment, {}]\n\n const { ref: downshiftRef, ...downshiftTriggerProps } = getToggleButtonProps({\n ...getDropdownProps(),\n onKeyDown: e => {\n setLastInteractionType('keyboard')\n // Escape: bubble-phase parents; table grid defers in capture via table-keyboard.\n if (e.key === 'Escape' && isOpen) {\n e.stopPropagation()\n }\n },\n })\n\n const isExpanded = downshiftTriggerProps['aria-expanded']\n\n const ref = useMergeRefs(forwardedRef, downshiftRef)\n\n return (\n <>\n {ariaLabel && (\n <VisuallyHidden>\n <label {...getLabelProps()}>{ariaLabel}</label>\n </VisuallyHidden>\n )}\n <WrapperComponent {...wrapperProps}>\n <button\n type=\"button\"\n ref={ref}\n disabled={disabled || readOnly}\n className={styles({ className, state, disabled, readOnly })}\n {...downshiftTriggerProps}\n data-spark-component=\"dropdown-trigger\"\n >\n <span className=\"gap-md flex items-center justify-start\">{children}</span>\n\n <Icon\n className={cx('ml-md shrink-0 rotate-0 transition duration-100 ease-in', {\n 'rotate-180': isExpanded,\n })}\n size=\"sm\"\n >\n <ArrowHorizontalDown />\n </Icon>\n </button>\n </WrapperComponent>\n </>\n )\n}\n\nTrigger.displayName = 'Dropdown.Trigger'\n","import { cx } from 'class-variance-authority'\nimport { ReactNode, Ref } from 'react'\n\nimport { useDropdownContext } from './DropdownContext'\n\nexport interface ValueProps {\n children?: ReactNode\n className?: string\n placeholder: string\n ref?: Ref<HTMLSpanElement>\n}\n\nexport const Value = ({ children, className, placeholder, ref: forwardedRef }: ValueProps) => {\n const { selectedItem, multiple, selectedItems } = useDropdownContext()\n\n const hasSelectedItems = !!(multiple ? selectedItems.length : selectedItem)\n const text = multiple ? selectedItems[0]?.text : selectedItem?.text\n const suffix = selectedItems.length > 1 ? `, +${selectedItems.length - 1}` : ''\n\n return (\n <span ref={forwardedRef} className={cx('flex shrink items-center text-left', className)}>\n <span\n className={cx(\n 'line-clamp-1 flex-1 overflow-hidden break-all text-ellipsis',\n !hasSelectedItems && 'text-on-surface/dim-1'\n )}\n >\n {!hasSelectedItems ? placeholder : children || text}\n </span>\n {suffix && <span>{suffix}</span>}\n </span>\n )\n}\n\nValue.displayName = 'Dropdown.Value'\n","import { Dropdown as Root } from './Dropdown'\nimport { DropdownProvider, useDropdownContext } from './DropdownContext'\nimport { Divider } from './DropdownDivider'\nimport { Group } from './DropdownGroup'\nimport { Item } from './DropdownItem'\nimport { ItemIndicator } from './DropdownItemIndicator'\nimport { Items } from './DropdownItems'\nimport { ItemText } from './DropdownItemText'\nimport { Label } from './DropdownLabel'\nimport { LeadingIcon } from './DropdownLeadingIcon'\nimport { Popover } from './DropdownPopover'\nimport { Portal } from './DropdownPortal'\nimport { Trigger } from './DropdownTrigger'\nimport { Value } from './DropdownValue'\n\nexport { useDropdownContext, DropdownProvider }\n\nexport const Dropdown: typeof Root & {\n Group: typeof Group\n Item: typeof Item\n Items: typeof Items\n ItemText: typeof ItemText\n ItemIndicator: typeof ItemIndicator\n Label: typeof Label\n Popover: typeof Popover\n Divider: typeof Divider\n Trigger: typeof Trigger\n Value: typeof Value\n LeadingIcon: typeof LeadingIcon\n Portal: typeof Portal\n} = Object.assign(Root, {\n Group,\n Item,\n Items,\n ItemText,\n ItemIndicator,\n Label,\n Popover,\n Divider,\n Trigger,\n Value,\n LeadingIcon,\n Portal,\n})\n\nDropdown.displayName = 'Dropdown'\nGroup.displayName = 'Dropdown.Group'\nItems.displayName = 'Dropdown.Items'\nItem.displayName = 'Dropdown.Item'\nItemText.displayName = 'Dropdown.ItemText'\nItemIndicator.displayName = 'Dropdown.ItemIndicator'\nLabel.displayName = 'Dropdown.Label'\nPopover.displayName = 'Dropdown.Popover'\nDivider.displayName = 'Dropdown.Divider'\nTrigger.displayName = 'Dropdown.Trigger'\nValue.displayName = 'Dropdown.Value'\nLeadingIcon.displayName = 'Dropdown.LeadingIcon'\nPortal.displayName = 'Dropdown.Portal'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/dropdown/useDropdown.ts","../../src/dropdown/utils.ts","../../src/dropdown/DropdownContext.tsx","../../src/dropdown/Dropdown.tsx","../../src/dropdown/DropdownDivider.tsx","../../src/dropdown/DropdownItemsGroupContext.tsx","../../src/dropdown/DropdownGroup.tsx","../../src/dropdown/DropdownItemContext.tsx","../../src/dropdown/DropdownItem.tsx","../../src/dropdown/DropdownItemIndicator.tsx","../../src/dropdown/DropdownItems.tsx","../../src/dropdown/DropdownItemText.tsx","../../src/dropdown/DropdownLabel.tsx","../../src/dropdown/DropdownLeadingIcon.tsx","../../src/dropdown/DropdownPopover.tsx","../../src/dropdown/DropdownPortal.tsx","../../src/dropdown/DropdownTrigger.styles.tsx","../../src/dropdown/DropdownTrigger.tsx","../../src/dropdown/DropdownValue.tsx","../../src/dropdown/index.ts"],"sourcesContent":["import { useMultipleSelection, useSelect, UseSelectProps } from 'downshift'\n\nimport { type DropdownItem, type ItemsMap } from './types'\n\ntype OnChangeValueType = string & string[]\n\nexport interface DownshiftProps {\n itemsMap: ItemsMap\n value: string | string[] | undefined\n defaultValue: string | string[] | undefined\n onValueChange: ((value: string) => void) | ((value: string[]) => void) | undefined\n open: boolean | undefined\n onOpenChange: ((isOpen: boolean) => void) | undefined\n defaultOpen: boolean | undefined\n multiple: boolean | undefined\n id: string\n labelId: string\n}\n\n/**\n * This hook abstract the complexity of using downshift with both single and multiple selection.\n */\nexport const useDropdown = ({\n itemsMap,\n defaultValue,\n value,\n onValueChange,\n open,\n onOpenChange,\n defaultOpen,\n multiple,\n id,\n labelId,\n}: DownshiftProps) => {\n const items = [...itemsMap.values()]\n\n const downshiftMultipleSelection = useMultipleSelection<DropdownItem>({\n selectedItems:\n value != null && multiple\n ? items.filter(item =>\n multiple ? (value as string[]).includes(item.value) : value === item.value\n )\n : undefined,\n initialSelectedItems:\n defaultValue != null && multiple\n ? items.filter(item =>\n multiple ? (defaultValue as string[]).includes(item.value) : defaultValue === item.value\n )\n : undefined,\n\n onSelectedItemsChange: ({ selectedItems }) => {\n if (selectedItems != null && multiple) {\n onValueChange?.(selectedItems.map(item => item.value) as OnChangeValueType)\n }\n },\n })\n\n /**\n * Custom state reducer for multiple selection behaviour:\n * - keeps the component opened when the user selects an item\n * - preserves the higlighted index when the user select an item\n * - selected items can be unselected, even the last selected item (as opposed to single selection behaviour)\n */\n const stateReducer: UseSelectProps<DropdownItem>['stateReducer'] = (state, { changes, type }) => {\n if (!multiple) return changes\n\n const { selectedItems, removeSelectedItem, addSelectedItem } = downshiftMultipleSelection\n\n // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check\n switch (type) {\n case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:\n case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton:\n case useSelect.stateChangeTypes.ItemClick:\n if (changes.selectedItem != null) {\n const isAlreadySelected = selectedItems.some(\n selectedItem => selectedItem.value === changes.selectedItem?.value\n )\n\n if (isAlreadySelected) removeSelectedItem(changes.selectedItem)\n else addSelectedItem(changes.selectedItem)\n }\n\n return {\n ...changes,\n isOpen: true, // keep the menu open after selection.\n highlightedIndex: state.highlightedIndex, // preserve highlighted index position\n }\n default:\n return changes\n }\n }\n\n const downshift = useSelect<DropdownItem>({\n items,\n isItemDisabled: item => item.disabled,\n itemToString: item => (item ? item.text : ''),\n // a11y attributes\n id,\n labelId,\n // Controlled open state\n isOpen: open, // undefined must be passed for stateful behaviour (uncontrolled)\n onIsOpenChange: ({ isOpen }) => {\n if (isOpen != null) onOpenChange?.(isOpen)\n },\n initialIsOpen: defaultOpen ?? false,\n stateReducer,\n // Controlled mode (single selection)\n selectedItem: value != null && !multiple ? itemsMap.get(value as string) || null : undefined,\n initialSelectedItem:\n (defaultValue != null || value != null) && !multiple\n ? itemsMap.get(defaultValue as string) || null\n : undefined,\n onSelectedItemChange: ({ selectedItem }) => {\n if (selectedItem?.value != null && !multiple) {\n onValueChange?.(selectedItem?.value as OnChangeValueType)\n }\n },\n /**\n * 1. Downshift default behaviour is to scroll into view the highlighted item when the dropdown opens. This behaviour is not stable and scrolls the dropdown to the bottom of the screen.\n */\n scrollIntoView: node => {\n if (node) {\n node.scrollIntoView({ block: 'nearest' })\n }\n\n return undefined\n },\n })\n\n return {\n ...downshift,\n ...downshiftMultipleSelection,\n /** There is a Downshift bug in React 19, it duplicates some selectedItems */\n selectedItems: [...new Set(downshiftMultipleSelection.selectedItems)],\n }\n}\n","import { type FC, isValidElement, type ReactElement, type ReactNode, Children } from 'react'\n\nimport { type ItemProps } from './DropdownItem'\nimport { ItemTextProps } from './DropdownItemText'\nimport { type DropdownItem, type ItemsMap } from './types'\n\nexport function getIndexByKey(map: ItemsMap, targetKey: string) {\n let index = 0\n for (const [key] of map.entries()) {\n if (key === targetKey) {\n return index\n }\n index++\n }\n\n return -1\n}\n\nconst getKeyAtIndex = (map: ItemsMap, index: number) => {\n let i = 0\n for (const key of map.keys()) {\n if (i === index) return key\n i++\n }\n\n return undefined\n}\n\nexport const getElementByIndex = (map: ItemsMap, index: number) => {\n const key = getKeyAtIndex(map, index)\n\n return key !== undefined ? map.get(key) : undefined\n}\n\nconst getElementDisplayName = (element?: ReactElement) => {\n return element ? (element.type as FC & { displayName?: string }).displayName : ''\n}\n\nexport const getOrderedItems = (\n children: ReactNode,\n result: DropdownItem[] = []\n): DropdownItem[] => {\n Children.forEach(children, child => {\n if (!isValidElement(child)) return\n\n if (getElementDisplayName(child) === 'Dropdown.Item') {\n const childProps = child.props as ItemProps\n result.push({\n value: childProps.value,\n disabled: !!childProps.disabled,\n text: getItemText(childProps.children),\n })\n }\n\n if ((child.props as { children: ReactNode }).children) {\n getOrderedItems((child.props as { children: ReactNode }).children, result)\n }\n })\n\n return result\n}\n\n/**\n * If Dropdown.Item children:\n * - is a string, then the string is used.\n * - is JSX markup, then we look for Dropdown.ItemText to get its string value.\n */\nexport const getItemText = (children: ReactNode, itemText = ''): string => {\n if (typeof children === 'string') {\n return children\n }\n\n Children.forEach(children, child => {\n if (!isValidElement(child)) return\n\n if (getElementDisplayName(child) === 'Dropdown.ItemText') {\n itemText = (child.props as ItemTextProps).children\n }\n\n if ((child.props as { children: ReactNode }).children) {\n getItemText((child.props as { children: ReactNode }).children, itemText)\n }\n })\n\n return itemText\n}\n\nexport const getItemsFromChildren = (children: ReactNode): ItemsMap => {\n const newMap: ItemsMap = new Map()\n\n getOrderedItems(children).forEach(itemData => {\n newMap.set(itemData.value, itemData)\n })\n\n return newMap\n}\n\nexport const hasChildComponent = (children: ReactNode, displayName: string): boolean => {\n return Children.toArray(children).some(child => {\n if (!isValidElement(child)) return false\n\n if (getElementDisplayName(child) === displayName) {\n return true\n } else if ((child.props as { children: ReactNode }).children) {\n return hasChildComponent((child.props as { children: ReactNode }).children, displayName)\n }\n\n return false\n })\n}\n","import { useFormFieldControl } from '@spark-ui/components/form-field'\nimport {\n createContext,\n Dispatch,\n Fragment,\n PropsWithChildren,\n SetStateAction,\n useContext,\n useEffect,\n useId,\n useState,\n} from 'react'\n\nimport { Popover } from '../popover'\nimport { type DownshiftState, type DropdownItem, type ItemsMap } from './types'\nimport { useDropdown } from './useDropdown'\nimport { getElementByIndex, getItemsFromChildren, hasChildComponent } from './utils'\n\nexport interface DropdownContextState extends DownshiftState {\n itemsMap: ItemsMap\n highlightedItem: DropdownItem | undefined\n hasPopover: boolean\n setHasPopover: Dispatch<SetStateAction<boolean>>\n multiple: boolean\n disabled: boolean\n readOnly: boolean\n state?: 'error' | 'alert' | 'success'\n lastInteractionType: 'mouse' | 'keyboard'\n setLastInteractionType: (type: 'mouse' | 'keyboard') => void\n}\n\nexport type DropdownContextCommonProps = PropsWithChildren<{\n /**\n * The controlled open state of the select. Must be used in conjunction with `onOpenChange`.\n */\n open?: boolean\n /**\n * Event handler called when the open state of the select changes.\n */\n onOpenChange?: (isOpen: boolean) => void\n /**\n * The open state of the select when it is initially rendered. Use when you do not need to control its open state.\n */\n defaultOpen?: boolean\n /**\n * Use `state` prop to assign a specific state to the dropdown, choosing from: `error`, `alert` and `success`. By doing so, the outline styles will be updated.\n */\n state?: 'error' | 'alert' | 'success'\n /**\n * When true, prevents the user from interacting with the dropdown.\n */\n disabled?: boolean\n /**\n * Sets the dropdown as interactive or not.\n */\n readOnly?: boolean\n}>\n\ninterface DropdownPropsSingle {\n /**\n * Prop 'multiple' indicating whether multiple values are allowed.\n */\n multiple?: false\n /**\n * The value of the select when initially rendered. Use when you do not need to control the state of the select.\n */\n defaultValue?: string\n /**\n * The controlled value of the select. Should be used in conjunction with `onValueChange`.\n */\n value?: string\n /**\n * Event handler called when the value changes.\n */\n onValueChange?: (value: string) => void\n}\n\ninterface DropdownPropsMultiple {\n /**\n * Prop 'multiple' indicating whether multiple values are allowed.\n */\n multiple: true\n /**\n * The value of the select when initially rendered. Use when you do not need to control the state of the select.\n */\n defaultValue?: string[]\n /**\n * The controlled value of the select. Should be used in conjunction with `onValueChange`.\n */\n value?: string[]\n /**\n * Event handler called when the value changes.\n */\n onValueChange?: (value: string[]) => void\n}\n\nexport type DropdownContextProps = DropdownContextCommonProps &\n (DropdownPropsSingle | DropdownPropsMultiple)\n\nconst DropdownContext = createContext<DropdownContextState | null>(null)\n\nexport const ID_PREFIX = ':dropdown'\n\nexport const DropdownProvider = ({\n children,\n defaultValue,\n value,\n onValueChange,\n open,\n onOpenChange,\n defaultOpen,\n multiple = false,\n disabled: disabledProp = false,\n readOnly: readOnlyProp = false,\n state: stateProp,\n}: DropdownContextProps) => {\n const [itemsMap, setItemsMap] = useState<ItemsMap>(getItemsFromChildren(children))\n const [hasPopover, setHasPopover] = useState<boolean>(\n hasChildComponent(children, 'Dropdown.Popover')\n )\n const [lastInteractionType, setLastInteractionType] = useState<'mouse' | 'keyboard'>('mouse')\n\n const field = useFormFieldControl()\n\n const state = field.state || stateProp\n\n const internalFieldLabelID = `${ID_PREFIX}-label-${useId()}`\n const internalFieldID = `${ID_PREFIX}-input-${useId()}`\n const id = field.id || internalFieldID\n const labelId = field.labelId || internalFieldLabelID\n\n const disabled = field.disabled ?? disabledProp\n const readOnly = field.readOnly ?? readOnlyProp\n\n const dropdownState = useDropdown({\n itemsMap,\n defaultValue,\n value,\n onValueChange,\n open,\n onOpenChange,\n defaultOpen,\n multiple,\n id,\n labelId,\n })\n\n /**\n * Indices in a Map are set when an element is added to the Map.\n * If for some reason, in the Dropdown:\n * - items order changes\n * - items are added\n * - items are removed\n *\n * The Map must be rebuilt from the new children in order to preserve logical indices.\n *\n * Downshift is heavily indices based for keyboard navigation, so it it important.\n */\n useEffect(() => {\n const newMap = getItemsFromChildren(children)\n\n const previousItems = [...itemsMap.values()]\n const newItems = [...newMap.values()]\n\n const hasItemsChanges =\n previousItems.length !== newItems.length ||\n previousItems.some((item, index) => {\n const hasUpdatedValue = item.value !== newItems[index]?.value\n const hasUpdatedText = item.text !== newItems[index]?.text\n\n return hasUpdatedValue || hasUpdatedText\n })\n\n if (hasItemsChanges) {\n setItemsMap(newMap)\n }\n }, [children])\n\n /**\n * Warning:\n * Downshift is expecting the items list to always be rendered, as per a11y guidelines.\n * This is why the `Popover` is always opened in this component, but visually hidden instead from Dropdown.Popover.\n */\n const [WrapperComponent, wrapperProps] = hasPopover ? [Popover, { open: true }] : [Fragment, {}]\n\n return (\n <DropdownContext.Provider\n value={{\n multiple,\n disabled,\n readOnly,\n ...dropdownState,\n itemsMap,\n highlightedItem: getElementByIndex(itemsMap, dropdownState.highlightedIndex),\n hasPopover,\n setHasPopover,\n state,\n lastInteractionType,\n setLastInteractionType,\n }}\n >\n <WrapperComponent {...wrapperProps}>{children}</WrapperComponent>\n </DropdownContext.Provider>\n )\n}\n\nexport const useDropdownContext = () => {\n const context = useContext(DropdownContext)\n\n if (!context) {\n throw Error('useDropdownContext must be used within a Dropdown provider')\n }\n\n return context\n}\n","import { type DropdownContextProps, DropdownProvider } from './DropdownContext'\n\nexport type DropdownProps = DropdownContextProps\n\nexport const Dropdown = ({ children, ...props }: DropdownProps) => {\n return <DropdownProvider {...props}>{children}</DropdownProvider>\n}\n\nDropdown.displayName = 'Dropdown'\n","import { cx } from 'class-variance-authority'\nimport { Ref } from 'react'\n\ninterface DividerProps {\n className?: string\n ref?: Ref<HTMLDivElement>\n}\n\nexport const Divider = ({ className, ref: forwardedRef }: DividerProps) => {\n return <div ref={forwardedRef} className={cx('my-md border-b-sm border-outline', className)} />\n}\n\nDivider.displayName = 'Dropdown.Divider'\n","import { createContext, type PropsWithChildren, useContext, useId } from 'react'\n\nimport { ID_PREFIX } from './DropdownContext'\n\nexport interface DropdownContextState {\n labelId: string\n}\n\ntype DropdownContextProps = PropsWithChildren\n\nconst DropdownGroupContext = createContext<DropdownContextState | null>(null)\n\nexport const DropdownGroupProvider = ({ children }: DropdownContextProps) => {\n const labelId = `${ID_PREFIX}-group-label-${useId()}`\n\n return (\n <DropdownGroupContext.Provider value={{ labelId }}>{children}</DropdownGroupContext.Provider>\n )\n}\n\nexport const useDropdownGroupContext = () => {\n const context = useContext(DropdownGroupContext)\n\n if (!context) {\n throw Error('useDropdownGroupContext must be used within a DropdownGroup provider')\n }\n\n return context\n}\n","import { cx } from 'class-variance-authority'\nimport { ReactNode, Ref } from 'react'\n\nimport { DropdownGroupProvider, useDropdownGroupContext } from './DropdownItemsGroupContext'\n\ninterface GroupProps {\n children: ReactNode\n className?: string\n ref?: Ref<HTMLDivElement>\n}\n\nexport const Group = ({ children, ref: forwardedRef, ...props }: GroupProps) => {\n return (\n <DropdownGroupProvider>\n <GroupContent ref={forwardedRef} {...props}>\n {children}\n </GroupContent>\n </DropdownGroupProvider>\n )\n}\n\nconst GroupContent = ({ children, className, ref: forwardedRef }: GroupProps) => {\n const { labelId } = useDropdownGroupContext()\n\n return (\n <div ref={forwardedRef} role=\"group\" aria-labelledby={labelId} className={cx(className)}>\n {children}\n </div>\n )\n}\n\nGroup.displayName = 'Dropdown.Group'\n","import {\n createContext,\n Dispatch,\n type PropsWithChildren,\n SetStateAction,\n useContext,\n useState,\n} from 'react'\n\nimport { useDropdownContext } from './DropdownContext'\nimport { DropdownItem } from './types'\nimport { getIndexByKey, getItemText } from './utils'\n\ntype ItemTextId = string | undefined\n\ninterface DropdownItemContextState {\n textId: ItemTextId\n setTextId: Dispatch<SetStateAction<ItemTextId>>\n isSelected: boolean\n itemData: DropdownItem\n index: number\n disabled: boolean\n}\n\nconst DropdownItemContext = createContext<DropdownItemContextState | null>(null)\n\nexport const DropdownItemProvider = ({\n value,\n disabled = false,\n children,\n}: PropsWithChildren<{ value: string; disabled?: boolean }>) => {\n const { multiple, itemsMap, selectedItem, selectedItems } = useDropdownContext()\n\n const [textId, setTextId] = useState<ItemTextId>(undefined)\n\n const index = getIndexByKey(itemsMap, value)\n const itemData: DropdownItem = { disabled, value, text: getItemText(children) }\n\n const isSelected = multiple\n ? selectedItems.some(selectedItem => selectedItem.value === value)\n : selectedItem?.value === value\n\n return (\n <DropdownItemContext.Provider\n value={{ textId, setTextId, isSelected, itemData, index, disabled }}\n >\n {children}\n </DropdownItemContext.Provider>\n )\n}\n\nexport const useDropdownItemContext = () => {\n const context = useContext(DropdownItemContext)\n\n if (!context) {\n throw Error('useDropdownItemContext must be used within a DropdownItem provider')\n }\n\n return context\n}\n","import { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport { cva, cx } from 'class-variance-authority'\nimport { type HTMLAttributes, type ReactNode, Ref } from 'react'\n\nimport { useDropdownContext } from './DropdownContext'\nimport { DropdownItemProvider, useDropdownItemContext } from './DropdownItemContext'\n\nexport interface ItemProps extends HTMLAttributes<HTMLLIElement> {\n disabled?: boolean\n value: string\n children: ReactNode\n className?: string\n ref?: Ref<HTMLLIElement>\n}\n\nexport const Item = ({ children, ref: forwardedRef, ...props }: ItemProps) => {\n const { value, disabled } = props\n\n return (\n <DropdownItemProvider value={value} disabled={disabled}>\n <ItemContent ref={forwardedRef} {...props}>\n {children}\n </ItemContent>\n </DropdownItemProvider>\n )\n}\n\nconst styles = cva('px-lg py-md text-body-1', {\n variants: {\n selected: {\n true: 'font-bold',\n },\n disabled: {\n true: 'opacity-dim-3 cursor-not-allowed',\n false: 'cursor-pointer',\n },\n highlighted: {\n true: '',\n },\n interactionType: {\n mouse: '',\n keyboard: '',\n },\n },\n compoundVariants: [\n {\n highlighted: true,\n interactionType: 'mouse',\n class: 'bg-surface-hovered',\n },\n {\n highlighted: true,\n interactionType: 'keyboard',\n class: 'u-outline',\n },\n ],\n})\n\nconst ItemContent = ({\n className,\n disabled = false,\n value,\n children,\n ref: forwardedRef,\n}: ItemProps) => {\n const { getItemProps, highlightedItem, lastInteractionType } = useDropdownContext()\n const { textId, index, itemData, isSelected } = useDropdownItemContext()\n\n const isHighlighted = highlightedItem?.value === value\n\n const { ref: downshiftRef, ...downshiftItemProps } = getItemProps({ item: itemData, index })\n const ref = useMergeRefs(forwardedRef, downshiftRef)\n\n return (\n <li\n ref={ref}\n className={cx(\n styles({\n selected: isSelected,\n disabled,\n highlighted: isHighlighted,\n interactionType: lastInteractionType,\n className,\n })\n )}\n key={value}\n {...downshiftItemProps}\n aria-selected={isSelected}\n aria-labelledby={textId}\n >\n {children}\n </li>\n )\n}\n\nItem.displayName = 'Dropdown.Item'\n","import { Check } from '@spark-ui/icons/Check'\nimport { cx } from 'class-variance-authority'\nimport { ReactNode, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport { useDropdownItemContext } from './DropdownItemContext'\n\nexport interface ItemIndicatorProps {\n children?: ReactNode\n className?: string\n label?: string\n ref?: Ref<HTMLSpanElement>\n}\n\nexport const ItemIndicator = ({\n className,\n children,\n label,\n ref: forwardedRef,\n}: ItemIndicatorProps) => {\n const { disabled, isSelected } = useDropdownItemContext()\n\n const childElement = children || (\n <Icon size=\"sm\">\n <Check aria-label={label} />\n </Icon>\n )\n\n return (\n <span\n ref={forwardedRef}\n className={cx('min-h-sz-16 min-w-sz-16 flex', disabled && 'opacity-dim-3', className)}\n >\n {isSelected && childElement}\n </span>\n )\n}\n\nItemIndicator.displayName = 'Dropdown.ItemIndicator'\n","import { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport { cx } from 'class-variance-authority'\nimport { ReactNode, Ref, useLayoutEffect, useRef } from 'react'\n\nimport { useDropdownContext } from './DropdownContext'\n\ninterface ItemsProps {\n children: ReactNode\n className?: string\n ref?: Ref<HTMLUListElement>\n}\n\n/**\n * BUGFIX\n *\n * 1. The !pointer-events-auto class is needed to prevent a bug\n * which cannot be reproduced when running Storybook locally,\n * in scenarios such as when a Dropdown is nested within a Dialog,\n * the \"props\" object, containing styles computed by Radix\n * may erroneously contain \"pointerEvents = 'none'\", while the Dropdown is open,\n * making it impossible to select a value using a pointer device\n *\n * 2. Closed + absolute still grows scrollable overflow of ancestors (e.g. Table.Grid).\n * max-h-0 overflow-hidden collapses that box; p-lg only applies when open.\n */\n\nexport const Items = ({ children, className, ref: forwardedRef, ...props }: ItemsProps) => {\n const { isOpen, getMenuProps, hasPopover, setLastInteractionType } = useDropdownContext()\n\n const { ref: downshiftRef, ...downshiftMenuProps } = getMenuProps({\n onMouseMove: () => {\n setLastInteractionType('mouse')\n },\n })\n\n const innerRef = useRef<HTMLElement>(null)\n\n const ref = useMergeRefs(forwardedRef, downshiftRef, innerRef)\n\n useLayoutEffect(() => {\n if (!hasPopover) return\n if (!innerRef.current) return\n\n if (innerRef.current.parentElement) {\n innerRef.current.parentElement.style.pointerEvents = isOpen ? '' : 'none'\n innerRef.current.style.pointerEvents = isOpen ? '' : 'none'\n }\n }, [isOpen, hasPopover])\n\n return (\n <ul\n ref={ref}\n className={cx(\n className,\n 'flex flex-col',\n isOpen\n ? 'pointer-events-auto! block' /* 1 */\n : 'pointer-events-none invisible absolute max-h-0 min-h-0 overflow-hidden opacity-0',\n hasPopover && isOpen && 'p-lg'\n )}\n {...props}\n {...downshiftMenuProps}\n /**\n * When used inside a Radix dialog/drawer, the focus trap behaviour of Radix prevent scrolling and hovering inside absolutely positioned elements in the dialog.\n * It does programatically in JS using the `style` attribute.\n *\n * Issue is known but there is no clear fix in sight: https://github.com/radix-ui/primitives/issues/1159\n *\n * A solution would be to make an abstraction of `Dialog.Overlay` instead of using the radix one, but that would mean managing body scroll freeze and scrollbar shifting ourselves.\n *\n */\n data-spark-component=\"dropdown-items\"\n >\n {children}\n </ul>\n )\n}\n\nItems.displayName = 'Dropdown.Items'\n","import { cx } from 'class-variance-authority'\nimport { Ref, useEffect, useId } from 'react'\n\nimport { ID_PREFIX } from './DropdownContext'\nimport { useDropdownItemContext } from './DropdownItemContext'\n\nexport interface ItemTextProps {\n children: string\n ref?: Ref<HTMLSpanElement>\n}\n\nexport const ItemText = ({ children, ref: forwardedRef }: ItemTextProps) => {\n const id = `${ID_PREFIX}-item-text-${useId()}`\n\n const { setTextId } = useDropdownItemContext()\n\n useEffect(() => {\n setTextId(id)\n\n return () => setTextId(undefined)\n })\n\n return (\n <span id={id} className={cx('inline')} ref={forwardedRef}>\n {children}\n </span>\n )\n}\n\nItemText.displayName = 'Dropdown.ItemText'\n","import { cx } from 'class-variance-authority'\nimport { Ref } from 'react'\n\nimport { useDropdownGroupContext } from './DropdownItemsGroupContext'\n\ninterface LabelProps {\n children: string\n className?: string\n ref?: Ref<HTMLDivElement>\n}\n\nexport const Label = ({ children, className, ref: forwardedRef }: LabelProps) => {\n const { labelId } = useDropdownGroupContext()\n\n return (\n <div\n ref={forwardedRef}\n id={labelId}\n className={cx('px-md py-sm text-body-2 text-neutral italic', className)}\n >\n {children}\n </div>\n )\n}\n\nLabel.displayName = 'Dropdown.Label'\n","import { ReactElement } from 'react'\n\nimport { Icon } from '../icon'\n\nexport const LeadingIcon = ({ children }: { children: ReactElement }) => {\n return (\n <Icon size={'sm'} className=\"shrink-0\">\n {children}\n </Icon>\n )\n}\n\nLeadingIcon.displayName = 'Dropdown.LeadingIcon'\n","import { cx } from 'class-variance-authority'\nimport { ComponentProps, useEffect } from 'react'\n\nimport { Popover as SparkPopover } from '../popover'\nimport { useDropdownContext } from './DropdownContext'\n\nexport const Popover = ({\n children,\n matchTriggerWidth = true,\n sideOffset = 4,\n className,\n elevation = 'dropdown',\n ref: forwardedRef,\n ...props\n}: ComponentProps<typeof SparkPopover.Content>) => {\n const ctx = useDropdownContext()\n\n useEffect(() => {\n ctx.setHasPopover(true)\n\n return () => ctx.setHasPopover(false)\n }, [])\n\n return (\n <SparkPopover.Content\n ref={forwardedRef}\n inset\n asChild\n matchTriggerWidth={matchTriggerWidth}\n elevation={elevation}\n className={cx('relative', className)}\n sideOffset={sideOffset}\n onOpenAutoFocus={e => {\n /**\n * With a combobox pattern, the focus should remain on the trigger at all times.\n * Passing the focus to the dropdown popover would break keyboard navigation.\n */\n e.preventDefault()\n }}\n {...props}\n data-spark-component=\"dropdown-popover\"\n >\n {children}\n </SparkPopover.Content>\n )\n}\n\nPopover.displayName = 'Dropdown.Popover'\n","import { ReactElement } from 'react'\n\nimport { Popover as SparkPopover } from '../popover'\n\nexport const Portal: typeof SparkPopover.Portal = ({ children, ...rest }): ReactElement => (\n <SparkPopover.Portal {...rest}>{children}</SparkPopover.Portal>\n)\n\nPortal.displayName = 'Dropdown.Portal'\n","import { cva } from 'class-variance-authority'\n\nexport const styles = cva(\n [\n 'flex w-full items-center justify-between',\n 'min-h-sz-44 rounded-lg bg-surface text-on-surface px-lg',\n 'text-body-1',\n // outline styles\n 'ring-1 outline-hidden ring-inset focus:ring-2 focus:ring-focus',\n ],\n {\n variants: {\n state: {\n undefined: 'ring-outline',\n error: 'ring-error',\n alert: 'ring-alert',\n success: 'ring-success',\n },\n disabled: {\n true: 'disabled:bg-on-surface/dim-5 cursor-not-allowed text-on-surface/dim-3',\n },\n readOnly: {\n true: 'disabled:bg-on-surface/dim-5 cursor-not-allowed text-on-surface/dim-3',\n },\n },\n compoundVariants: [\n {\n disabled: false,\n state: undefined,\n class: 'default:hover:ring-outline-high',\n },\n ],\n }\n)\n","import { useMergeRefs } from '@spark-ui/hooks/use-merge-refs'\nimport { ArrowHorizontalDown } from '@spark-ui/icons/ArrowHorizontalDown'\nimport { cx } from 'class-variance-authority'\nimport { Fragment, ReactNode, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport { Popover } from '../popover'\nimport { VisuallyHidden } from '../visually-hidden'\nimport { useDropdownContext } from './DropdownContext'\nimport { styles } from './DropdownTrigger.styles'\n\ninterface TriggerProps {\n 'aria-label'?: string\n children: ReactNode\n className?: string\n ref?: Ref<HTMLButtonElement>\n}\n\nexport const Trigger = ({\n 'aria-label': ariaLabel,\n children,\n className,\n ref: forwardedRef,\n}: TriggerProps) => {\n const {\n getToggleButtonProps,\n getDropdownProps,\n getLabelProps,\n hasPopover,\n disabled,\n readOnly,\n state,\n setLastInteractionType,\n isOpen,\n } = useDropdownContext()\n\n const [WrapperComponent, wrapperProps] = hasPopover\n ? [Popover.Trigger, { asChild: true }]\n : [Fragment, {}]\n\n const { ref: downshiftRef, ...downshiftTriggerProps } = getToggleButtonProps({\n ...getDropdownProps(),\n onKeyDown: e => {\n setLastInteractionType('keyboard')\n // Escape: bubble-phase parents; table grid defers in capture via table-keyboard.\n if (e.key === 'Escape' && isOpen) {\n e.stopPropagation()\n }\n },\n })\n\n const isExpanded = downshiftTriggerProps['aria-expanded']\n\n const ref = useMergeRefs(forwardedRef, downshiftRef)\n\n return (\n <>\n {ariaLabel && (\n <VisuallyHidden>\n <label {...getLabelProps()}>{ariaLabel}</label>\n </VisuallyHidden>\n )}\n <WrapperComponent {...wrapperProps}>\n <button\n type=\"button\"\n ref={ref}\n disabled={disabled || readOnly}\n className={styles({ className, state, disabled, readOnly })}\n {...downshiftTriggerProps}\n data-spark-component=\"dropdown-trigger\"\n >\n <span className=\"gap-md flex items-center justify-start\">{children}</span>\n\n <Icon\n className={cx('ml-md shrink-0 rotate-0 transition duration-100 ease-in', {\n 'rotate-180': isExpanded,\n })}\n size=\"sm\"\n >\n <ArrowHorizontalDown />\n </Icon>\n </button>\n </WrapperComponent>\n </>\n )\n}\n\nTrigger.displayName = 'Dropdown.Trigger'\n","import { cx } from 'class-variance-authority'\nimport { ReactNode, Ref } from 'react'\n\nimport { useDropdownContext } from './DropdownContext'\n\nexport interface ValueProps {\n children?: ReactNode\n className?: string\n placeholder: string\n ref?: Ref<HTMLSpanElement>\n}\n\nexport const Value = ({ children, className, placeholder, ref: forwardedRef }: ValueProps) => {\n const { selectedItem, multiple, selectedItems } = useDropdownContext()\n\n const hasSelectedItems = !!(multiple ? selectedItems.length : selectedItem)\n const text = multiple ? selectedItems[0]?.text : selectedItem?.text\n const suffix = selectedItems.length > 1 ? `, +${selectedItems.length - 1}` : ''\n\n return (\n <span ref={forwardedRef} className={cx('flex shrink items-center text-left', className)}>\n <span\n className={cx(\n 'line-clamp-1 flex-1 overflow-hidden break-all text-ellipsis',\n !hasSelectedItems && 'text-on-surface/dim-1'\n )}\n >\n {!hasSelectedItems ? placeholder : children || text}\n </span>\n {suffix && <span>{suffix}</span>}\n </span>\n )\n}\n\nValue.displayName = 'Dropdown.Value'\n","import { Dropdown as Root } from './Dropdown'\nimport { DropdownProvider, useDropdownContext } from './DropdownContext'\nimport { Divider } from './DropdownDivider'\nimport { Group } from './DropdownGroup'\nimport { Item } from './DropdownItem'\nimport { ItemIndicator } from './DropdownItemIndicator'\nimport { Items } from './DropdownItems'\nimport { ItemText } from './DropdownItemText'\nimport { Label } from './DropdownLabel'\nimport { LeadingIcon } from './DropdownLeadingIcon'\nimport { Popover } from './DropdownPopover'\nimport { Portal } from './DropdownPortal'\nimport { Trigger } from './DropdownTrigger'\nimport { Value } from './DropdownValue'\n\nexport { useDropdownContext, DropdownProvider }\n\n/**\n * A list of options that appears when users interact with a trigger element.\n */\nexport const Dropdown: typeof Root & {\n Group: typeof Group\n Item: typeof Item\n Items: typeof Items\n ItemText: typeof ItemText\n ItemIndicator: typeof ItemIndicator\n Label: typeof Label\n Popover: typeof Popover\n Divider: typeof Divider\n Trigger: typeof Trigger\n Value: typeof Value\n LeadingIcon: typeof LeadingIcon\n Portal: typeof Portal\n} = Object.assign(Root, {\n Group,\n Item,\n Items,\n ItemText,\n ItemIndicator,\n Label,\n Popover,\n Divider,\n Trigger,\n Value,\n LeadingIcon,\n Portal,\n})\n\nDropdown.displayName = 'Dropdown'\nGroup.displayName = 'Dropdown.Group'\nItems.displayName = 'Dropdown.Items'\nItem.displayName = 'Dropdown.Item'\nItemText.displayName = 'Dropdown.ItemText'\nItemIndicator.displayName = 'Dropdown.ItemIndicator'\nLabel.displayName = 'Dropdown.Label'\nPopover.displayName = 'Dropdown.Popover'\nDivider.displayName = 'Dropdown.Divider'\nTrigger.displayName = 'Dropdown.Trigger'\nValue.displayName = 'Dropdown.Value'\nLeadingIcon.displayName = 'Dropdown.LeadingIcon'\nPortal.displayName = 'Dropdown.Portal'\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/file-upload/constants.ts","../../src/file-upload/utils.ts","../../src/file-upload/useFileUploadState.tsx","../../src/file-upload/FileUpload.tsx","../../src/file-upload/FileUploadItemDeleteTrigger.tsx","../../src/file-upload/FileUploadAcceptedFile.tsx","../../src/file-upload/FileUploadContext.tsx","../../src/file-upload/FileUploadDropzone.tsx","../../src/file-upload/FileUploadPreviewImage.tsx","../../src/file-upload/FileUploadRejectedFileDeleteTrigger.tsx","../../src/file-upload/FileUploadRejectedFile.tsx","../../src/file-upload/FileUploadTrigger.tsx","../../src/file-upload/index.ts"],"sourcesContent":["/**\n * File upload error codes\n */\nexport const FILE_UPLOAD_ERRORS = {\n /**\n * Exceeds the maxFiles limit\n */\n TOO_MANY_FILES: 'TOO_MANY_FILES',\n /**\n * File type not in the accept list\n */\n FILE_INVALID_TYPE: 'FILE_INVALID_TYPE',\n /**\n * File size exceeds maxFileSize\n */\n FILE_TOO_LARGE: 'FILE_TOO_LARGE',\n /**\n * File size below minFileSize\n */\n FILE_TOO_SMALL: 'FILE_TOO_SMALL',\n /**\n * Generic validation failure\n */\n FILE_INVALID: 'FILE_INVALID',\n /**\n * Duplicate file detected\n */\n FILE_EXISTS: 'FILE_EXISTS',\n} as const\n","import { CvOutline } from '@spark-ui/icons/CvOutline'\nimport { FilePdfOutline } from '@spark-ui/icons/FilePdfOutline'\nimport { ImageOutline } from '@spark-ui/icons/ImageOutline'\nimport { PlayOutline } from '@spark-ui/icons/PlayOutline'\nimport { createElement, ReactElement, type RefObject } from 'react'\n\n/**\n * Validates if a file matches the accept patterns\n * Supports MIME types (e.g., \"image/*\", \"image/png\", \"application/pdf\")\n * and file extensions (e.g., \".pdf\", \".doc\", \".jpg\")\n */\nexport function validateFileAccept(file: File, accept: string): boolean {\n if (!accept) {\n return true\n }\n\n const patterns = accept.split(',').map(pattern => pattern.trim())\n\n return patterns.some(pattern => {\n // Handle MIME type patterns (e.g., \"image/*\", \"image/png\")\n if (pattern.includes('/')) {\n if (pattern.endsWith('/*')) {\n // Wildcard MIME type (e.g., \"image/*\")\n const baseType = pattern.slice(0, -2)\n\n return file.type.startsWith(baseType + '/')\n }\n // Exact MIME type (e.g., \"image/png\")\n\n return file.type === pattern\n }\n\n // Handle file extension patterns (e.g., \".pdf\", \".doc\")\n if (pattern.startsWith('.')) {\n const extension = pattern.toLowerCase()\n const fileName = file.name.toLowerCase()\n\n return fileName.endsWith(extension)\n }\n\n // Handle extension without dot (e.g., \"pdf\", \"doc\")\n const extension = '.' + pattern.toLowerCase()\n const fileName = file.name.toLowerCase()\n\n return fileName.endsWith(extension)\n })\n}\n\n/**\n * Validates if a file size is within the allowed range\n * @param file - The file to validate\n * @param minFileSize - Minimum file size in bytes\n * @param maxFileSize - Maximum file size in bytes\n * @param locale - Locale code for error messages. Defaults to browser locale or 'en'\n * @returns Object with validation result and error message if invalid\n */\nexport function validateFileSize(\n file: File,\n minFileSize?: number,\n maxFileSize?: number,\n locale?: string\n): { valid: boolean; error?: string } {\n const defaultLocale = locale || getDefaultLocale()\n if (minFileSize !== undefined && file.size < minFileSize) {\n const errorMessage = `File \"${file.name}\" is too small. Minimum size is ${formatFileSize(minFileSize, defaultLocale)}.`\n\n return {\n valid: false,\n error: errorMessage,\n }\n }\n\n if (maxFileSize !== undefined && file.size > maxFileSize) {\n const errorMessage = `File \"${file.name}\" is too large. Maximum size is ${formatFileSize(maxFileSize, defaultLocale)}.`\n\n return {\n valid: false,\n error: errorMessage,\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Gets the default locale from the browser or falls back to 'en'\n * @returns The browser's locale or 'en' as fallback\n */\nfunction getDefaultLocale(): string {\n if (typeof navigator !== 'undefined' && navigator.language) {\n return navigator.language\n }\n\n return 'en'\n}\n\n/**\n * Formats file size in bytes to human-readable format\n * @param bytes - File size in bytes\n * @param locale - Locale code (e.g., 'en', 'fr'). Defaults to browser locale or 'en'\n * @returns Formatted file size string with appropriate unit\n */\nexport function formatFileSize(bytes: number, locale?: string): string {\n const defaultLocale = locale || getDefaultLocale()\n // Normalize locale (e.g., 'fr' -> 'fr-FR', 'en' -> 'en-US')\n let normalizedLocale = defaultLocale\n if (defaultLocale.length === 2) {\n normalizedLocale = defaultLocale === 'fr' ? 'fr-FR' : 'en-US'\n }\n\n if (bytes === 0) {\n const formatter = new Intl.NumberFormat(normalizedLocale, {\n style: 'unit',\n unit: 'byte',\n unitDisplay: 'long',\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n })\n\n return formatter.format(0)\n }\n\n const k = 1024\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n\n // Map to Intl.NumberFormat supported units\n const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte'] as const\n const unit = units[i] || 'byte'\n\n const size = bytes / Math.pow(k, i)\n\n // Use 'long' display for bytes to get proper pluralization (bytes/octets)\n // Use 'short' display for other units (KB/MB/GB, Ko/Mo/Go)\n const unitDisplay = i === 0 ? 'long' : 'short'\n\n // Use Intl.NumberFormat with unit style to format number and unit according to locale\n const formatter = new Intl.NumberFormat(normalizedLocale, {\n style: 'unit',\n unit,\n unitDisplay,\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n })\n\n return formatter.format(size)\n}\n\n/**\n * Returns the appropriate icon component based on the file type\n * @param file - The file to get the icon for\n * @returns React element representing the icon component\n */\nexport function getFileIcon(file: File): ReactElement {\n const fileType = file.type.toLowerCase()\n const fileName = file.name.toLowerCase()\n\n // Check for images\n if (fileType.startsWith('image/') || /\\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(fileName)) {\n return createElement(ImageOutline)\n }\n\n // Check for PDFs\n if (fileType === 'application/pdf' || fileName.endsWith('.pdf')) {\n return createElement(FilePdfOutline)\n }\n\n // Check for videos\n if (fileType.startsWith('video/') || /\\.(mp4|avi|mov|wmv|flv|webm|mkv)$/i.test(fileName)) {\n return createElement(PlayOutline)\n }\n\n // Default icon for other file types\n return createElement(CvOutline)\n}\n\n/**\n * Checks if an element is focusable\n * @param element - The element to check\n * @returns True if the element is focusable\n */\nfunction isFocusable(element: HTMLElement | null): boolean {\n if (!element) {\n return false\n }\n\n // Check if element has tabIndex >= 0 (not -1)\n const tabIndex = element.tabIndex\n if (tabIndex >= 0) {\n return true\n }\n\n // Check if element is naturally focusable (input, button, select, textarea, a with href, etc.)\n const isContentEditable = String(element.contentEditable) === 'true'\n const naturallyFocusable: boolean =\n element instanceof HTMLInputElement ||\n element instanceof HTMLButtonElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement ||\n (element instanceof HTMLAnchorElement && Boolean(element.href)) ||\n isContentEditable\n\n return naturallyFocusable\n}\n\n/**\n * Finds the first focusable element from a list of candidates\n * Falls back to inputRef if no other element is focusable\n * @param candidates - Array of candidate elements to check\n * @param inputRef - Input element to use as last resort\n * @returns The first focusable element, or null if none found\n */\nexport function findFocusableElement(\n candidates: (HTMLElement | null)[],\n inputRef: RefObject<HTMLInputElement | null>\n): HTMLElement | null {\n // Try each candidate in order\n for (const candidate of candidates) {\n if (isFocusable(candidate)) {\n return candidate\n }\n }\n\n // Last resort: use inputRef (even though it has tabIndex={-1}, we can still focus it programmatically)\n if (inputRef.current) {\n return inputRef.current\n }\n\n return null\n}\n","/* eslint-disable max-lines-per-function */\nimport { useCombinedState } from '@spark-ui/hooks/use-combined-state'\nimport { useState } from 'react'\n\nimport { FILE_UPLOAD_ERRORS } from './constants'\nimport { validateFileAccept, validateFileSize } from './utils'\n\nexport type FileUploadFileError =\n | typeof FILE_UPLOAD_ERRORS.TOO_MANY_FILES\n | typeof FILE_UPLOAD_ERRORS.FILE_INVALID_TYPE\n | typeof FILE_UPLOAD_ERRORS.FILE_TOO_LARGE\n | typeof FILE_UPLOAD_ERRORS.FILE_TOO_SMALL\n | typeof FILE_UPLOAD_ERRORS.FILE_INVALID\n | typeof FILE_UPLOAD_ERRORS.FILE_EXISTS\n\nexport interface RejectedFile {\n file: File\n errors: FileUploadFileError[]\n}\n\nexport interface FileAcceptDetails {\n files: File[]\n}\n\nexport interface FileRejectDetails {\n files: RejectedFile[]\n}\n\nexport interface FileChangeDetails {\n acceptedFiles: File[]\n rejectedFiles: RejectedFile[]\n}\n\nexport interface UseFileUploadStateProps {\n /**\n * Initial files to display when the component mounts (uncontrolled mode)\n */\n defaultValue?: File[]\n /**\n * Controlled files value (controlled mode)\n * When provided, the component becomes controlled\n */\n value?: File[]\n /**\n * Callback when files are accepted\n */\n onFileAccept?: (details: FileAcceptDetails) => void\n /**\n * Callback when files are rejected\n */\n onFileReject?: (details: FileRejectDetails) => void\n /**\n * Callback when files change (both accepted and rejected)\n * For controlled mode, use this to update the value prop by extracting details.acceptedFiles\n */\n onFileChange?: (details: FileChangeDetails) => void\n /**\n * Whether multiple files can be selected\n * @default true\n */\n multiple?: boolean\n /**\n * Comma-separated list of accepted file types\n */\n accept?: string\n /**\n * Maximum number of files that can be uploaded\n */\n maxFiles?: number\n /**\n * Maximum file size in bytes\n */\n maxFileSize?: number\n /**\n * Minimum file size in bytes\n */\n minFileSize?: number\n /**\n * When `true`, prevents the user from interacting with the file upload\n */\n disabled?: boolean\n /**\n * When `true`, sets the file upload to read-only mode\n */\n readOnly?: boolean\n /**\n * The [BCP47](https://www.ietf.org/rfc/bcp/bcp47.txt) language code for the locale.\n * Used for formatting file sizes and error messages.\n */\n locale?: string\n}\n\nexport interface UseFileUploadStateReturn {\n files: File[]\n rejectedFiles: RejectedFile[]\n addFiles: (files: File[]) => void\n removeFile: (index: number) => void\n removeRejectedFile: (index: number) => void\n clearFiles: () => void\n clearRejectedFiles: () => void\n maxFilesReached: boolean\n}\n\n/**\n * Hook that manages file upload state, validation, and file operations\n */\nexport function useFileUploadState({\n defaultValue = [],\n value: controlledValue,\n onFileAccept,\n onFileReject,\n onFileChange,\n multiple = true,\n accept,\n maxFiles,\n maxFileSize,\n minFileSize,\n disabled = false,\n readOnly = false,\n locale,\n}: UseFileUploadStateProps): UseFileUploadStateReturn {\n // Get default locale from browser or fallback to 'en'\n const defaultLocale =\n locale || (typeof navigator !== 'undefined' && navigator.language ? navigator.language : 'en')\n\n // For controlled mode, use onFileChange to update value prop\n // useCombinedState doesn't need a callback - we'll call onFileChange manually in addFiles/removeFile\n const [filesState, setFilesState] = useCombinedState<File[]>(controlledValue, defaultValue)\n const files = filesState ?? []\n const setFiles = setFilesState as (value: File[] | ((prev: File[]) => File[])) => void\n const [rejectedFiles, setRejectedFiles] = useState<RejectedFile[]>([])\n\n const addFiles = (newFiles: File[]) => {\n // Don't allow adding files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n // Reset rejectedFiles at the start of each new file addition attempt\n setRejectedFiles([])\n\n const newRejectedFiles: RejectedFile[] = []\n\n // Helper function to check if a file already exists\n // Compares by name and size to detect duplicates (lastModified can differ when re-selecting the same file)\n const fileExists = (file: File, existingFiles: File[]): boolean => {\n return existingFiles.some(\n existingFile => existingFile.name === file.name && existingFile.size === file.size\n )\n }\n\n // Helper function to add or update rejected file\n const addRejectedFile = (file: File, error: FileUploadFileError) => {\n const existingRejection = newRejectedFiles.find(\n rejected => rejected.file.name === file.name && rejected.file.size === file.size\n )\n\n if (existingRejection) {\n // Add error to existing rejection if not already present\n if (!existingRejection.errors.includes(error)) {\n existingRejection.errors.push(error)\n }\n } else {\n // Create new rejection\n newRejectedFiles.push({\n file,\n errors: [error],\n })\n }\n }\n\n setFiles((prev: File[]) => {\n const currentFiles = prev ?? []\n\n // Calculate remaining slots once at the beginning\n const remainingSlots = maxFiles !== undefined ? maxFiles - currentFiles.length : undefined\n\n // Mark all files with TOO_MANY_FILES if already at max (before other validations)\n // This allows a file to have multiple error codes (e.g., FILE_INVALID_TYPE + TOO_MANY_FILES)\n if (remainingSlots !== undefined && remainingSlots <= 0) {\n newFiles.forEach(file => {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.TOO_MANY_FILES)\n })\n }\n\n // Track files rejected by accept pattern\n let filteredFiles = newFiles\n if (accept) {\n const rejectedByAccept = newFiles.filter(file => !validateFileAccept(file, accept))\n rejectedByAccept.forEach(file => {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_INVALID_TYPE)\n })\n filteredFiles = newFiles.filter(file => validateFileAccept(file, accept))\n }\n\n // Track files rejected by size\n let validSizeFiles = filteredFiles\n if (minFileSize !== undefined || maxFileSize !== undefined) {\n validSizeFiles = filteredFiles.filter(file => {\n const validation = validateFileSize(file, minFileSize, maxFileSize, defaultLocale)\n if (!validation.valid) {\n if (maxFileSize !== undefined && file.size > maxFileSize) {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_TOO_LARGE)\n } else if (minFileSize !== undefined && file.size < minFileSize) {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_TOO_SMALL)\n } else {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_INVALID)\n }\n\n return false\n }\n\n return true\n })\n }\n\n // Check for duplicate files (both against existing files and within the current batch)\n const seenFiles = new Map<string, File>()\n const uniqueFiles = validSizeFiles.filter(file => {\n // Create a unique key for the file (name + size)\n // Using name and size only, as lastModified can differ when re-selecting the same file\n const fileKey = `${file.name}-${file.size}`\n\n // Check if file already exists in previously accepted files\n const existsInPrev = fileExists(file, currentFiles)\n if (existsInPrev) {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_EXISTS)\n\n return false\n }\n\n // Check if file already exists in the current batch\n if (seenFiles.has(fileKey)) {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_EXISTS)\n\n return false\n }\n\n // Mark this file as seen\n seenFiles.set(fileKey, file)\n\n return true\n })\n\n // If multiple is false, replace existing files with only the first new file\n let filesToAdd = multiple ? uniqueFiles : uniqueFiles.slice(0, 1)\n\n // Apply maxFiles limit to files that passed all other validations\n if (remainingSlots !== undefined) {\n if (remainingSlots <= 0) {\n // Already at max, reject all valid files (they should already have TOO_MANY_FILES error from the first check)\n filesToAdd = []\n } else if (filesToAdd.length > remainingSlots) {\n // Reject all files if batch exceeds limit (\"all or nothing\" approach)\n filesToAdd.forEach(file => {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.TOO_MANY_FILES)\n })\n filesToAdd = []\n }\n }\n\n const updated = multiple ? [...currentFiles, ...filesToAdd] : filesToAdd\n\n // Add rejected files to state synchronously\n // Note: newRejectedFiles is mutated inside this setFiles callback, so it should be populated by now\n // Copy the array to avoid closure issues\n const rejectedFilesToAdd = [...newRejectedFiles]\n // Replace rejectedFiles completely (not accumulate)\n setRejectedFiles(rejectedFilesToAdd)\n\n // Call callbacks with the calculated values\n // Note: These callbacks are called synchronously with the new values\n // React will update the state asynchronously, but the callbacks receive the correct new values\n if (filesToAdd.length > 0 && onFileAccept) {\n onFileAccept({ files: filesToAdd })\n }\n\n if (rejectedFilesToAdd.length > 0 && onFileReject) {\n onFileReject({ files: rejectedFilesToAdd })\n }\n\n if (onFileChange) {\n onFileChange({\n acceptedFiles: updated,\n rejectedFiles: rejectedFilesToAdd,\n })\n }\n\n return updated\n })\n }\n\n const removeFile = (index: number) => {\n // Don't allow removing files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n setFiles((prev: File[]) => {\n const currentFiles = prev ?? []\n const updated = currentFiles.filter((_: File, i: number) => i !== index)\n\n // Clean up TOO_MANY_FILES errors if we're now below the maxFiles limit\n let updatedRejectedFiles = rejectedFiles\n if (maxFiles !== undefined && updated.length < maxFiles) {\n updatedRejectedFiles = rejectedFiles.filter(\n rejected => !rejected.errors.includes(FILE_UPLOAD_ERRORS.TOO_MANY_FILES)\n )\n setRejectedFiles(updatedRejectedFiles)\n }\n\n // Call onFileChange for controlled mode\n if (onFileChange) {\n onFileChange({\n acceptedFiles: updated,\n rejectedFiles: updatedRejectedFiles,\n })\n }\n\n return updated\n })\n }\n\n const clearFiles = () => {\n // Don't allow clearing files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n setFiles([])\n setRejectedFiles([])\n\n // Call onFileChange for controlled mode\n if (onFileChange) {\n onFileChange({\n acceptedFiles: [],\n rejectedFiles: [],\n })\n }\n }\n\n const removeRejectedFile = (index: number) => {\n // Don't allow removing rejected files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n setRejectedFiles(prev => prev.filter((_, i) => i !== index))\n }\n\n const clearRejectedFiles = () => {\n setRejectedFiles([])\n }\n\n const maxFilesReached = maxFiles !== undefined && files.length >= maxFiles\n\n return {\n files,\n rejectedFiles,\n addFiles,\n removeFile,\n removeRejectedFile,\n clearFiles,\n clearRejectedFiles,\n maxFilesReached,\n }\n}\n","/* eslint-disable max-lines-per-function */\nimport { useFormFieldControl } from '@spark-ui/components/form-field'\nimport { createContext, ReactNode, Ref, useContext, useId, useRef } from 'react'\n\nimport {\n type FileAcceptDetails,\n type FileChangeDetails,\n type FileRejectDetails,\n type FileUploadFileError,\n type RejectedFile,\n useFileUploadState,\n} from './useFileUploadState'\n\n// Re-export types for backward compatibility\nexport type {\n FileAcceptDetails,\n FileChangeDetails,\n FileRejectDetails,\n FileUploadFileError,\n RejectedFile,\n}\n\nexport interface FileUploadProps {\n /**\n * Change the default rendered element for the one passed as a child, merging their props and behavior.\n */\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n children: ReactNode\n className?: string\n /**\n * Initial files to display when the component mounts (uncontrolled mode)\n */\n defaultValue?: File[]\n /**\n * Controlled files value (controlled mode)\n * When provided, the component becomes controlled\n */\n value?: File[]\n /**\n * Callback when files are accepted\n * @param details - Details about the accepted files\n */\n onFileAccept?: (details: FileAcceptDetails) => void\n /**\n * Callback when files are rejected\n * @param details - Details about the rejected files and their errors\n */\n onFileReject?: (details: FileRejectDetails) => void\n /**\n * Callback when files change (both accepted and rejected)\n * For controlled mode, use this to update the value prop by extracting details.acceptedFiles\n * @param details - Details about both accepted and rejected files\n */\n onFileChange?: (details: FileChangeDetails) => void\n /**\n * Whether multiple files can be selected\n * @default true\n */\n multiple?: boolean\n /**\n * Comma-separated list of accepted file types\n * Supports MIME types (e.g., \"image/*\", \"image/png\", \"application/pdf\")\n * and file extensions (e.g., \".pdf\", \".doc\", \".jpg\")\n * @example \"image/*\"\n * @example \".pdf,.doc\"\n * @example \"image/png,image/jpeg,.pdf\"\n */\n accept?: string\n /**\n * Maximum number of files that can be uploaded\n * Files beyond this limit will be rejected\n */\n maxFiles?: number\n /**\n * Maximum file size in bytes\n * Files larger than this will be rejected\n */\n maxFileSize?: number\n /**\n * Minimum file size in bytes\n * Files smaller than this will be rejected\n */\n minFileSize?: number\n /**\n * When `true`, prevents the user from interacting with the file upload\n */\n disabled?: boolean\n /**\n * When `true`, sets the file upload to read-only mode\n */\n readOnly?: boolean\n /**\n * The [BCP47](https://www.ietf.org/rfc/bcp/bcp47.txt) language code for the locale.\n * Used for formatting file sizes and error messages.\n * @default Browser locale or 'en' if not available\n */\n locale?: string\n}\n\nexport const FileUploadContext = createContext<{\n inputRef: React.RefObject<HTMLInputElement | null>\n files: File[]\n rejectedFiles: RejectedFile[]\n addFiles: (files: File[]) => void\n removeFile: (index: number) => void\n removeRejectedFile: (index: number) => void\n clearFiles: () => void\n clearRejectedFiles: () => void\n triggerRef: React.RefObject<HTMLElement | null>\n dropzoneRef: React.RefObject<HTMLElement | null>\n deleteButtonRefs: React.MutableRefObject<HTMLButtonElement[]>\n rejectedFileDeleteButtonRefs: React.MutableRefObject<HTMLButtonElement[]>\n multiple: boolean\n maxFiles?: number\n maxFilesReached: boolean\n disabled: boolean\n readOnly: boolean\n locale: string\n description?: string\n isInvalid?: boolean\n isRequired?: boolean\n} | null>(null)\n\nconst ID_PREFIX = ':file-upload'\n\nexport const FileUpload = ({\n asChild: _asChild = false,\n children,\n defaultValue = [],\n value: controlledValue,\n onFileAccept,\n onFileReject,\n onFileChange,\n multiple = true,\n accept,\n maxFiles,\n maxFileSize,\n minFileSize,\n disabled: disabledProp = false,\n readOnly: readOnlyProp = false,\n locale,\n}: FileUploadProps) => {\n const field = useFormFieldControl()\n\n // Generate unique ID if none provided by FormField\n const internalId = useId()\n const inputId = field.id || `${ID_PREFIX}-${internalId}`\n\n // Use FormField name or undefined (no hardcoded fallback)\n const inputName = field.name\n\n const inputRef = useRef<HTMLInputElement>(null)\n const triggerRef = useRef<HTMLElement>(null)\n const dropzoneRef = useRef<HTMLElement>(null)\n const deleteButtonRefs = useRef<HTMLButtonElement[]>([])\n const rejectedFileDeleteButtonRefs = useRef<HTMLButtonElement[]>([])\n\n // Merge FormField props with component props (FormField takes precedence)\n const disabled = field.disabled ?? disabledProp\n const readOnly = field.readOnly ?? readOnlyProp\n\n // Use the file upload state hook to manage all file operations\n const {\n files,\n rejectedFiles,\n addFiles,\n removeFile,\n removeRejectedFile,\n clearFiles: clearFilesFromHook,\n clearRejectedFiles,\n maxFilesReached,\n } = useFileUploadState({\n defaultValue,\n value: controlledValue,\n onFileAccept,\n onFileReject,\n onFileChange,\n multiple,\n accept,\n maxFiles,\n maxFileSize,\n minFileSize,\n disabled,\n readOnly,\n locale,\n })\n\n // Override clearFiles to also clear deleteButtonRefs\n const clearFiles = () => {\n clearFilesFromHook()\n deleteButtonRefs.current = []\n }\n\n // Override clearRejectedFiles to also clear rejectedFileDeleteButtonRefs\n const clearRejectedFilesWithRefs = () => {\n clearRejectedFiles()\n rejectedFileDeleteButtonRefs.current = []\n }\n\n return (\n <FileUploadContext.Provider\n value={{\n inputRef,\n files,\n rejectedFiles,\n addFiles,\n removeFile,\n removeRejectedFile,\n clearFiles,\n clearRejectedFiles: clearRejectedFilesWithRefs,\n triggerRef,\n dropzoneRef,\n deleteButtonRefs,\n rejectedFileDeleteButtonRefs,\n multiple,\n maxFiles,\n maxFilesReached,\n disabled,\n readOnly,\n locale:\n locale ||\n (typeof navigator !== 'undefined' && navigator.language ? navigator.language : 'en'),\n description: field.description,\n isInvalid: field.isInvalid,\n isRequired: field.isRequired,\n }}\n >\n {/* <Comp data-spark-component=\"file-upload\" className={cx('relative', className)} {...props}> */}\n <div className=\"relative\">\n {children}\n <input\n ref={inputRef}\n type=\"file\"\n tabIndex={-1}\n id={inputId}\n multiple={multiple}\n name={inputName}\n accept={accept}\n disabled={disabled}\n readOnly={readOnly && !disabled}\n required={field.isRequired}\n aria-invalid={field.isInvalid}\n aria-describedby={field.description}\n // Hardcoded aria-label is acceptable here because:\n // 1. The input is visually hidden (sr-only) and not keyboard accessible (tabIndex={-1})\n // 2. Users never interact directly with this input - they interact via Trigger/Dropzone\n // 3. Screen readers will announce the Trigger/Dropzone content (which can be translated) instead\n // 4. This is only used as a fallback when no FormField.Label is present\n aria-label={!field.labelId ? 'Upload files' : undefined}\n className=\"sr-only\"\n onChange={e => {\n if (e.target.files && !disabled && !readOnly) {\n addFiles(Array.from(e.target.files))\n // Reset input value to allow selecting the same file again\n try {\n e.target.value = ''\n } catch {\n // Ignore error if value is read-only (e.g., in tests)\n }\n }\n }}\n />\n </div>\n {/* </Comp> */}\n </FileUploadContext.Provider>\n )\n}\n\nFileUpload.displayName = 'FileUpload'\n\nexport const useFileUploadContext = () => {\n const context = useContext(FileUploadContext)\n\n if (!context) {\n throw Error('useFileUploadContext must be used within a FileUpload provider')\n }\n\n return context\n}\n","import { Close } from '@spark-ui/icons/Close'\nimport { cx } from 'class-variance-authority'\nimport { useRef } from 'react'\n\nimport { Icon } from '../icon'\nimport { IconButton } from '../icon-button'\nimport { useFileUploadContext } from './FileUpload'\nimport { findFocusableElement } from './utils'\n\nexport interface FileUploadItemDeleteTriggerProps extends React.ComponentProps<typeof IconButton> {\n /**\n * The file to delete\n */\n file: File\n}\n\nexport const ItemDeleteTrigger = ({\n className,\n file,\n onClick,\n ...props\n}: FileUploadItemDeleteTriggerProps) => {\n const {\n removeFile,\n triggerRef,\n dropzoneRef,\n deleteButtonRefs,\n inputRef,\n disabled,\n readOnly,\n files,\n } = useFileUploadContext()\n const buttonRef = useRef<HTMLButtonElement>(null)\n\n // Find the index of the file using name + size (consistent with duplicate detection logic)\n const fileIndex = files.findIndex(f => f.name === file.name && f.size === file.size)\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n // Don't allow removing files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n // Remove the file\n removeFile(fileIndex)\n\n // Handle focus after removal\n requestAnimationFrame(() => {\n // Get all remaining delete buttons from the refs array\n const remainingButtons = deleteButtonRefs.current.filter(Boolean)\n\n if (remainingButtons.length > 0) {\n // Find the button that should receive focus\n // We want to focus on the button that takes the same position as the removed one\n // If that position doesn't exist (we removed the last item), focus on the previous one\n const targetIndex = Math.min(fileIndex, remainingButtons.length - 1)\n const nextButton = remainingButtons[targetIndex]\n\n if (nextButton) {\n nextButton.focus()\n }\n } else {\n // No more files, find a focusable element (trigger, dropzone, or input as last resort)\n const focusTarget = findFocusableElement(\n [triggerRef.current, dropzoneRef.current],\n inputRef\n )\n if (focusTarget) {\n focusTarget.focus()\n }\n }\n })\n\n onClick?.(e)\n }\n\n const setRef = (node: HTMLButtonElement | null) => {\n buttonRef.current = node\n if (node) {\n // Ensure the array is large enough\n while (deleteButtonRefs.current.length <= fileIndex) {\n deleteButtonRefs.current.push(null as any)\n }\n deleteButtonRefs.current[fileIndex] = node\n } else {\n // Remove the ref when component unmounts\n if (deleteButtonRefs.current[fileIndex]) {\n deleteButtonRefs.current[fileIndex] = null as any\n }\n }\n }\n\n return (\n <IconButton\n ref={setRef}\n data-spark-component=\"file-upload-item-delete-trigger\"\n className={cx(className)}\n onClick={handleClick}\n disabled={disabled || readOnly}\n size=\"sm\"\n design=\"contrast\"\n intent=\"surface\"\n {...props}\n >\n <Icon size=\"sm\">\n <Close />\n </Icon>\n </IconButton>\n )\n}\n\nItemDeleteTrigger.displayName = 'FileUpload.ItemDeleteTrigger'\n","import { cx } from 'class-variance-authority'\nimport { ComponentPropsWithoutRef, Ref, useCallback, useEffect, useState } from 'react'\n\nimport { Icon } from '../icon'\nimport { Progress } from '../progress'\nimport { useFileUploadContext } from './FileUpload'\nimport { ItemDeleteTrigger } from './FileUploadItemDeleteTrigger'\nimport { formatFileSize, getFileIcon } from './utils'\n\nexport interface FileUploadAcceptedFileProps extends ComponentPropsWithoutRef<'li'> {\n ref?: Ref<HTMLLIElement>\n /**\n * The file to display\n */\n file: File\n /**\n * Upload progress value (0-100). When provided, displays a progress bar at the bottom of the file item.\n */\n uploadProgress?: number\n /**\n * Accessible label for the delete button\n */\n deleteButtonAriaLabel: string\n /**\n * Accessible label for the progress bar. Required when uploadProgress is provided.\n */\n progressAriaLabel?: string\n className?: string\n}\n\nexport const AcceptedFile = ({\n className,\n file,\n uploadProgress,\n deleteButtonAriaLabel,\n progressAriaLabel,\n ...props\n}: FileUploadAcceptedFileProps) => {\n const { locale } = useFileUploadContext()\n const [showProgress, setShowProgress] = useState(uploadProgress !== undefined)\n\n useEffect(() => {\n // Reset showProgress when uploadProgress becomes defined\n if (uploadProgress !== undefined) {\n setShowProgress(true)\n } else {\n setShowProgress(false)\n }\n }, [uploadProgress])\n\n const handleProgressComplete = useCallback(() => {\n setShowProgress(false)\n }, [])\n\n return (\n <li\n data-spark-component=\"file-upload-accepted-file\"\n className={cx(\n 'relative',\n 'default:bg-surface default:border-sm default:border-outline default:p-md default:rounded-md',\n 'gap-md flex items-center justify-between default:w-full',\n className\n )}\n {...props}\n >\n <div className=\"size-sz-36 bg-support-container flex items-center justify-center rounded-md\">\n <Icon size=\"md\">{getFileIcon(file)}</Icon>\n </div>\n\n <div className=\"gap-md relative flex min-w-0 flex-1 flex-row items-center justify-between self-stretch\">\n <p className=\"text-body-2 truncate font-medium\">{file.name}</p>\n <p className=\"text-caption opacity-dim-1\">{formatFileSize(file.size, locale)}</p>\n {showProgress && uploadProgress !== undefined && (\n <div className=\"absolute bottom-0 left-0 w-full\">\n <Progress\n value={uploadProgress}\n max={100}\n aria-label={progressAriaLabel}\n onComplete={handleProgressComplete}\n />\n </div>\n )}\n </div>\n\n <ItemDeleteTrigger aria-label={deleteButtonAriaLabel} file={file} />\n </li>\n )\n}\n\nAcceptedFile.displayName = 'FileUpload.AcceptedFile'\n","import { ReactNode } from 'react'\n\nimport { type RejectedFile, useFileUploadContext } from './FileUpload'\nimport { formatFileSize } from './utils'\n\nexport interface FileUploadContextProps {\n /**\n * Render prop that receives acceptedFiles, rejectedFiles, formatFileSize, and locale\n */\n children: (props: {\n acceptedFiles: File[]\n rejectedFiles: RejectedFile[]\n formatFileSize: (bytes: number, locale?: string) => string\n locale?: string\n }) => ReactNode\n}\n\nexport const Context = ({ children }: FileUploadContextProps) => {\n const { files = [], rejectedFiles = [], locale } = useFileUploadContext()\n\n return (\n <>\n {children({\n acceptedFiles: files,\n rejectedFiles,\n formatFileSize,\n locale,\n })}\n </>\n )\n}\n\nContext.displayName = 'FileUpload.Context'\n","import { cx } from 'class-variance-authority'\nimport { createContext, useContext, useRef } from 'react'\n\nimport { useFileUploadContext } from './FileUpload'\n\n// Context to signal that we're inside a Dropzone\nexport const DropzoneContext = createContext<boolean>(false)\n\nexport const useDropzoneContext = () => useContext(DropzoneContext)\n\nexport function Dropzone({\n children,\n className,\n unstyled = false,\n}: {\n children?: React.ReactNode\n className?: string\n unstyled?: boolean\n}) {\n const ctx = useFileUploadContext()\n const dropzoneRef = useRef<HTMLDivElement>(null)\n\n if (!ctx) throw new Error('FileUploadDropzone must be used inside <FileUpload>')\n\n const handleDrop = (e: React.DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n e.currentTarget.setAttribute('data-drag-over', 'false')\n\n // Don't allow dropping files when disabled or readOnly\n if (ctx.disabled || ctx.readOnly) {\n return\n }\n\n const files = e.dataTransfer.files\n\n // Add files to the context\n // Convert to array - handle both FileList and array (for tests)\n let filesArray: File[] = []\n if (files) {\n filesArray = Array.isArray(files) ? [...files] : Array.from(files)\n }\n\n if (filesArray.length > 0) {\n ctx.addFiles(filesArray)\n }\n }\n\n const handleClick = () => {\n if (!ctx.disabled && !ctx.readOnly) {\n ctx.inputRef.current?.click()\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n if (!ctx.disabled && !ctx.readOnly) {\n ctx.inputRef.current?.click()\n }\n }\n }\n\n const isDisabled = ctx.disabled || ctx.readOnly\n\n return (\n <DropzoneContext.Provider value={true}>\n <div\n ref={node => {\n dropzoneRef.current = node\n if (ctx.dropzoneRef) {\n ctx.dropzoneRef.current = node\n }\n }}\n role=\"button\"\n tabIndex={isDisabled ? -1 : 0}\n aria-disabled={ctx.disabled ? true : undefined}\n aria-describedby={ctx.description}\n aria-invalid={ctx.isInvalid}\n aria-required={ctx.isRequired}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onDrop={handleDrop}\n onDragOver={e => {\n e.preventDefault()\n }}\n className={\n unstyled\n ? className\n : cx(\n 'default:bg-surface default:border-sm default:border-outline default:relative default:rounded-lg default:border-dashed',\n 'gap-lg flex flex-col items-center justify-center text-center',\n 'default:p-xl',\n 'transition-colors duration-200',\n !isDisabled && 'default:hover:bg-surface-hovered',\n 'data-[drag-over=true]:border-outline-high data-[drag-over=true]:bg-surface-hovered data-[drag-over=true]:border-solid',\n // Disabled: more visually disabled (opacity + cursor)\n ctx.disabled && 'cursor-not-allowed opacity-50',\n // ReadOnly: less visually disabled (just cursor, no opacity)\n ctx.readOnly && !ctx.disabled && 'cursor-default',\n className\n )\n }\n onDragEnter={e => {\n if (!isDisabled) {\n e.currentTarget.setAttribute('data-drag-over', 'true')\n }\n }}\n onDragLeave={e => {\n e.currentTarget.setAttribute('data-drag-over', 'false')\n }}\n >\n {children}\n </div>\n </DropzoneContext.Provider>\n )\n}\n\nDropzone.displayName = 'FileUploadDropzone'\n","import { cx } from 'class-variance-authority'\nimport { ComponentPropsWithoutRef, Ref, useEffect, useState } from 'react'\n\nexport interface FileUploadPreviewImageProps extends ComponentPropsWithoutRef<'div'> {\n ref?: Ref<HTMLDivElement>\n className?: string\n /**\n * The file to preview\n */\n file: File\n /**\n * Fallback content when file is not an image or preview fails\n */\n fallback?: React.ReactNode\n}\n\nexport const PreviewImage = ({\n className,\n file,\n fallback = '📄',\n ...props\n}: FileUploadPreviewImageProps) => {\n const [imageError, setImageError] = useState(false)\n const [imageLoaded, setImageLoaded] = useState(false)\n const [imageUrl, setImageUrl] = useState<string | null>(null)\n\n const isImage = file.type.startsWith('image/')\n\n // Create and clean up the object URL when file changes\n useEffect(() => {\n if (!isImage) {\n setImageUrl(null)\n return\n }\n\n const url = URL.createObjectURL(file)\n setImageUrl(url)\n\n // Clean up: only revoke when the file actually changes or component unmounts\n return () => {\n URL.revokeObjectURL(url)\n }\n }, [file, isImage])\n\n if (!isImage || imageError) {\n return (\n <div\n data-spark-component=\"file-upload-preview-image\"\n className={cx(\n 'bg-neutral-container flex items-center justify-center rounded-md',\n className\n )}\n {...props}\n >\n {fallback}\n </div>\n )\n }\n\n return (\n <div\n data-spark-component=\"file-upload-preview-image\"\n className={cx('bg-neutral-container overflow-hidden', className)}\n {...props}\n >\n <img\n src={imageUrl!}\n alt={file.name}\n className={cx('size-full object-cover', !imageLoaded && 'opacity-0')}\n onLoad={() => setImageLoaded(true)}\n onError={() => setImageError(true)}\n />\n {!imageLoaded && (\n <div className=\"absolute inset-0 flex items-center justify-center\">{fallback}</div>\n )}\n </div>\n )\n}\n\nPreviewImage.displayName = 'FileUpload.PreviewImage'\n","import { Close } from '@spark-ui/icons/Close'\nimport { cx } from 'class-variance-authority'\nimport { useRef } from 'react'\n\nimport { Icon } from '../icon'\nimport { IconButton } from '../icon-button'\nimport { type RejectedFile as RejectedFileType, useFileUploadContext } from './FileUpload'\nimport { findFocusableElement } from './utils'\n\nexport interface FileUploadRejectedFileDeleteTriggerProps extends React.ComponentProps<\n typeof IconButton\n> {\n /**\n * The rejected file to remove\n */\n rejectedFile: RejectedFileType\n}\n\nexport const RejectedFileDeleteTrigger = ({\n className,\n rejectedFile,\n onClick,\n ...props\n}: FileUploadRejectedFileDeleteTriggerProps) => {\n const {\n removeRejectedFile,\n triggerRef,\n dropzoneRef,\n rejectedFileDeleteButtonRefs,\n inputRef,\n disabled,\n readOnly,\n rejectedFiles,\n } = useFileUploadContext()\n const buttonRef = useRef<HTMLButtonElement>(null)\n\n // Find the index of the rejected file using name + size (consistent with duplicate detection logic)\n const rejectedFileIndex = rejectedFiles.findIndex(\n rf => rf.file.name === rejectedFile.file.name && rf.file.size === rejectedFile.file.size\n )\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n // Don't allow removing rejected files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n // Remove the rejected file\n removeRejectedFile(rejectedFileIndex)\n\n // Handle focus after removal\n requestAnimationFrame(() => {\n // Get all remaining rejected file delete buttons from the refs array\n const remainingButtons = rejectedFileDeleteButtonRefs.current.filter(Boolean)\n\n if (remainingButtons.length > 0) {\n // Find the button that should receive focus\n // We want to focus on the button that takes the same position as the removed one\n // If that position doesn't exist (we removed the last item), focus on the previous one\n const targetIndex = Math.min(rejectedFileIndex, remainingButtons.length - 1)\n const nextButton = remainingButtons[targetIndex]\n\n if (nextButton) {\n nextButton.focus()\n }\n } else {\n // No more rejected files, find a focusable element (trigger, dropzone, or input as last resort)\n const focusTarget = findFocusableElement(\n [triggerRef.current, dropzoneRef.current],\n inputRef\n )\n if (focusTarget) {\n focusTarget.focus()\n }\n }\n })\n\n onClick?.(e)\n }\n\n const setRef = (node: HTMLButtonElement | null) => {\n buttonRef.current = node\n if (node) {\n // Ensure the array is large enough\n while (rejectedFileDeleteButtonRefs.current.length <= rejectedFileIndex) {\n rejectedFileDeleteButtonRefs.current.push(null as any)\n }\n rejectedFileDeleteButtonRefs.current[rejectedFileIndex] = node\n } else {\n // Remove the ref when component unmounts\n if (rejectedFileDeleteButtonRefs.current[rejectedFileIndex]) {\n rejectedFileDeleteButtonRefs.current[rejectedFileIndex] = null as any\n }\n }\n }\n\n return (\n <IconButton\n ref={setRef}\n data-spark-component=\"file-upload-rejected-file-delete-trigger\"\n className={cx(className)}\n onClick={handleClick}\n disabled={disabled || readOnly}\n size=\"sm\"\n design=\"contrast\"\n intent=\"surface\"\n {...props}\n >\n <Icon size=\"sm\">\n <Close />\n </Icon>\n </IconButton>\n )\n}\n\nRejectedFileDeleteTrigger.displayName = 'FileUpload.RejectedFileDeleteTrigger'\n","import { WarningOutline } from '@spark-ui/icons/WarningOutline'\nimport { cx } from 'class-variance-authority'\nimport { ComponentPropsWithoutRef, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport {\n type FileUploadFileError,\n type RejectedFile as RejectedFileType,\n useFileUploadContext,\n} from './FileUpload'\nimport { RejectedFileDeleteTrigger } from './FileUploadRejectedFileDeleteTrigger'\nimport { formatFileSize } from './utils'\n\nexport interface FileUploadRejectedFileProps extends ComponentPropsWithoutRef<'li'> {\n ref?: Ref<HTMLLIElement>\n /**\n * The rejected file to display\n */\n rejectedFile: RejectedFileType\n /**\n * Function to render the error message for each error code\n * @param error - The error code\n * @returns The error message to display\n */\n renderError: (error: FileUploadFileError) => string\n /**\n * Accessible label for the delete button\n */\n deleteButtonAriaLabel: string\n className?: string\n}\n\nexport const RejectedFile = ({\n className,\n rejectedFile,\n renderError,\n deleteButtonAriaLabel,\n ...props\n}: FileUploadRejectedFileProps) => {\n const { locale } = useFileUploadContext()\n\n return (\n <li\n data-spark-component=\"file-upload-rejected-file\"\n className={cx(\n 'relative',\n 'default:bg-surface default:border-sm default:border-outline default:p-md default:rounded-md',\n 'gap-md flex items-center justify-between default:w-full',\n 'border-error border-md',\n className\n )}\n {...props}\n >\n <div className=\"size-sz-36 bg-error-container flex items-center justify-center rounded-md\">\n <Icon size=\"md\" className=\"text-error\">\n <WarningOutline />\n </Icon>\n </div>\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"gap-md flex flex-col\">\n <div className=\"gap-md flex flex-row items-center justify-between\">\n <p className=\"text-body-2 truncate font-medium\">{rejectedFile.file.name}</p>\n <p className=\"text-caption opacity-dim-1\">\n {formatFileSize(rejectedFile.file.size, locale)}\n </p>\n </div>\n <div className=\"gap-xs flex flex-col\">\n {rejectedFile.errors.map((error, errorIndex) => (\n <div key={errorIndex} className=\"text-caption text-error\" data-error-code={error}>\n {renderError(error)}\n </div>\n ))}\n </div>\n </div>\n </div>\n\n <RejectedFileDeleteTrigger aria-label={deleteButtonAriaLabel} rejectedFile={rejectedFile} />\n </li>\n )\n}\n\nRejectedFile.displayName = 'FileUpload.RejectedFile'\n","import { cx } from 'class-variance-authority'\nimport React, { ReactNode, Ref } from 'react'\n\nimport { Button, type ButtonProps } from '../button'\nimport { buttonStyles } from '../button/Button.styles'\nimport { Slot } from '../slot'\nimport { useFileUploadContext } from './FileUpload'\nimport { useDropzoneContext } from './FileUploadDropzone'\n\nexport interface FileUploadTriggerProps extends Omit<ButtonProps, 'children' | 'disabled'> {\n ref?: Ref<HTMLButtonElement>\n className?: string\n children: ReactNode\n unstyled?: boolean\n}\n\nexport const Trigger = ({\n className,\n children,\n asChild = false,\n unstyled = false,\n design = 'filled',\n intent = 'support',\n size = 'md',\n shape = 'rounded',\n ref,\n ...props\n}: FileUploadTriggerProps) => {\n const { inputRef, triggerRef, disabled, readOnly, description, isInvalid, isRequired } =\n useFileUploadContext()\n const isInsideDropzone = useDropzoneContext()\n\n const handleClick = (e: React.MouseEvent) => {\n e.stopPropagation()\n e.preventDefault()\n if (!disabled && !readOnly) {\n inputRef.current?.click()\n }\n }\n\n // Shared ref forwarding logic\n const handleRef = (node: HTMLElement | null) => {\n // Forward ref to both the context ref and the user ref\n if (triggerRef) {\n triggerRef.current = node\n }\n if (ref) {\n if (typeof ref === 'function') {\n ref(node as HTMLButtonElement)\n } else {\n ref.current = node as HTMLButtonElement\n }\n }\n }\n\n // Determine component and props based on context\n // If inside a Dropzone, render as a non-interactive span\n // The Dropzone handles all interactions\n let Component: React.ElementType\n let componentProps: Record<string, unknown>\n\n if (isInsideDropzone) {\n // Don't use asChild when inside Dropzone - we always want a span\n Component = 'span'\n const spanStyles = unstyled\n ? className\n : buttonStyles({\n design,\n intent,\n size,\n shape,\n disabled: disabled || readOnly,\n className,\n })\n\n componentProps = {\n ref: handleRef,\n 'data-spark-component': 'file-upload-trigger',\n className: spanStyles,\n // No onClick, no role, no tabIndex - Dropzone handles interaction\n // No aria attributes here - they're on the Dropzone\n }\n } else {\n // Normal behavior when not inside Dropzone\n const buttonComponent = unstyled ? 'button' : Button\n Component = asChild ? Slot : buttonComponent\n\n componentProps = {\n ref: handleRef,\n type: 'button',\n design,\n intent,\n size,\n shape,\n 'data-spark-component': 'file-upload-trigger',\n className: cx(className),\n disabled: disabled || readOnly,\n onClick: handleClick,\n 'aria-describedby': description,\n 'aria-invalid': isInvalid,\n 'aria-required': isRequired,\n ...props,\n }\n }\n\n return <Component {...componentProps}>{children}</Component>\n}\n\nTrigger.displayName = 'FileUpload.Trigger'\n","import { FILE_UPLOAD_ERRORS } from './constants'\nimport {\n type FileAcceptDetails,\n type FileChangeDetails,\n type FileRejectDetails,\n FileUpload as Root,\n type FileUploadFileError,\n type RejectedFile,\n} from './FileUpload'\nimport { AcceptedFile } from './FileUploadAcceptedFile'\nimport { Context } from './FileUploadContext'\nimport { Dropzone } from './FileUploadDropzone'\nimport { ItemDeleteTrigger } from './FileUploadItemDeleteTrigger'\nimport { PreviewImage } from './FileUploadPreviewImage'\nimport { RejectedFile as RejectedFileComponent } from './FileUploadRejectedFile'\nimport { RejectedFileDeleteTrigger } from './FileUploadRejectedFileDeleteTrigger'\nimport { Trigger } from './FileUploadTrigger'\n\nexport type {\n FileAcceptDetails,\n FileChangeDetails,\n FileRejectDetails,\n RejectedFile,\n FileUploadFileError,\n}\n\nexport { FILE_UPLOAD_ERRORS }\n\nexport const FileUpload: typeof Root & {\n Trigger: typeof Trigger\n Dropzone: typeof Dropzone\n Context: typeof Context\n ItemDeleteTrigger: typeof ItemDeleteTrigger\n PreviewImage: typeof PreviewImage\n AcceptedFile: typeof AcceptedFile\n RejectedFile: typeof RejectedFileComponent\n RejectedFileDeleteTrigger: typeof RejectedFileDeleteTrigger\n} = Object.assign(Root, {\n // Main input components\n Trigger,\n Dropzone,\n // Context components\n Context,\n AcceptedFile,\n RejectedFile: RejectedFileComponent,\n // Helpers for custom renders\n PreviewImage,\n ItemDeleteTrigger,\n RejectedFileDeleteTrigger,\n})\n\nFileUpload.displayName = 'FileUpload'\nTrigger.displayName = 'FileUpload.Trigger'\nDropzone.displayName = 'FileUpload.Dropzone'\nContext.displayName = 'FileUpload.Context'\nItemDeleteTrigger.displayName = 'FileUpload.ItemDeleteTrigger'\nPreviewImage.displayName = 'FileUpload.PreviewImage'\nAcceptedFile.displayName = 'FileUpload.AcceptedFile'\nRejectedFileComponent.displayName = 'FileUpload.RejectedFile'\nRejectedFileDeleteTrigger.displayName = 'FileUpload.RejectedFileDeleteTrigger'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/file-upload/constants.ts","../../src/file-upload/utils.ts","../../src/file-upload/useFileUploadState.tsx","../../src/file-upload/FileUpload.tsx","../../src/file-upload/FileUploadItemDeleteTrigger.tsx","../../src/file-upload/FileUploadAcceptedFile.tsx","../../src/file-upload/FileUploadContext.tsx","../../src/file-upload/FileUploadDropzone.tsx","../../src/file-upload/FileUploadPreviewImage.tsx","../../src/file-upload/FileUploadRejectedFileDeleteTrigger.tsx","../../src/file-upload/FileUploadRejectedFile.tsx","../../src/file-upload/FileUploadTrigger.tsx","../../src/file-upload/index.ts"],"sourcesContent":["/**\n * File upload error codes\n */\nexport const FILE_UPLOAD_ERRORS = {\n /**\n * Exceeds the maxFiles limit\n */\n TOO_MANY_FILES: 'TOO_MANY_FILES',\n /**\n * File type not in the accept list\n */\n FILE_INVALID_TYPE: 'FILE_INVALID_TYPE',\n /**\n * File size exceeds maxFileSize\n */\n FILE_TOO_LARGE: 'FILE_TOO_LARGE',\n /**\n * File size below minFileSize\n */\n FILE_TOO_SMALL: 'FILE_TOO_SMALL',\n /**\n * Generic validation failure\n */\n FILE_INVALID: 'FILE_INVALID',\n /**\n * Duplicate file detected\n */\n FILE_EXISTS: 'FILE_EXISTS',\n} as const\n","import { CvOutline } from '@spark-ui/icons/CvOutline'\nimport { FilePdfOutline } from '@spark-ui/icons/FilePdfOutline'\nimport { ImageOutline } from '@spark-ui/icons/ImageOutline'\nimport { PlayOutline } from '@spark-ui/icons/PlayOutline'\nimport { createElement, ReactElement, type RefObject } from 'react'\n\n/**\n * Validates if a file matches the accept patterns\n * Supports MIME types (e.g., \"image/*\", \"image/png\", \"application/pdf\")\n * and file extensions (e.g., \".pdf\", \".doc\", \".jpg\")\n */\nexport function validateFileAccept(file: File, accept: string): boolean {\n if (!accept) {\n return true\n }\n\n const patterns = accept.split(',').map(pattern => pattern.trim())\n\n return patterns.some(pattern => {\n // Handle MIME type patterns (e.g., \"image/*\", \"image/png\")\n if (pattern.includes('/')) {\n if (pattern.endsWith('/*')) {\n // Wildcard MIME type (e.g., \"image/*\")\n const baseType = pattern.slice(0, -2)\n\n return file.type.startsWith(baseType + '/')\n }\n // Exact MIME type (e.g., \"image/png\")\n\n return file.type === pattern\n }\n\n // Handle file extension patterns (e.g., \".pdf\", \".doc\")\n if (pattern.startsWith('.')) {\n const extension = pattern.toLowerCase()\n const fileName = file.name.toLowerCase()\n\n return fileName.endsWith(extension)\n }\n\n // Handle extension without dot (e.g., \"pdf\", \"doc\")\n const extension = '.' + pattern.toLowerCase()\n const fileName = file.name.toLowerCase()\n\n return fileName.endsWith(extension)\n })\n}\n\n/**\n * Validates if a file size is within the allowed range\n * @param file - The file to validate\n * @param minFileSize - Minimum file size in bytes\n * @param maxFileSize - Maximum file size in bytes\n * @param locale - Locale code for error messages. Defaults to browser locale or 'en'\n * @returns Object with validation result and error message if invalid\n */\nexport function validateFileSize(\n file: File,\n minFileSize?: number,\n maxFileSize?: number,\n locale?: string\n): { valid: boolean; error?: string } {\n const defaultLocale = locale || getDefaultLocale()\n if (minFileSize !== undefined && file.size < minFileSize) {\n const errorMessage = `File \"${file.name}\" is too small. Minimum size is ${formatFileSize(minFileSize, defaultLocale)}.`\n\n return {\n valid: false,\n error: errorMessage,\n }\n }\n\n if (maxFileSize !== undefined && file.size > maxFileSize) {\n const errorMessage = `File \"${file.name}\" is too large. Maximum size is ${formatFileSize(maxFileSize, defaultLocale)}.`\n\n return {\n valid: false,\n error: errorMessage,\n }\n }\n\n return { valid: true }\n}\n\n/**\n * Gets the default locale from the browser or falls back to 'en'\n * @returns The browser's locale or 'en' as fallback\n */\nfunction getDefaultLocale(): string {\n if (typeof navigator !== 'undefined' && navigator.language) {\n return navigator.language\n }\n\n return 'en'\n}\n\n/**\n * Formats file size in bytes to human-readable format\n * @param bytes - File size in bytes\n * @param locale - Locale code (e.g., 'en', 'fr'). Defaults to browser locale or 'en'\n * @returns Formatted file size string with appropriate unit\n */\nexport function formatFileSize(bytes: number, locale?: string): string {\n const defaultLocale = locale || getDefaultLocale()\n // Normalize locale (e.g., 'fr' -> 'fr-FR', 'en' -> 'en-US')\n let normalizedLocale = defaultLocale\n if (defaultLocale.length === 2) {\n normalizedLocale = defaultLocale === 'fr' ? 'fr-FR' : 'en-US'\n }\n\n if (bytes === 0) {\n const formatter = new Intl.NumberFormat(normalizedLocale, {\n style: 'unit',\n unit: 'byte',\n unitDisplay: 'long',\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n })\n\n return formatter.format(0)\n }\n\n const k = 1024\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n\n // Map to Intl.NumberFormat supported units\n const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte'] as const\n const unit = units[i] || 'byte'\n\n const size = bytes / Math.pow(k, i)\n\n // Use 'long' display for bytes to get proper pluralization (bytes/octets)\n // Use 'short' display for other units (KB/MB/GB, Ko/Mo/Go)\n const unitDisplay = i === 0 ? 'long' : 'short'\n\n // Use Intl.NumberFormat with unit style to format number and unit according to locale\n const formatter = new Intl.NumberFormat(normalizedLocale, {\n style: 'unit',\n unit,\n unitDisplay,\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n })\n\n return formatter.format(size)\n}\n\n/**\n * Returns the appropriate icon component based on the file type\n * @param file - The file to get the icon for\n * @returns React element representing the icon component\n */\nexport function getFileIcon(file: File): ReactElement {\n const fileType = file.type.toLowerCase()\n const fileName = file.name.toLowerCase()\n\n // Check for images\n if (fileType.startsWith('image/') || /\\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(fileName)) {\n return createElement(ImageOutline)\n }\n\n // Check for PDFs\n if (fileType === 'application/pdf' || fileName.endsWith('.pdf')) {\n return createElement(FilePdfOutline)\n }\n\n // Check for videos\n if (fileType.startsWith('video/') || /\\.(mp4|avi|mov|wmv|flv|webm|mkv)$/i.test(fileName)) {\n return createElement(PlayOutline)\n }\n\n // Default icon for other file types\n return createElement(CvOutline)\n}\n\n/**\n * Checks if an element is focusable\n * @param element - The element to check\n * @returns True if the element is focusable\n */\nfunction isFocusable(element: HTMLElement | null): boolean {\n if (!element) {\n return false\n }\n\n // Check if element has tabIndex >= 0 (not -1)\n const tabIndex = element.tabIndex\n if (tabIndex >= 0) {\n return true\n }\n\n // Check if element is naturally focusable (input, button, select, textarea, a with href, etc.)\n const isContentEditable = String(element.contentEditable) === 'true'\n const naturallyFocusable: boolean =\n element instanceof HTMLInputElement ||\n element instanceof HTMLButtonElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement ||\n (element instanceof HTMLAnchorElement && Boolean(element.href)) ||\n isContentEditable\n\n return naturallyFocusable\n}\n\n/**\n * Finds the first focusable element from a list of candidates\n * Falls back to inputRef if no other element is focusable\n * @param candidates - Array of candidate elements to check\n * @param inputRef - Input element to use as last resort\n * @returns The first focusable element, or null if none found\n */\nexport function findFocusableElement(\n candidates: (HTMLElement | null)[],\n inputRef: RefObject<HTMLInputElement | null>\n): HTMLElement | null {\n // Try each candidate in order\n for (const candidate of candidates) {\n if (isFocusable(candidate)) {\n return candidate\n }\n }\n\n // Last resort: use inputRef (even though it has tabIndex={-1}, we can still focus it programmatically)\n if (inputRef.current) {\n return inputRef.current\n }\n\n return null\n}\n","/* eslint-disable max-lines-per-function */\nimport { useCombinedState } from '@spark-ui/hooks/use-combined-state'\nimport { useState } from 'react'\n\nimport { FILE_UPLOAD_ERRORS } from './constants'\nimport { validateFileAccept, validateFileSize } from './utils'\n\nexport type FileUploadFileError =\n | typeof FILE_UPLOAD_ERRORS.TOO_MANY_FILES\n | typeof FILE_UPLOAD_ERRORS.FILE_INVALID_TYPE\n | typeof FILE_UPLOAD_ERRORS.FILE_TOO_LARGE\n | typeof FILE_UPLOAD_ERRORS.FILE_TOO_SMALL\n | typeof FILE_UPLOAD_ERRORS.FILE_INVALID\n | typeof FILE_UPLOAD_ERRORS.FILE_EXISTS\n\nexport interface RejectedFile {\n file: File\n errors: FileUploadFileError[]\n}\n\nexport interface FileAcceptDetails {\n files: File[]\n}\n\nexport interface FileRejectDetails {\n files: RejectedFile[]\n}\n\nexport interface FileChangeDetails {\n acceptedFiles: File[]\n rejectedFiles: RejectedFile[]\n}\n\nexport interface UseFileUploadStateProps {\n /**\n * Initial files to display when the component mounts (uncontrolled mode)\n */\n defaultValue?: File[]\n /**\n * Controlled files value (controlled mode)\n * When provided, the component becomes controlled\n */\n value?: File[]\n /**\n * Callback when files are accepted\n */\n onFileAccept?: (details: FileAcceptDetails) => void\n /**\n * Callback when files are rejected\n */\n onFileReject?: (details: FileRejectDetails) => void\n /**\n * Callback when files change (both accepted and rejected)\n * For controlled mode, use this to update the value prop by extracting details.acceptedFiles\n */\n onFileChange?: (details: FileChangeDetails) => void\n /**\n * Whether multiple files can be selected\n * @default true\n */\n multiple?: boolean\n /**\n * Comma-separated list of accepted file types\n */\n accept?: string\n /**\n * Maximum number of files that can be uploaded\n */\n maxFiles?: number\n /**\n * Maximum file size in bytes\n */\n maxFileSize?: number\n /**\n * Minimum file size in bytes\n */\n minFileSize?: number\n /**\n * When `true`, prevents the user from interacting with the file upload\n */\n disabled?: boolean\n /**\n * When `true`, sets the file upload to read-only mode\n */\n readOnly?: boolean\n /**\n * The [BCP47](https://www.ietf.org/rfc/bcp/bcp47.txt) language code for the locale.\n * Used for formatting file sizes and error messages.\n */\n locale?: string\n}\n\nexport interface UseFileUploadStateReturn {\n files: File[]\n rejectedFiles: RejectedFile[]\n addFiles: (files: File[]) => void\n removeFile: (index: number) => void\n removeRejectedFile: (index: number) => void\n clearFiles: () => void\n clearRejectedFiles: () => void\n maxFilesReached: boolean\n}\n\n/**\n * Hook that manages file upload state, validation, and file operations\n */\nexport function useFileUploadState({\n defaultValue = [],\n value: controlledValue,\n onFileAccept,\n onFileReject,\n onFileChange,\n multiple = true,\n accept,\n maxFiles,\n maxFileSize,\n minFileSize,\n disabled = false,\n readOnly = false,\n locale,\n}: UseFileUploadStateProps): UseFileUploadStateReturn {\n // Get default locale from browser or fallback to 'en'\n const defaultLocale =\n locale || (typeof navigator !== 'undefined' && navigator.language ? navigator.language : 'en')\n\n // For controlled mode, use onFileChange to update value prop\n // useCombinedState doesn't need a callback - we'll call onFileChange manually in addFiles/removeFile\n const [filesState, setFilesState] = useCombinedState<File[]>(controlledValue, defaultValue)\n const files = filesState ?? []\n const setFiles = setFilesState as (value: File[] | ((prev: File[]) => File[])) => void\n const [rejectedFiles, setRejectedFiles] = useState<RejectedFile[]>([])\n\n const addFiles = (newFiles: File[]) => {\n // Don't allow adding files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n // Reset rejectedFiles at the start of each new file addition attempt\n setRejectedFiles([])\n\n const newRejectedFiles: RejectedFile[] = []\n\n // Helper function to check if a file already exists\n // Compares by name and size to detect duplicates (lastModified can differ when re-selecting the same file)\n const fileExists = (file: File, existingFiles: File[]): boolean => {\n return existingFiles.some(\n existingFile => existingFile.name === file.name && existingFile.size === file.size\n )\n }\n\n // Helper function to add or update rejected file\n const addRejectedFile = (file: File, error: FileUploadFileError) => {\n const existingRejection = newRejectedFiles.find(\n rejected => rejected.file.name === file.name && rejected.file.size === file.size\n )\n\n if (existingRejection) {\n // Add error to existing rejection if not already present\n if (!existingRejection.errors.includes(error)) {\n existingRejection.errors.push(error)\n }\n } else {\n // Create new rejection\n newRejectedFiles.push({\n file,\n errors: [error],\n })\n }\n }\n\n setFiles((prev: File[]) => {\n const currentFiles = prev ?? []\n\n // Calculate remaining slots once at the beginning\n const remainingSlots = maxFiles !== undefined ? maxFiles - currentFiles.length : undefined\n\n // Mark all files with TOO_MANY_FILES if already at max (before other validations)\n // This allows a file to have multiple error codes (e.g., FILE_INVALID_TYPE + TOO_MANY_FILES)\n if (remainingSlots !== undefined && remainingSlots <= 0) {\n newFiles.forEach(file => {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.TOO_MANY_FILES)\n })\n }\n\n // Track files rejected by accept pattern\n let filteredFiles = newFiles\n if (accept) {\n const rejectedByAccept = newFiles.filter(file => !validateFileAccept(file, accept))\n rejectedByAccept.forEach(file => {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_INVALID_TYPE)\n })\n filteredFiles = newFiles.filter(file => validateFileAccept(file, accept))\n }\n\n // Track files rejected by size\n let validSizeFiles = filteredFiles\n if (minFileSize !== undefined || maxFileSize !== undefined) {\n validSizeFiles = filteredFiles.filter(file => {\n const validation = validateFileSize(file, minFileSize, maxFileSize, defaultLocale)\n if (!validation.valid) {\n if (maxFileSize !== undefined && file.size > maxFileSize) {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_TOO_LARGE)\n } else if (minFileSize !== undefined && file.size < minFileSize) {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_TOO_SMALL)\n } else {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_INVALID)\n }\n\n return false\n }\n\n return true\n })\n }\n\n // Check for duplicate files (both against existing files and within the current batch)\n const seenFiles = new Map<string, File>()\n const uniqueFiles = validSizeFiles.filter(file => {\n // Create a unique key for the file (name + size)\n // Using name and size only, as lastModified can differ when re-selecting the same file\n const fileKey = `${file.name}-${file.size}`\n\n // Check if file already exists in previously accepted files\n const existsInPrev = fileExists(file, currentFiles)\n if (existsInPrev) {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_EXISTS)\n\n return false\n }\n\n // Check if file already exists in the current batch\n if (seenFiles.has(fileKey)) {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.FILE_EXISTS)\n\n return false\n }\n\n // Mark this file as seen\n seenFiles.set(fileKey, file)\n\n return true\n })\n\n // If multiple is false, replace existing files with only the first new file\n let filesToAdd = multiple ? uniqueFiles : uniqueFiles.slice(0, 1)\n\n // Apply maxFiles limit to files that passed all other validations\n if (remainingSlots !== undefined) {\n if (remainingSlots <= 0) {\n // Already at max, reject all valid files (they should already have TOO_MANY_FILES error from the first check)\n filesToAdd = []\n } else if (filesToAdd.length > remainingSlots) {\n // Reject all files if batch exceeds limit (\"all or nothing\" approach)\n filesToAdd.forEach(file => {\n addRejectedFile(file, FILE_UPLOAD_ERRORS.TOO_MANY_FILES)\n })\n filesToAdd = []\n }\n }\n\n const updated = multiple ? [...currentFiles, ...filesToAdd] : filesToAdd\n\n // Add rejected files to state synchronously\n // Note: newRejectedFiles is mutated inside this setFiles callback, so it should be populated by now\n // Copy the array to avoid closure issues\n const rejectedFilesToAdd = [...newRejectedFiles]\n // Replace rejectedFiles completely (not accumulate)\n setRejectedFiles(rejectedFilesToAdd)\n\n // Call callbacks with the calculated values\n // Note: These callbacks are called synchronously with the new values\n // React will update the state asynchronously, but the callbacks receive the correct new values\n if (filesToAdd.length > 0 && onFileAccept) {\n onFileAccept({ files: filesToAdd })\n }\n\n if (rejectedFilesToAdd.length > 0 && onFileReject) {\n onFileReject({ files: rejectedFilesToAdd })\n }\n\n if (onFileChange) {\n onFileChange({\n acceptedFiles: updated,\n rejectedFiles: rejectedFilesToAdd,\n })\n }\n\n return updated\n })\n }\n\n const removeFile = (index: number) => {\n // Don't allow removing files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n setFiles((prev: File[]) => {\n const currentFiles = prev ?? []\n const updated = currentFiles.filter((_: File, i: number) => i !== index)\n\n // Clean up TOO_MANY_FILES errors if we're now below the maxFiles limit\n let updatedRejectedFiles = rejectedFiles\n if (maxFiles !== undefined && updated.length < maxFiles) {\n updatedRejectedFiles = rejectedFiles.filter(\n rejected => !rejected.errors.includes(FILE_UPLOAD_ERRORS.TOO_MANY_FILES)\n )\n setRejectedFiles(updatedRejectedFiles)\n }\n\n // Call onFileChange for controlled mode\n if (onFileChange) {\n onFileChange({\n acceptedFiles: updated,\n rejectedFiles: updatedRejectedFiles,\n })\n }\n\n return updated\n })\n }\n\n const clearFiles = () => {\n // Don't allow clearing files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n setFiles([])\n setRejectedFiles([])\n\n // Call onFileChange for controlled mode\n if (onFileChange) {\n onFileChange({\n acceptedFiles: [],\n rejectedFiles: [],\n })\n }\n }\n\n const removeRejectedFile = (index: number) => {\n // Don't allow removing rejected files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n setRejectedFiles(prev => prev.filter((_, i) => i !== index))\n }\n\n const clearRejectedFiles = () => {\n setRejectedFiles([])\n }\n\n const maxFilesReached = maxFiles !== undefined && files.length >= maxFiles\n\n return {\n files,\n rejectedFiles,\n addFiles,\n removeFile,\n removeRejectedFile,\n clearFiles,\n clearRejectedFiles,\n maxFilesReached,\n }\n}\n","/* eslint-disable max-lines-per-function */\nimport { useFormFieldControl } from '@spark-ui/components/form-field'\nimport { createContext, ReactNode, Ref, useContext, useId, useRef } from 'react'\n\nimport {\n type FileAcceptDetails,\n type FileChangeDetails,\n type FileRejectDetails,\n type FileUploadFileError,\n type RejectedFile,\n useFileUploadState,\n} from './useFileUploadState'\n\n// Re-export types for backward compatibility\nexport type {\n FileAcceptDetails,\n FileChangeDetails,\n FileRejectDetails,\n FileUploadFileError,\n RejectedFile,\n}\n\nexport interface FileUploadProps {\n /**\n * Change the default rendered element for the one passed as a child, merging their props and behavior.\n */\n asChild?: boolean\n ref?: Ref<HTMLDivElement>\n children: ReactNode\n className?: string\n /**\n * Initial files to display when the component mounts (uncontrolled mode)\n */\n defaultValue?: File[]\n /**\n * Controlled files value (controlled mode)\n * When provided, the component becomes controlled\n */\n value?: File[]\n /**\n * Callback when files are accepted\n * @param details - Details about the accepted files\n */\n onFileAccept?: (details: FileAcceptDetails) => void\n /**\n * Callback when files are rejected\n * @param details - Details about the rejected files and their errors\n */\n onFileReject?: (details: FileRejectDetails) => void\n /**\n * Callback when files change (both accepted and rejected)\n * For controlled mode, use this to update the value prop by extracting details.acceptedFiles\n * @param details - Details about both accepted and rejected files\n */\n onFileChange?: (details: FileChangeDetails) => void\n /**\n * Whether multiple files can be selected\n * @default true\n */\n multiple?: boolean\n /**\n * Comma-separated list of accepted file types\n * Supports MIME types (e.g., \"image/*\", \"image/png\", \"application/pdf\")\n * and file extensions (e.g., \".pdf\", \".doc\", \".jpg\")\n * @example \"image/*\"\n * @example \".pdf,.doc\"\n * @example \"image/png,image/jpeg,.pdf\"\n */\n accept?: string\n /**\n * Maximum number of files that can be uploaded\n * Files beyond this limit will be rejected\n */\n maxFiles?: number\n /**\n * Maximum file size in bytes\n * Files larger than this will be rejected\n */\n maxFileSize?: number\n /**\n * Minimum file size in bytes\n * Files smaller than this will be rejected\n */\n minFileSize?: number\n /**\n * When `true`, prevents the user from interacting with the file upload\n */\n disabled?: boolean\n /**\n * When `true`, sets the file upload to read-only mode\n */\n readOnly?: boolean\n /**\n * The [BCP47](https://www.ietf.org/rfc/bcp/bcp47.txt) language code for the locale.\n * Used for formatting file sizes and error messages.\n * @default Browser locale or 'en' if not available\n */\n locale?: string\n}\n\nexport const FileUploadContext = createContext<{\n inputRef: React.RefObject<HTMLInputElement | null>\n files: File[]\n rejectedFiles: RejectedFile[]\n addFiles: (files: File[]) => void\n removeFile: (index: number) => void\n removeRejectedFile: (index: number) => void\n clearFiles: () => void\n clearRejectedFiles: () => void\n triggerRef: React.RefObject<HTMLElement | null>\n dropzoneRef: React.RefObject<HTMLElement | null>\n deleteButtonRefs: React.MutableRefObject<HTMLButtonElement[]>\n rejectedFileDeleteButtonRefs: React.MutableRefObject<HTMLButtonElement[]>\n multiple: boolean\n maxFiles?: number\n maxFilesReached: boolean\n disabled: boolean\n readOnly: boolean\n locale: string\n description?: string\n isInvalid?: boolean\n isRequired?: boolean\n} | null>(null)\n\nconst ID_PREFIX = ':file-upload'\n\nexport const FileUpload = ({\n asChild: _asChild = false,\n children,\n defaultValue = [],\n value: controlledValue,\n onFileAccept,\n onFileReject,\n onFileChange,\n multiple = true,\n accept,\n maxFiles,\n maxFileSize,\n minFileSize,\n disabled: disabledProp = false,\n readOnly: readOnlyProp = false,\n locale,\n}: FileUploadProps) => {\n const field = useFormFieldControl()\n\n // Generate unique ID if none provided by FormField\n const internalId = useId()\n const inputId = field.id || `${ID_PREFIX}-${internalId}`\n\n // Use FormField name or undefined (no hardcoded fallback)\n const inputName = field.name\n\n const inputRef = useRef<HTMLInputElement>(null)\n const triggerRef = useRef<HTMLElement>(null)\n const dropzoneRef = useRef<HTMLElement>(null)\n const deleteButtonRefs = useRef<HTMLButtonElement[]>([])\n const rejectedFileDeleteButtonRefs = useRef<HTMLButtonElement[]>([])\n\n // Merge FormField props with component props (FormField takes precedence)\n const disabled = field.disabled ?? disabledProp\n const readOnly = field.readOnly ?? readOnlyProp\n\n // Use the file upload state hook to manage all file operations\n const {\n files,\n rejectedFiles,\n addFiles,\n removeFile,\n removeRejectedFile,\n clearFiles: clearFilesFromHook,\n clearRejectedFiles,\n maxFilesReached,\n } = useFileUploadState({\n defaultValue,\n value: controlledValue,\n onFileAccept,\n onFileReject,\n onFileChange,\n multiple,\n accept,\n maxFiles,\n maxFileSize,\n minFileSize,\n disabled,\n readOnly,\n locale,\n })\n\n // Override clearFiles to also clear deleteButtonRefs\n const clearFiles = () => {\n clearFilesFromHook()\n deleteButtonRefs.current = []\n }\n\n // Override clearRejectedFiles to also clear rejectedFileDeleteButtonRefs\n const clearRejectedFilesWithRefs = () => {\n clearRejectedFiles()\n rejectedFileDeleteButtonRefs.current = []\n }\n\n return (\n <FileUploadContext.Provider\n value={{\n inputRef,\n files,\n rejectedFiles,\n addFiles,\n removeFile,\n removeRejectedFile,\n clearFiles,\n clearRejectedFiles: clearRejectedFilesWithRefs,\n triggerRef,\n dropzoneRef,\n deleteButtonRefs,\n rejectedFileDeleteButtonRefs,\n multiple,\n maxFiles,\n maxFilesReached,\n disabled,\n readOnly,\n locale:\n locale ||\n (typeof navigator !== 'undefined' && navigator.language ? navigator.language : 'en'),\n description: field.description,\n isInvalid: field.isInvalid,\n isRequired: field.isRequired,\n }}\n >\n {/* <Comp data-spark-component=\"file-upload\" className={cx('relative', className)} {...props}> */}\n <div className=\"relative\">\n {children}\n <input\n ref={inputRef}\n type=\"file\"\n tabIndex={-1}\n id={inputId}\n multiple={multiple}\n name={inputName}\n accept={accept}\n disabled={disabled}\n readOnly={readOnly && !disabled}\n required={field.isRequired}\n aria-invalid={field.isInvalid}\n aria-describedby={field.description}\n // Hardcoded aria-label is acceptable here because:\n // 1. The input is visually hidden (sr-only) and not keyboard accessible (tabIndex={-1})\n // 2. Users never interact directly with this input - they interact via Trigger/Dropzone\n // 3. Screen readers will announce the Trigger/Dropzone content (which can be translated) instead\n // 4. This is only used as a fallback when no FormField.Label is present\n aria-label={!field.labelId ? 'Upload files' : undefined}\n className=\"sr-only\"\n onChange={e => {\n if (e.target.files && !disabled && !readOnly) {\n addFiles(Array.from(e.target.files))\n // Reset input value to allow selecting the same file again\n try {\n e.target.value = ''\n } catch {\n // Ignore error if value is read-only (e.g., in tests)\n }\n }\n }}\n />\n </div>\n {/* </Comp> */}\n </FileUploadContext.Provider>\n )\n}\n\nFileUpload.displayName = 'FileUpload'\n\nexport const useFileUploadContext = () => {\n const context = useContext(FileUploadContext)\n\n if (!context) {\n throw Error('useFileUploadContext must be used within a FileUpload provider')\n }\n\n return context\n}\n","import { Close } from '@spark-ui/icons/Close'\nimport { cx } from 'class-variance-authority'\nimport { useRef } from 'react'\n\nimport { Icon } from '../icon'\nimport { IconButton } from '../icon-button'\nimport { useFileUploadContext } from './FileUpload'\nimport { findFocusableElement } from './utils'\n\nexport interface FileUploadItemDeleteTriggerProps extends React.ComponentProps<typeof IconButton> {\n /**\n * The file to delete\n */\n file: File\n}\n\nexport const ItemDeleteTrigger = ({\n className,\n file,\n onClick,\n ...props\n}: FileUploadItemDeleteTriggerProps) => {\n const {\n removeFile,\n triggerRef,\n dropzoneRef,\n deleteButtonRefs,\n inputRef,\n disabled,\n readOnly,\n files,\n } = useFileUploadContext()\n const buttonRef = useRef<HTMLButtonElement>(null)\n\n // Find the index of the file using name + size (consistent with duplicate detection logic)\n const fileIndex = files.findIndex(f => f.name === file.name && f.size === file.size)\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n // Don't allow removing files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n // Remove the file\n removeFile(fileIndex)\n\n // Handle focus after removal\n requestAnimationFrame(() => {\n // Get all remaining delete buttons from the refs array\n const remainingButtons = deleteButtonRefs.current.filter(Boolean)\n\n if (remainingButtons.length > 0) {\n // Find the button that should receive focus\n // We want to focus on the button that takes the same position as the removed one\n // If that position doesn't exist (we removed the last item), focus on the previous one\n const targetIndex = Math.min(fileIndex, remainingButtons.length - 1)\n const nextButton = remainingButtons[targetIndex]\n\n if (nextButton) {\n nextButton.focus()\n }\n } else {\n // No more files, find a focusable element (trigger, dropzone, or input as last resort)\n const focusTarget = findFocusableElement(\n [triggerRef.current, dropzoneRef.current],\n inputRef\n )\n if (focusTarget) {\n focusTarget.focus()\n }\n }\n })\n\n onClick?.(e)\n }\n\n const setRef = (node: HTMLButtonElement | null) => {\n buttonRef.current = node\n if (node) {\n // Ensure the array is large enough\n while (deleteButtonRefs.current.length <= fileIndex) {\n deleteButtonRefs.current.push(null as any)\n }\n deleteButtonRefs.current[fileIndex] = node\n } else {\n // Remove the ref when component unmounts\n if (deleteButtonRefs.current[fileIndex]) {\n deleteButtonRefs.current[fileIndex] = null as any\n }\n }\n }\n\n return (\n <IconButton\n ref={setRef}\n data-spark-component=\"file-upload-item-delete-trigger\"\n className={cx(className)}\n onClick={handleClick}\n disabled={disabled || readOnly}\n size=\"sm\"\n design=\"contrast\"\n intent=\"surface\"\n {...props}\n >\n <Icon size=\"sm\">\n <Close />\n </Icon>\n </IconButton>\n )\n}\n\nItemDeleteTrigger.displayName = 'FileUpload.ItemDeleteTrigger'\n","import { cx } from 'class-variance-authority'\nimport { ComponentPropsWithoutRef, Ref, useCallback, useEffect, useState } from 'react'\n\nimport { Icon } from '../icon'\nimport { Progress } from '../progress'\nimport { useFileUploadContext } from './FileUpload'\nimport { ItemDeleteTrigger } from './FileUploadItemDeleteTrigger'\nimport { formatFileSize, getFileIcon } from './utils'\n\nexport interface FileUploadAcceptedFileProps extends ComponentPropsWithoutRef<'li'> {\n ref?: Ref<HTMLLIElement>\n /**\n * The file to display\n */\n file: File\n /**\n * Upload progress value (0-100). When provided, displays a progress bar at the bottom of the file item.\n */\n uploadProgress?: number\n /**\n * Accessible label for the delete button\n */\n deleteButtonAriaLabel: string\n /**\n * Accessible label for the progress bar. Required when uploadProgress is provided.\n */\n progressAriaLabel?: string\n className?: string\n}\n\nexport const AcceptedFile = ({\n className,\n file,\n uploadProgress,\n deleteButtonAriaLabel,\n progressAriaLabel,\n ...props\n}: FileUploadAcceptedFileProps) => {\n const { locale } = useFileUploadContext()\n const [showProgress, setShowProgress] = useState(uploadProgress !== undefined)\n\n useEffect(() => {\n // Reset showProgress when uploadProgress becomes defined\n if (uploadProgress !== undefined) {\n setShowProgress(true)\n } else {\n setShowProgress(false)\n }\n }, [uploadProgress])\n\n const handleProgressComplete = useCallback(() => {\n setShowProgress(false)\n }, [])\n\n return (\n <li\n data-spark-component=\"file-upload-accepted-file\"\n className={cx(\n 'relative',\n 'default:bg-surface default:border-sm default:border-outline default:p-md default:rounded-md',\n 'gap-md flex items-center justify-between default:w-full',\n className\n )}\n {...props}\n >\n <div className=\"size-sz-36 bg-support-container flex items-center justify-center rounded-md\">\n <Icon size=\"md\">{getFileIcon(file)}</Icon>\n </div>\n\n <div className=\"gap-md relative flex min-w-0 flex-1 flex-row items-center justify-between self-stretch\">\n <p className=\"text-body-2 truncate font-medium\">{file.name}</p>\n <p className=\"text-caption opacity-dim-1\">{formatFileSize(file.size, locale)}</p>\n {showProgress && uploadProgress !== undefined && (\n <div className=\"absolute bottom-0 left-0 w-full\">\n <Progress\n value={uploadProgress}\n max={100}\n aria-label={progressAriaLabel}\n onComplete={handleProgressComplete}\n />\n </div>\n )}\n </div>\n\n <ItemDeleteTrigger aria-label={deleteButtonAriaLabel} file={file} />\n </li>\n )\n}\n\nAcceptedFile.displayName = 'FileUpload.AcceptedFile'\n","import { ReactNode } from 'react'\n\nimport { type RejectedFile, useFileUploadContext } from './FileUpload'\nimport { formatFileSize } from './utils'\n\nexport interface FileUploadContextProps {\n /**\n * Render prop that receives acceptedFiles, rejectedFiles, formatFileSize, and locale\n */\n children: (props: {\n acceptedFiles: File[]\n rejectedFiles: RejectedFile[]\n formatFileSize: (bytes: number, locale?: string) => string\n locale?: string\n }) => ReactNode\n}\n\nexport const Context = ({ children }: FileUploadContextProps) => {\n const { files = [], rejectedFiles = [], locale } = useFileUploadContext()\n\n return (\n <>\n {children({\n acceptedFiles: files,\n rejectedFiles,\n formatFileSize,\n locale,\n })}\n </>\n )\n}\n\nContext.displayName = 'FileUpload.Context'\n","import { cx } from 'class-variance-authority'\nimport { createContext, useContext, useRef } from 'react'\n\nimport { useFileUploadContext } from './FileUpload'\n\n// Context to signal that we're inside a Dropzone\nexport const DropzoneContext = createContext<boolean>(false)\n\nexport const useDropzoneContext = () => useContext(DropzoneContext)\n\nexport function Dropzone({\n children,\n className,\n unstyled = false,\n}: {\n children?: React.ReactNode\n className?: string\n unstyled?: boolean\n}) {\n const ctx = useFileUploadContext()\n const dropzoneRef = useRef<HTMLDivElement>(null)\n\n if (!ctx) throw new Error('FileUploadDropzone must be used inside <FileUpload>')\n\n const handleDrop = (e: React.DragEvent) => {\n e.preventDefault()\n e.stopPropagation()\n e.currentTarget.setAttribute('data-drag-over', 'false')\n\n // Don't allow dropping files when disabled or readOnly\n if (ctx.disabled || ctx.readOnly) {\n return\n }\n\n const files = e.dataTransfer.files\n\n // Add files to the context\n // Convert to array - handle both FileList and array (for tests)\n let filesArray: File[] = []\n if (files) {\n filesArray = Array.isArray(files) ? [...files] : Array.from(files)\n }\n\n if (filesArray.length > 0) {\n ctx.addFiles(filesArray)\n }\n }\n\n const handleClick = () => {\n if (!ctx.disabled && !ctx.readOnly) {\n ctx.inputRef.current?.click()\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n if (!ctx.disabled && !ctx.readOnly) {\n ctx.inputRef.current?.click()\n }\n }\n }\n\n const isDisabled = ctx.disabled || ctx.readOnly\n\n return (\n <DropzoneContext.Provider value={true}>\n <div\n ref={node => {\n dropzoneRef.current = node\n if (ctx.dropzoneRef) {\n ctx.dropzoneRef.current = node\n }\n }}\n role=\"button\"\n tabIndex={isDisabled ? -1 : 0}\n aria-disabled={ctx.disabled ? true : undefined}\n aria-describedby={ctx.description}\n aria-invalid={ctx.isInvalid}\n aria-required={ctx.isRequired}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onDrop={handleDrop}\n onDragOver={e => {\n e.preventDefault()\n }}\n className={\n unstyled\n ? className\n : cx(\n 'default:bg-surface default:border-sm default:border-outline default:relative default:rounded-lg default:border-dashed',\n 'gap-lg flex flex-col items-center justify-center text-center',\n 'default:p-xl',\n 'transition-colors duration-200',\n !isDisabled && 'default:hover:bg-surface-hovered',\n 'data-[drag-over=true]:border-outline-high data-[drag-over=true]:bg-surface-hovered data-[drag-over=true]:border-solid',\n // Disabled: more visually disabled (opacity + cursor)\n ctx.disabled && 'cursor-not-allowed opacity-50',\n // ReadOnly: less visually disabled (just cursor, no opacity)\n ctx.readOnly && !ctx.disabled && 'cursor-default',\n className\n )\n }\n onDragEnter={e => {\n if (!isDisabled) {\n e.currentTarget.setAttribute('data-drag-over', 'true')\n }\n }}\n onDragLeave={e => {\n e.currentTarget.setAttribute('data-drag-over', 'false')\n }}\n >\n {children}\n </div>\n </DropzoneContext.Provider>\n )\n}\n\nDropzone.displayName = 'FileUploadDropzone'\n","import { cx } from 'class-variance-authority'\nimport { ComponentPropsWithoutRef, Ref, useEffect, useState } from 'react'\n\nexport interface FileUploadPreviewImageProps extends ComponentPropsWithoutRef<'div'> {\n ref?: Ref<HTMLDivElement>\n className?: string\n /**\n * The file to preview\n */\n file: File\n /**\n * Fallback content when file is not an image or preview fails\n */\n fallback?: React.ReactNode\n}\n\nexport const PreviewImage = ({\n className,\n file,\n fallback = '📄',\n ...props\n}: FileUploadPreviewImageProps) => {\n const [imageError, setImageError] = useState(false)\n const [imageLoaded, setImageLoaded] = useState(false)\n const [imageUrl, setImageUrl] = useState<string | null>(null)\n\n const isImage = file.type.startsWith('image/')\n\n // Create and clean up the object URL when file changes\n useEffect(() => {\n if (!isImage) {\n setImageUrl(null)\n return\n }\n\n const url = URL.createObjectURL(file)\n setImageUrl(url)\n\n // Clean up: only revoke when the file actually changes or component unmounts\n return () => {\n URL.revokeObjectURL(url)\n }\n }, [file, isImage])\n\n if (!isImage || imageError) {\n return (\n <div\n data-spark-component=\"file-upload-preview-image\"\n className={cx(\n 'bg-neutral-container flex items-center justify-center rounded-md',\n className\n )}\n {...props}\n >\n {fallback}\n </div>\n )\n }\n\n return (\n <div\n data-spark-component=\"file-upload-preview-image\"\n className={cx('bg-neutral-container overflow-hidden', className)}\n {...props}\n >\n <img\n src={imageUrl!}\n alt={file.name}\n className={cx('size-full object-cover', !imageLoaded && 'opacity-0')}\n onLoad={() => setImageLoaded(true)}\n onError={() => setImageError(true)}\n />\n {!imageLoaded && (\n <div className=\"absolute inset-0 flex items-center justify-center\">{fallback}</div>\n )}\n </div>\n )\n}\n\nPreviewImage.displayName = 'FileUpload.PreviewImage'\n","import { Close } from '@spark-ui/icons/Close'\nimport { cx } from 'class-variance-authority'\nimport { useRef } from 'react'\n\nimport { Icon } from '../icon'\nimport { IconButton } from '../icon-button'\nimport { type RejectedFile as RejectedFileType, useFileUploadContext } from './FileUpload'\nimport { findFocusableElement } from './utils'\n\nexport interface FileUploadRejectedFileDeleteTriggerProps extends React.ComponentProps<\n typeof IconButton\n> {\n /**\n * The rejected file to remove\n */\n rejectedFile: RejectedFileType\n}\n\nexport const RejectedFileDeleteTrigger = ({\n className,\n rejectedFile,\n onClick,\n ...props\n}: FileUploadRejectedFileDeleteTriggerProps) => {\n const {\n removeRejectedFile,\n triggerRef,\n dropzoneRef,\n rejectedFileDeleteButtonRefs,\n inputRef,\n disabled,\n readOnly,\n rejectedFiles,\n } = useFileUploadContext()\n const buttonRef = useRef<HTMLButtonElement>(null)\n\n // Find the index of the rejected file using name + size (consistent with duplicate detection logic)\n const rejectedFileIndex = rejectedFiles.findIndex(\n rf => rf.file.name === rejectedFile.file.name && rf.file.size === rejectedFile.file.size\n )\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n // Don't allow removing rejected files when disabled or readOnly\n if (disabled || readOnly) {\n return\n }\n\n // Remove the rejected file\n removeRejectedFile(rejectedFileIndex)\n\n // Handle focus after removal\n requestAnimationFrame(() => {\n // Get all remaining rejected file delete buttons from the refs array\n const remainingButtons = rejectedFileDeleteButtonRefs.current.filter(Boolean)\n\n if (remainingButtons.length > 0) {\n // Find the button that should receive focus\n // We want to focus on the button that takes the same position as the removed one\n // If that position doesn't exist (we removed the last item), focus on the previous one\n const targetIndex = Math.min(rejectedFileIndex, remainingButtons.length - 1)\n const nextButton = remainingButtons[targetIndex]\n\n if (nextButton) {\n nextButton.focus()\n }\n } else {\n // No more rejected files, find a focusable element (trigger, dropzone, or input as last resort)\n const focusTarget = findFocusableElement(\n [triggerRef.current, dropzoneRef.current],\n inputRef\n )\n if (focusTarget) {\n focusTarget.focus()\n }\n }\n })\n\n onClick?.(e)\n }\n\n const setRef = (node: HTMLButtonElement | null) => {\n buttonRef.current = node\n if (node) {\n // Ensure the array is large enough\n while (rejectedFileDeleteButtonRefs.current.length <= rejectedFileIndex) {\n rejectedFileDeleteButtonRefs.current.push(null as any)\n }\n rejectedFileDeleteButtonRefs.current[rejectedFileIndex] = node\n } else {\n // Remove the ref when component unmounts\n if (rejectedFileDeleteButtonRefs.current[rejectedFileIndex]) {\n rejectedFileDeleteButtonRefs.current[rejectedFileIndex] = null as any\n }\n }\n }\n\n return (\n <IconButton\n ref={setRef}\n data-spark-component=\"file-upload-rejected-file-delete-trigger\"\n className={cx(className)}\n onClick={handleClick}\n disabled={disabled || readOnly}\n size=\"sm\"\n design=\"contrast\"\n intent=\"surface\"\n {...props}\n >\n <Icon size=\"sm\">\n <Close />\n </Icon>\n </IconButton>\n )\n}\n\nRejectedFileDeleteTrigger.displayName = 'FileUpload.RejectedFileDeleteTrigger'\n","import { WarningOutline } from '@spark-ui/icons/WarningOutline'\nimport { cx } from 'class-variance-authority'\nimport { ComponentPropsWithoutRef, Ref } from 'react'\n\nimport { Icon } from '../icon'\nimport {\n type FileUploadFileError,\n type RejectedFile as RejectedFileType,\n useFileUploadContext,\n} from './FileUpload'\nimport { RejectedFileDeleteTrigger } from './FileUploadRejectedFileDeleteTrigger'\nimport { formatFileSize } from './utils'\n\nexport interface FileUploadRejectedFileProps extends ComponentPropsWithoutRef<'li'> {\n ref?: Ref<HTMLLIElement>\n /**\n * The rejected file to display\n */\n rejectedFile: RejectedFileType\n /**\n * Function to render the error message for each error code\n * @param error - The error code\n * @returns The error message to display\n */\n renderError: (error: FileUploadFileError) => string\n /**\n * Accessible label for the delete button\n */\n deleteButtonAriaLabel: string\n className?: string\n}\n\nexport const RejectedFile = ({\n className,\n rejectedFile,\n renderError,\n deleteButtonAriaLabel,\n ...props\n}: FileUploadRejectedFileProps) => {\n const { locale } = useFileUploadContext()\n\n return (\n <li\n data-spark-component=\"file-upload-rejected-file\"\n className={cx(\n 'relative',\n 'default:bg-surface default:border-sm default:border-outline default:p-md default:rounded-md',\n 'gap-md flex items-center justify-between default:w-full',\n 'border-error border-md',\n className\n )}\n {...props}\n >\n <div className=\"size-sz-36 bg-error-container flex items-center justify-center rounded-md\">\n <Icon size=\"md\" className=\"text-error\">\n <WarningOutline />\n </Icon>\n </div>\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"gap-md flex flex-col\">\n <div className=\"gap-md flex flex-row items-center justify-between\">\n <p className=\"text-body-2 truncate font-medium\">{rejectedFile.file.name}</p>\n <p className=\"text-caption opacity-dim-1\">\n {formatFileSize(rejectedFile.file.size, locale)}\n </p>\n </div>\n <div className=\"gap-xs flex flex-col\">\n {rejectedFile.errors.map((error, errorIndex) => (\n <div key={errorIndex} className=\"text-caption text-error\" data-error-code={error}>\n {renderError(error)}\n </div>\n ))}\n </div>\n </div>\n </div>\n\n <RejectedFileDeleteTrigger aria-label={deleteButtonAriaLabel} rejectedFile={rejectedFile} />\n </li>\n )\n}\n\nRejectedFile.displayName = 'FileUpload.RejectedFile'\n","import { cx } from 'class-variance-authority'\nimport React, { ReactNode, Ref } from 'react'\n\nimport { Button, type ButtonProps } from '../button'\nimport { buttonStyles } from '../button/Button.styles'\nimport { Slot } from '../slot'\nimport { useFileUploadContext } from './FileUpload'\nimport { useDropzoneContext } from './FileUploadDropzone'\n\nexport interface FileUploadTriggerProps extends Omit<ButtonProps, 'children' | 'disabled'> {\n ref?: Ref<HTMLButtonElement>\n className?: string\n children: ReactNode\n unstyled?: boolean\n}\n\nexport const Trigger = ({\n className,\n children,\n asChild = false,\n unstyled = false,\n design = 'filled',\n intent = 'support',\n size = 'md',\n shape = 'rounded',\n ref,\n ...props\n}: FileUploadTriggerProps) => {\n const { inputRef, triggerRef, disabled, readOnly, description, isInvalid, isRequired } =\n useFileUploadContext()\n const isInsideDropzone = useDropzoneContext()\n\n const handleClick = (e: React.MouseEvent) => {\n e.stopPropagation()\n e.preventDefault()\n if (!disabled && !readOnly) {\n inputRef.current?.click()\n }\n }\n\n // Shared ref forwarding logic\n const handleRef = (node: HTMLElement | null) => {\n // Forward ref to both the context ref and the user ref\n if (triggerRef) {\n triggerRef.current = node\n }\n if (ref) {\n if (typeof ref === 'function') {\n ref(node as HTMLButtonElement)\n } else {\n ref.current = node as HTMLButtonElement\n }\n }\n }\n\n // Determine component and props based on context\n // If inside a Dropzone, render as a non-interactive span\n // The Dropzone handles all interactions\n let Component: React.ElementType\n let componentProps: Record<string, unknown>\n\n if (isInsideDropzone) {\n // Don't use asChild when inside Dropzone - we always want a span\n Component = 'span'\n const spanStyles = unstyled\n ? className\n : buttonStyles({\n design,\n intent,\n size,\n shape,\n disabled: disabled || readOnly,\n className,\n })\n\n componentProps = {\n ref: handleRef,\n 'data-spark-component': 'file-upload-trigger',\n className: spanStyles,\n // No onClick, no role, no tabIndex - Dropzone handles interaction\n // No aria attributes here - they're on the Dropzone\n }\n } else {\n // Normal behavior when not inside Dropzone\n const buttonComponent = unstyled ? 'button' : Button\n Component = asChild ? Slot : buttonComponent\n\n componentProps = {\n ref: handleRef,\n type: 'button',\n design,\n intent,\n size,\n shape,\n 'data-spark-component': 'file-upload-trigger',\n className: cx(className),\n disabled: disabled || readOnly,\n onClick: handleClick,\n 'aria-describedby': description,\n 'aria-invalid': isInvalid,\n 'aria-required': isRequired,\n ...props,\n }\n }\n\n return <Component {...componentProps}>{children}</Component>\n}\n\nTrigger.displayName = 'FileUpload.Trigger'\n","import { FILE_UPLOAD_ERRORS } from './constants'\nimport {\n type FileAcceptDetails,\n type FileChangeDetails,\n type FileRejectDetails,\n FileUpload as Root,\n type FileUploadFileError,\n type RejectedFile,\n} from './FileUpload'\nimport { AcceptedFile } from './FileUploadAcceptedFile'\nimport { Context } from './FileUploadContext'\nimport { Dropzone } from './FileUploadDropzone'\nimport { ItemDeleteTrigger } from './FileUploadItemDeleteTrigger'\nimport { PreviewImage } from './FileUploadPreviewImage'\nimport { RejectedFile as RejectedFileComponent } from './FileUploadRejectedFile'\nimport { RejectedFileDeleteTrigger } from './FileUploadRejectedFileDeleteTrigger'\nimport { Trigger } from './FileUploadTrigger'\n\nexport type {\n FileAcceptDetails,\n FileChangeDetails,\n FileRejectDetails,\n RejectedFile,\n FileUploadFileError,\n}\n\nexport { FILE_UPLOAD_ERRORS }\n\n/**\n * A component that allows users to select and upload files from their device.\n */\nexport const FileUpload: typeof Root & {\n Trigger: typeof Trigger\n Dropzone: typeof Dropzone\n Context: typeof Context\n ItemDeleteTrigger: typeof ItemDeleteTrigger\n PreviewImage: typeof PreviewImage\n AcceptedFile: typeof AcceptedFile\n RejectedFile: typeof RejectedFileComponent\n RejectedFileDeleteTrigger: typeof RejectedFileDeleteTrigger\n} = Object.assign(Root, {\n // Main input components\n Trigger,\n Dropzone,\n // Context components\n Context,\n AcceptedFile,\n RejectedFile: RejectedFileComponent,\n // Helpers for custom renders\n PreviewImage,\n ItemDeleteTrigger,\n RejectedFileDeleteTrigger,\n})\n\nFileUpload.displayName = 'FileUpload'\nTrigger.displayName = 'FileUpload.Trigger'\nDropzone.displayName = 'FileUpload.Dropzone'\nContext.displayName = 'FileUpload.Context'\nItemDeleteTrigger.displayName = 'FileUpload.ItemDeleteTrigger'\nPreviewImage.displayName = 'FileUpload.PreviewImage'\nAcceptedFile.displayName = 'FileUpload.AcceptedFile'\nRejectedFileComponent.displayName = 'FileUpload.RejectedFile'\nRejectedFileDeleteTrigger.displayName = 'FileUpload.RejectedFileDeleteTrigger'\n"],"mappings":"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"}