@mortenbrudvik/waveui 0.9.0 → 0.11.0

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 (264) hide show
  1. package/README.md +758 -152
  2. package/dist/components/button/Toolbar.cjs +1 -1
  3. package/dist/components/button/Toolbar.cjs.map +1 -1
  4. package/dist/components/button/Toolbar.mjs +25 -30
  5. package/dist/components/button/Toolbar.mjs.map +1 -1
  6. package/dist/components/button/Toolbar.navigation.cjs +2 -0
  7. package/dist/components/button/Toolbar.navigation.cjs.map +1 -0
  8. package/dist/components/button/Toolbar.navigation.mjs +17 -0
  9. package/dist/components/button/Toolbar.navigation.mjs.map +1 -0
  10. package/dist/components/data-display/Avatar.cjs +1 -1
  11. package/dist/components/data-display/Avatar.cjs.map +1 -1
  12. package/dist/components/data-display/Avatar.context.cjs +2 -0
  13. package/dist/components/data-display/Avatar.context.cjs.map +1 -0
  14. package/dist/components/data-display/Avatar.context.mjs +9 -0
  15. package/dist/components/data-display/Avatar.context.mjs.map +1 -0
  16. package/dist/components/data-display/Avatar.mjs +66 -61
  17. package/dist/components/data-display/Avatar.mjs.map +1 -1
  18. package/dist/components/data-display/InteractionTag.cjs +2 -0
  19. package/dist/components/data-display/InteractionTag.cjs.map +1 -0
  20. package/dist/components/data-display/InteractionTag.mjs +234 -0
  21. package/dist/components/data-display/InteractionTag.mjs.map +1 -0
  22. package/dist/components/data-display/Tag.cjs +1 -1
  23. package/dist/components/data-display/Tag.cjs.map +1 -1
  24. package/dist/components/data-display/Tag.mjs +222 -90
  25. package/dist/components/data-display/Tag.mjs.map +1 -1
  26. package/dist/components/data-display/TagGroup.cjs +2 -0
  27. package/dist/components/data-display/TagGroup.cjs.map +1 -0
  28. package/dist/components/data-display/TagGroup.context.cjs +2 -0
  29. package/dist/components/data-display/TagGroup.context.cjs.map +1 -0
  30. package/dist/components/data-display/TagGroup.context.mjs +113 -0
  31. package/dist/components/data-display/TagGroup.context.mjs.map +1 -0
  32. package/dist/components/data-display/TagGroup.mjs +149 -0
  33. package/dist/components/data-display/TagGroup.mjs.map +1 -0
  34. package/dist/components/data-display/tagStyles.cjs +2 -0
  35. package/dist/components/data-display/tagStyles.cjs.map +1 -0
  36. package/dist/components/data-display/tagStyles.mjs +108 -0
  37. package/dist/components/data-display/tagStyles.mjs.map +1 -0
  38. package/dist/components/feedback/Toaster.cjs +1 -1
  39. package/dist/components/feedback/Toaster.cjs.map +1 -1
  40. package/dist/components/feedback/Toaster.mjs +213 -181
  41. package/dist/components/feedback/Toaster.mjs.map +1 -1
  42. package/dist/components/input/Calendar.cjs +2 -0
  43. package/dist/components/input/Calendar.cjs.map +1 -0
  44. package/dist/components/input/Calendar.mjs +352 -0
  45. package/dist/components/input/Calendar.mjs.map +1 -0
  46. package/dist/components/input/Calendar.views.cjs +2 -0
  47. package/dist/components/input/Calendar.views.cjs.map +1 -0
  48. package/dist/components/input/Calendar.views.mjs +194 -0
  49. package/dist/components/input/Calendar.views.mjs.map +1 -0
  50. package/dist/components/input/ColorArea.cjs +1 -1
  51. package/dist/components/input/ColorArea.cjs.map +1 -1
  52. package/dist/components/input/ColorArea.mjs +18 -18
  53. package/dist/components/input/ColorArea.mjs.map +1 -1
  54. package/dist/components/input/Combobox.cjs +1 -1
  55. package/dist/components/input/Combobox.cjs.map +1 -1
  56. package/dist/components/input/Combobox.expand.cjs +1 -1
  57. package/dist/components/input/Combobox.expand.cjs.map +1 -1
  58. package/dist/components/input/Combobox.expand.mjs +7 -7
  59. package/dist/components/input/Combobox.expand.mjs.map +1 -1
  60. package/dist/components/input/Combobox.mjs +232 -156
  61. package/dist/components/input/Combobox.mjs.map +1 -1
  62. package/dist/components/input/Combobox.text.cjs +2 -0
  63. package/dist/components/input/Combobox.text.cjs.map +1 -0
  64. package/dist/components/input/Combobox.text.mjs +24 -0
  65. package/dist/components/input/Combobox.text.mjs.map +1 -0
  66. package/dist/components/input/DatePicker.cjs +1 -1
  67. package/dist/components/input/DatePicker.cjs.map +1 -1
  68. package/dist/components/input/DatePicker.mjs +203 -288
  69. package/dist/components/input/DatePicker.mjs.map +1 -1
  70. package/dist/components/input/Dropdown.cjs +1 -1
  71. package/dist/components/input/Dropdown.cjs.map +1 -1
  72. package/dist/components/input/Dropdown.mjs +135 -109
  73. package/dist/components/input/Dropdown.mjs.map +1 -1
  74. package/dist/components/input/Listbox.cjs +2 -0
  75. package/dist/components/input/Listbox.cjs.map +1 -0
  76. package/dist/components/input/Listbox.mjs +134 -0
  77. package/dist/components/input/Listbox.mjs.map +1 -0
  78. package/dist/components/input/Option.cjs +1 -1
  79. package/dist/components/input/Option.cjs.map +1 -1
  80. package/dist/components/input/Option.mjs +163 -97
  81. package/dist/components/input/Option.mjs.map +1 -1
  82. package/dist/components/input/SwatchPicker.cjs +1 -1
  83. package/dist/components/input/SwatchPicker.cjs.map +1 -1
  84. package/dist/components/input/SwatchPicker.context.cjs +2 -0
  85. package/dist/components/input/SwatchPicker.context.cjs.map +1 -0
  86. package/dist/components/input/SwatchPicker.context.mjs +35 -0
  87. package/dist/components/input/SwatchPicker.context.mjs.map +1 -0
  88. package/dist/components/input/SwatchPicker.mjs +131 -103
  89. package/dist/components/input/SwatchPicker.mjs.map +1 -1
  90. package/dist/components/input/SwatchPicker.swatches.cjs +2 -0
  91. package/dist/components/input/SwatchPicker.swatches.cjs.map +1 -0
  92. package/dist/components/input/SwatchPicker.swatches.mjs +207 -0
  93. package/dist/components/input/SwatchPicker.swatches.mjs.map +1 -0
  94. package/dist/components/input/TagPicker.cjs +1 -1
  95. package/dist/components/input/TagPicker.cjs.map +1 -1
  96. package/dist/components/input/TagPicker.context.cjs +2 -0
  97. package/dist/components/input/TagPicker.context.cjs.map +1 -0
  98. package/dist/components/input/TagPicker.context.mjs +20 -0
  99. package/dist/components/input/TagPicker.context.mjs.map +1 -0
  100. package/dist/components/input/TagPicker.create.cjs +2 -0
  101. package/dist/components/input/TagPicker.create.cjs.map +1 -0
  102. package/dist/components/input/TagPicker.create.mjs +24 -0
  103. package/dist/components/input/TagPicker.create.mjs.map +1 -0
  104. package/dist/components/input/TagPicker.mjs +13 -274
  105. package/dist/components/input/TagPicker.mjs.map +1 -1
  106. package/dist/components/input/TagPicker.option.cjs +2 -0
  107. package/dist/components/input/TagPicker.option.cjs.map +1 -0
  108. package/dist/components/input/TagPicker.option.mjs +45 -0
  109. package/dist/components/input/TagPicker.option.mjs.map +1 -0
  110. package/dist/components/input/TagPicker.overflow.cjs +2 -0
  111. package/dist/components/input/TagPicker.overflow.cjs.map +1 -0
  112. package/dist/components/input/TagPicker.overflow.mjs +153 -0
  113. package/dist/components/input/TagPicker.overflow.mjs.map +1 -0
  114. package/dist/components/input/TagPicker.parts.cjs +2 -0
  115. package/dist/components/input/TagPicker.parts.cjs.map +1 -0
  116. package/dist/components/input/TagPicker.parts.mjs +425 -0
  117. package/dist/components/input/TagPicker.parts.mjs.map +1 -0
  118. package/dist/components/input/TagPicker.root.cjs +2 -0
  119. package/dist/components/input/TagPicker.root.cjs.map +1 -0
  120. package/dist/components/input/TagPicker.root.mjs +416 -0
  121. package/dist/components/input/TagPicker.root.mjs.map +1 -0
  122. package/dist/components/input/TimePicker.cjs +1 -1
  123. package/dist/components/input/TimePicker.cjs.map +1 -1
  124. package/dist/components/input/TimePicker.mjs +77 -77
  125. package/dist/components/input/TimePicker.mjs.map +1 -1
  126. package/dist/components/input/dateUtils.cjs +1 -1
  127. package/dist/components/input/dateUtils.cjs.map +1 -1
  128. package/dist/components/input/dateUtils.mjs +114 -96
  129. package/dist/components/input/dateUtils.mjs.map +1 -1
  130. package/dist/components/input/pickerLabels.cjs +2 -0
  131. package/dist/components/input/pickerLabels.cjs.map +1 -0
  132. package/dist/components/input/pickerLabels.mjs +16 -0
  133. package/dist/components/input/pickerLabels.mjs.map +1 -0
  134. package/dist/components/input/pickerValues.cjs +2 -0
  135. package/dist/components/input/pickerValues.cjs.map +1 -0
  136. package/dist/components/input/pickerValues.mjs +24 -0
  137. package/dist/components/input/pickerValues.mjs.map +1 -0
  138. package/dist/components/layout/Accordion.cjs.map +1 -1
  139. package/dist/components/layout/Accordion.mjs.map +1 -1
  140. package/dist/components/navigation/Hamburger.cjs +2 -0
  141. package/dist/components/navigation/Hamburger.cjs.map +1 -0
  142. package/dist/components/navigation/Hamburger.mjs +54 -0
  143. package/dist/components/navigation/Hamburger.mjs.map +1 -0
  144. package/dist/components/navigation/Nav.cjs +1 -1
  145. package/dist/components/navigation/Nav.cjs.map +1 -1
  146. package/dist/components/navigation/Nav.context.cjs +2 -0
  147. package/dist/components/navigation/Nav.context.cjs.map +1 -0
  148. package/dist/components/navigation/Nav.context.mjs +137 -0
  149. package/dist/components/navigation/Nav.context.mjs.map +1 -0
  150. package/dist/components/navigation/Nav.mjs +151 -258
  151. package/dist/components/navigation/Nav.mjs.map +1 -1
  152. package/dist/components/navigation/Nav.parts.cjs +2 -0
  153. package/dist/components/navigation/Nav.parts.cjs.map +1 -0
  154. package/dist/components/navigation/Nav.parts.mjs +77 -0
  155. package/dist/components/navigation/Nav.parts.mjs.map +1 -0
  156. package/dist/components/navigation/Nav.root.cjs +2 -0
  157. package/dist/components/navigation/Nav.root.cjs.map +1 -0
  158. package/dist/components/navigation/Nav.root.mjs +78 -0
  159. package/dist/components/navigation/Nav.root.mjs.map +1 -0
  160. package/dist/components/navigation/NavDrawer.cjs +2 -0
  161. package/dist/components/navigation/NavDrawer.cjs.map +1 -0
  162. package/dist/components/navigation/NavDrawer.context.cjs +2 -0
  163. package/dist/components/navigation/NavDrawer.context.cjs.map +1 -0
  164. package/dist/components/navigation/NavDrawer.context.mjs +11 -0
  165. package/dist/components/navigation/NavDrawer.context.mjs.map +1 -0
  166. package/dist/components/navigation/NavDrawer.mjs +179 -0
  167. package/dist/components/navigation/NavDrawer.mjs.map +1 -0
  168. package/dist/components/overlays/Dialog.cjs +1 -1
  169. package/dist/components/overlays/Dialog.cjs.map +1 -1
  170. package/dist/components/overlays/Dialog.mjs +190 -125
  171. package/dist/components/overlays/Dialog.mjs.map +1 -1
  172. package/dist/components/overlays/Dialog.shared.cjs +1 -1
  173. package/dist/components/overlays/Dialog.shared.cjs.map +1 -1
  174. package/dist/components/overlays/Dialog.shared.mjs +157 -81
  175. package/dist/components/overlays/Dialog.shared.mjs.map +1 -1
  176. package/dist/components/overlays/Dialog.slots.cjs +2 -0
  177. package/dist/components/overlays/Dialog.slots.cjs.map +1 -0
  178. package/dist/components/overlays/Dialog.slots.mjs +129 -0
  179. package/dist/components/overlays/Dialog.slots.mjs.map +1 -0
  180. package/dist/components/overlays/Drawer.cjs +1 -1
  181. package/dist/components/overlays/Drawer.cjs.map +1 -1
  182. package/dist/components/overlays/Drawer.context.cjs +2 -0
  183. package/dist/components/overlays/Drawer.context.cjs.map +1 -0
  184. package/dist/components/overlays/Drawer.context.mjs +29 -0
  185. package/dist/components/overlays/Drawer.context.mjs.map +1 -0
  186. package/dist/components/overlays/Drawer.mjs +12 -187
  187. package/dist/components/overlays/Drawer.mjs.map +1 -1
  188. package/dist/components/overlays/Drawer.panel.cjs +2 -0
  189. package/dist/components/overlays/Drawer.panel.cjs.map +1 -0
  190. package/dist/components/overlays/Drawer.panel.mjs +159 -0
  191. package/dist/components/overlays/Drawer.panel.mjs.map +1 -0
  192. package/dist/components/overlays/Drawer.parts.cjs +2 -0
  193. package/dist/components/overlays/Drawer.parts.cjs.map +1 -0
  194. package/dist/components/overlays/Drawer.parts.mjs +152 -0
  195. package/dist/components/overlays/Drawer.parts.mjs.map +1 -0
  196. package/dist/components/overlays/Drawer.root.cjs +2 -0
  197. package/dist/components/overlays/Drawer.root.cjs.map +1 -0
  198. package/dist/components/overlays/Drawer.root.mjs +207 -0
  199. package/dist/components/overlays/Drawer.root.mjs.map +1 -0
  200. package/dist/components/overlays/Drawer.scroll.cjs +2 -0
  201. package/dist/components/overlays/Drawer.scroll.cjs.map +1 -0
  202. package/dist/components/overlays/Drawer.scroll.mjs +42 -0
  203. package/dist/components/overlays/Drawer.scroll.mjs.map +1 -0
  204. package/dist/components/table/useGridNavigation.cjs +1 -1
  205. package/dist/components/table/useGridNavigation.cjs.map +1 -1
  206. package/dist/components/table/useGridNavigation.mjs +67 -70
  207. package/dist/components/table/useGridNavigation.mjs.map +1 -1
  208. package/dist/hooks/useActiveDescendant.cjs +2 -0
  209. package/dist/hooks/useActiveDescendant.cjs.map +1 -0
  210. package/dist/hooks/useActiveDescendant.mjs +56 -0
  211. package/dist/hooks/useActiveDescendant.mjs.map +1 -0
  212. package/dist/hooks/useDismiss.cjs.map +1 -1
  213. package/dist/hooks/useDismiss.mjs.map +1 -1
  214. package/dist/hooks/useFocusTrap.cjs +1 -1
  215. package/dist/hooks/useFocusTrap.cjs.map +1 -1
  216. package/dist/hooks/useFocusTrap.mjs +115 -98
  217. package/dist/hooks/useFocusTrap.mjs.map +1 -1
  218. package/dist/hooks/useListbox.cjs +1 -1
  219. package/dist/hooks/useListbox.cjs.map +1 -1
  220. package/dist/hooks/useListbox.mjs +250 -210
  221. package/dist/hooks/useListbox.mjs.map +1 -1
  222. package/dist/hooks/useModalLayer.cjs +1 -1
  223. package/dist/hooks/useModalLayer.cjs.map +1 -1
  224. package/dist/hooks/useModalLayer.mjs +35 -22
  225. package/dist/hooks/useModalLayer.mjs.map +1 -1
  226. package/dist/hooks/useRestoreFocus.cjs +1 -1
  227. package/dist/hooks/useRestoreFocus.cjs.map +1 -1
  228. package/dist/hooks/useRestoreFocus.mjs +91 -78
  229. package/dist/hooks/useRestoreFocus.mjs.map +1 -1
  230. package/dist/hooks/useRovingGrid.cjs +2 -0
  231. package/dist/hooks/useRovingGrid.cjs.map +1 -0
  232. package/dist/hooks/useRovingGrid.mjs +69 -0
  233. package/dist/hooks/useRovingGrid.mjs.map +1 -0
  234. package/dist/hooks/useRovingTabIndex.cjs +1 -1
  235. package/dist/hooks/useRovingTabIndex.cjs.map +1 -1
  236. package/dist/hooks/useRovingTabIndex.mjs +102 -78
  237. package/dist/hooks/useRovingTabIndex.mjs.map +1 -1
  238. package/dist/index.cjs +1 -1
  239. package/dist/index.d.cts +4391 -394
  240. package/dist/index.d.ts +4391 -394
  241. package/dist/index.mjs +115 -101
  242. package/dist/lib/classNames.cjs +1 -1
  243. package/dist/lib/classNames.cjs.map +1 -1
  244. package/dist/lib/classNames.mjs +170 -74
  245. package/dist/lib/classNames.mjs.map +1 -1
  246. package/dist/lib/date.cjs +2 -0
  247. package/dist/lib/date.cjs.map +1 -0
  248. package/dist/lib/date.mjs +44 -0
  249. package/dist/lib/date.mjs.map +1 -0
  250. package/dist/lib/focus.cjs +1 -1
  251. package/dist/lib/focus.cjs.map +1 -1
  252. package/dist/lib/focus.mjs +5 -2
  253. package/dist/lib/focus.mjs.map +1 -1
  254. package/dist/lib/icons.cjs +1 -1
  255. package/dist/lib/icons.cjs.map +1 -1
  256. package/dist/lib/icons.mjs +13 -6
  257. package/dist/lib/icons.mjs.map +1 -1
  258. package/dist/lib/layers.cjs +1 -1
  259. package/dist/lib/layers.cjs.map +1 -1
  260. package/dist/lib/layers.mjs +53 -47
  261. package/dist/lib/layers.mjs.map +1 -1
  262. package/dist/styles.css +1 -1
  263. package/package.json +1 -1
  264. package/src/styles/tokens.css +25 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TagGroup.cjs","names":["React","TAG_ITEM_SELECTOR","TOOLBAR_ITEM_SELECTOR","isHiddenInput","TagGroupHostContext","useToolbarNavigation","useMergedRefs","useControllable","EMPTY_VALUES","useEventCallback","toggleValue","sameValues","isDev","hasWarned","TagGroupContext","composeEventHandlers","cn","tagGroupClassNames","HiddenInput","require_runtime","require_cn","require_dev","require_composeEventHandlers","require_classNames","require_useEventCallback","require_useControllable","require_focus","require_useMergedRefs","require_Toolbar_navigation","require_useFormReset","require_HiddenInput","require_pickerValues","require_TagGroup_context","react","react_jsx_runtime","tagGroupGapClasses","DEFAULT_SIZE","DEFAULT_APPEARANCE","DEFAULT_SHAPE","KNOWN_APPEARANCES","KNOWN_SHAPES","duplicateValueMessage","value","LISTBOX_ROLE_MESSAGE","STATIC_TOOLBAR_MESSAGE","useValueRegistry","counts","reportsRef","reportsValues","count","remaining","hasOwnTags","item","root","hasToolbarControl","element","TagGroup","roleProp","sizeProp","appearanceProp","shapeProp","valueProp","ariaOrientation","ariaDisabled","rest","role","mode","isToolbar","size","appearance","shape","host","removalFocus","hiddenValues","dimDisabled","rootRef","mergedRef","containerProps","ref","values","setValues","isControlled","defaultValue","onValueChange","next","selecting","selectedValues","isSelected","toggle","current","deselect","selected","initial","form","focusTabStop","reportsDismissals","onDismiss","register","dismiss","context","disabled","dismissible","fallbackFocusRef","isListbox","orientation","onKeyDown","onFocus","className","children"],"sources":["../../../src/components/data-display/TagGroup.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { tagGroupClassNames } from '../../lib/classNames';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { hasWarned, isDev, warnOnce } from '../../lib/dev';\nimport { getFirstTabbable, isHiddenInput } from '../../lib/focus';\nimport type { Orientation, TagAppearance, TagShape, TagSize } from '../../lib/types';\nimport { useControllable } from '../../hooks/useControllable';\nimport { useEventCallback } from '../../hooks/useEventCallback';\nimport { useFormReset } from '../../hooks/useFormReset';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { TOOLBAR_ITEM_SELECTOR, useToolbarNavigation } from '../button/Toolbar.navigation';\nimport { HiddenInput } from '../internal/HiddenInput';\nimport { EMPTY_VALUES, sameValues, toggleValue } from '../input/pickerValues';\nimport {\n TAG_GROUP_SELECTOR,\n TAG_ITEM_SELECTOR,\n TagGroupContext,\n TagGroupHostContext,\n} from './TagGroup.context';\nimport type { TagGroupContextValue, TagGroupMode } from './TagGroup.context';\n\n/** Props of {@link TagGroup}: the element that holds the tags (a `<div>`). */\nexport interface TagGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'defaultValue'> {\n /**\n * `'toolbar'`: one Tab stop and arrow keys, for tags that are actions (dismissible tags,\n * InteractionTags, links). `'list'`: a list (every tag gets `role=\"listitem\"`), each control\n * its own Tab stop and no arrow keys, for tags that only display. Another role keeps the\n * toolbar keyboard; `'listbox'` is not supported (an option cannot hold a Dismiss button).\n * Name the group with `aria-label` or `aria-labelledby`.\n * @default 'toolbar'\n */\n role?: React.AriaRole;\n /**\n * Layout and arrow-key axis, as on Toolbar: Left/Right (mirrored in RTL) when horizontal,\n * Up/Down when vertical; a wrapping set moves in DOM order. `aria-orientation` (toolbar role)\n * and `data-orientation`.\n * @default 'horizontal'\n */\n orientation?: Orientation;\n /** Default `size` of its tags (a tag's own wins) and the gap between them. @default 'medium' */\n size?: TagSize;\n /** Default `appearance` of its tags (a tag's own wins). @default 'filled' */\n appearance?: TagAppearance;\n /** Default `shape` of its tags (a tag's own wins). @default 'circular' */\n shape?: TagShape;\n /**\n * Disables every tag: a tag cannot re-enable itself. A control that has focus keeps it,\n * unavailable, until focus leaves it. `aria-disabled` (not with `role=\"list\"`), `data-disabled`.\n * @default false\n */\n disabled?: boolean;\n /**\n * Default `dismissible` of its Tags (a Tag's own value wins). An InteractionTag is dismissible\n * when it has an `InteractionTag.Secondary`.\n * @default false\n */\n dismissible?: boolean;\n /**\n * Called with the tag's `value` on every dismissal: a click, Enter or Space on a Dismiss\n * button, or Delete or Backspace on a tag (Fluent's `onDismiss(event, { value })`). Remove the\n * tag from your state; when it goes, focus moves to the next tag, else the previous one, else\n * `fallbackFocusRef`. Fluent's TagGroup moves focus as it calls `onDismiss`, whether or not the\n * tag goes, and has no fallback. Dismissing a selected tag also removes its value from the\n * selection; Fluent's TagGroup leaves the selection alone.\n */\n onDismiss?: (value: string) => void;\n /**\n * Where focus goes when the tag that had it is removed and no other tag can take it, for\n * example an \"Add\" or \"Reset filters\" button outside the group. WaveUI-only.\n */\n fallbackFocusRef?: React.RefObject<HTMLElement | null>;\n /**\n * The selected values (controlled), in selection order: the `value`s of the pressed\n * InteractionTags. Passing `value`, `defaultValue` or `onValueChange` makes the group select\n * (Fluent's `selectedValues`). Plain Tags are never selected.\n */\n value?: readonly string[];\n /** Initially selected values (uncontrolled; Fluent's `defaultSelectedValues`). @default [] */\n defaultValue?: readonly string[];\n /** Called with the new selected values when they change (Fluent's `onTagSelect`). */\n onValueChange?: (value: string[]) => void;\n /**\n * Form field name: one hidden input per selected value (a selecting group only). Fluent's\n * TagGroup submits nothing.\n */\n name?: string;\n /** The id of the form the hidden inputs belong to, when the group is outside it. */\n form?: string;\n /**\n * An empty selection blocks the form's submission (native validation only: a toolbar has no\n * `aria-required`).\n * @default false\n */\n required?: boolean;\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** The gap between the tags per size: 8, 6 or 4px. */\nconst tagGroupGapClasses: Readonly<Record<TagSize, string>> = {\n medium: 'gap-2',\n small: 'gap-1.5',\n 'extra-small': 'gap-1',\n};\n\nconst DEFAULT_SIZE: TagSize = 'medium';\nconst DEFAULT_APPEARANCE: TagAppearance = 'filled';\nconst DEFAULT_SHAPE: TagShape = 'circular';\nconst KNOWN_APPEARANCES: ReadonlySet<string> = new Set(['filled', 'outline', 'brand']);\nconst KNOWN_SHAPES: ReadonlySet<string> = new Set(['circular', 'rounded']);\n\nfunction duplicateValueMessage(value: string): string {\n return `TagGroup: several tags share the value \"${value}\". Give every tag of a group its own \\`value\\`: \\`onDismiss\\` and the selection cannot tell them apart.`;\n}\n\nconst LISTBOX_ROLE_MESSAGE =\n 'TagGroup: `role=\"listbox\"` is not supported (an option cannot hold a Dismiss button); the group keeps the toolbar keyboard. Use InteractionTags in the default toolbar for selectable tags, or Listbox for a list of values.';\n\nconst STATIC_TOOLBAR_MESSAGE =\n 'TagGroup: this toolbar has tags but no control (no Dismiss button, InteractionTag or link). Pass `role=\"list\"` for tags that only display.';\n\n/**\n * The value registry of one TagGroup: a count per explicit tag `value` (registered from the tags'\n * layout effects). A second tag with one value warns once per value while the group reports\n * values (`reportsValues`), also when it starts reporting them after the tags mounted.\n */\nfunction useValueRegistry(reportsValues: boolean): TagGroupContextValue['register'] {\n const [counts] = React.useState(() => new Map<string, number>());\n const reportsRef = React.useRef(reportsValues);\n\n React.useLayoutEffect(() => {\n reportsRef.current = reportsValues;\n if (!reportsValues) return;\n for (const [value, count] of counts) {\n if (count > 1) warnOnce(`TagGroup:duplicate-value:${value}`, duplicateValueMessage(value));\n }\n }, [counts, reportsValues]);\n\n return React.useCallback(\n (value: string) => {\n const count = (counts.get(value) ?? 0) + 1;\n counts.set(value, count);\n if (count > 1 && reportsRef.current) {\n warnOnce(`TagGroup:duplicate-value:${value}`, duplicateValueMessage(value));\n }\n return () => {\n const remaining = (counts.get(value) ?? 1) - 1;\n if (remaining > 0) counts.set(value, remaining);\n else counts.delete(value);\n };\n },\n [counts],\n );\n}\n\n/** Whether `root` holds tags of its own (not only those of a nested group). */\nfunction hasOwnTags(root: HTMLElement): boolean {\n for (const item of root.querySelectorAll(TAG_ITEM_SELECTOR)) {\n if (item.parentElement?.closest(TAG_GROUP_SELECTOR) === root) return true;\n }\n return false;\n}\n\n/**\n * Whether `root` holds a control a toolbar can move to: a form input of the group's own (a\n * `type=\"hidden\"` input, or the invisible input of a required group) is none.\n */\nfunction hasToolbarControl(root: HTMLElement): boolean {\n for (const element of root.querySelectorAll(TOOLBAR_ITEM_SELECTOR)) {\n if (!isHiddenInput(element) && !element.hasAttribute('data-wave-hidden-input')) return true;\n }\n return false;\n}\n\n/**\n * A set of tags with one keyboard model: by default an APG toolbar (`role=\"toolbar\"`), or with\n * `role=\"list\"` a list of tags that only display.\n *\n * - **Toolbar** (the default): one Tab stop, the last focused control (at first the first enabled\n * one). Left/Right move between the controls when horizontal (mirrored in RTL), Up/Down when\n * `orientation=\"vertical\"`, the other axis does nothing; the arrows wrap, in DOM order across\n * wrapped rows, and Home/End jump to the first and last control, the same rule as Toolbar.\n * Natively disabled controls are skipped. Use it for tags that are actions: dismissible tags,\n * InteractionTags (both of their buttons join the arrow order), link chips. Renders\n * `aria-orientation`, and `aria-disabled` while disabled.\n * - **List** (`role=\"list\"`): every tag is a list item (`role=\"listitem\"`), every control keeps\n * its own Tab stop and the group handles no arrow key. A tag rendered as a link or a button\n * takes no list item role: put the link in the tag's content (`<Tag><a href>…</a></Tag>`), or\n * wrap the tag in an element with `role=\"listitem\"`.\n * - **Delete and Backspace** on a tag's Dismiss button, on either button of an InteractionTag with\n * an `InteractionTag.Secondary`, or on a link in a dismissible tag's text, dismiss the tag (a\n * held key dismisses one tag). Enter, Space and a click on the Dismiss button do too.\n * - **Defaults for its tags**: `size`, `appearance`, `shape` and, for Tags, `dismissible` (a tag's\n * own value wins; an InteractionTag is dismissible when it has a Secondary), and `disabled`,\n * which disables every tag (a tag cannot re-enable itself).\n * - **`onDismiss(value)`** is called on every dismissal, after the Tag's own `onDismiss` or the\n * Secondary's `onClick` (whose `preventDefault()` keeps the group from being called). Remove\n * the tag from your state: when the tag that had focus goes, focus moves to the next tag, else\n * the previous one, else `fallbackFocusRef`, so it never falls to the page. Give every tag a\n * `value`. A dismissal you refuse (the tag stays) moves no focus.\n * - **Selection**: with `value`, `defaultValue` or `onValueChange` (Fluent's `selectedValues`,\n * `defaultSelectedValues` and `onTagSelect`) the group selects. Each `InteractionTag.Primary`\n * is then a toggle (`aria-pressed`): a click, Enter or Space adds its tag's `value` to the\n * selection or removes it, after the Primary's `onClick` (whose `preventDefault()` cancels the\n * toggle); the arrow keys only move. `onValueChange` receives the values in selection order,\n * and values without a tag are kept. Dismissing a selected InteractionTag calls `onDismiss`\n * and then `onValueChange` without its value. Plain Tags are never selected; selection is\n * multiple only (RadioGroup or `Toolbar.RadioGroup` for a single choice).\n * - **Forms**: a selecting group with `name` submits one entry per selected value, also from\n * outside its form with `form`; `required` blocks the submission of an empty selection and\n * moves focus to the group's Tab stop (native validation only, no `aria-required`); a form\n * reset restores `defaultValue`. A disabled group submits nothing, and a group that does not\n * select renders no form input.\n * - No accessible name by default: name the group with `aria-label` or `aria-labelledby`.\n * - Renders `data-orientation`, `data-size`, `data-appearance`, `data-shape` and, while\n * disabled, `data-disabled`.\n *\n * @example\n * const [filters, setFilters] = React.useState(['Red', 'Blue', 'Large']);\n * const reset = React.useRef<HTMLButtonElement>(null);\n * <TagGroup\n * aria-label=\"Active filters\"\n * dismissible\n * fallbackFocusRef={reset}\n * onDismiss={(value) => setFilters((current) => current.filter((f) => f !== value))}\n * >\n * {filters.map((filter) => (\n * <Tag key={filter} value={filter}>\n * {filter}\n * </Tag>\n * ))}\n * </TagGroup>\n * <Button ref={reset} onClick={() => setFilters(['Red', 'Blue', 'Large'])}>Reset filters</Button>\n *\n * @example\n * // Selectable chips, submitted with a form as \"fruit\".\n * const [fruit, setFruit] = React.useState<string[]>(['plum']);\n * <TagGroup aria-label=\"Fruit\" name=\"fruit\" value={fruit} onValueChange={setFruit}>\n * <InteractionTag value=\"cherry\">\n * <InteractionTag.Primary>Cherry</InteractionTag.Primary>\n * </InteractionTag>\n * <InteractionTag value=\"plum\">\n * <InteractionTag.Primary>Plum</InteractionTag.Primary>\n * </InteractionTag>\n * </TagGroup>\n */\nexport const TagGroup = ({\n role: roleProp,\n orientation = 'horizontal',\n size: sizeProp = DEFAULT_SIZE,\n appearance: appearanceProp = DEFAULT_APPEARANCE,\n shape: shapeProp = DEFAULT_SHAPE,\n disabled = false,\n dismissible = false,\n onDismiss,\n fallbackFocusRef,\n value: valueProp,\n defaultValue,\n onValueChange,\n name,\n form,\n required = false,\n className,\n onKeyDown,\n onFocus,\n 'aria-orientation': ariaOrientation,\n 'aria-disabled': ariaDisabled,\n ref,\n children,\n ...rest\n}: TagGroupProps) => {\n const role = roleProp ?? 'toolbar';\n const mode: TagGroupMode = role === 'list' ? 'list' : 'toolbar';\n const isToolbar = mode === 'toolbar';\n // An untyped value outside the unions falls back to the default look.\n const size: TagSize = Object.hasOwn(tagGroupGapClasses, sizeProp) ? sizeProp : DEFAULT_SIZE;\n const appearance: TagAppearance = KNOWN_APPEARANCES.has(appearanceProp)\n ? appearanceProp\n : DEFAULT_APPEARANCE;\n const shape: TagShape = KNOWN_SHAPES.has(shapeProp) ? shapeProp : DEFAULT_SHAPE;\n\n // The host options apply to this group only: a nested group reads `null` (provided below).\n const host = React.useContext(TagGroupHostContext);\n const removalFocus = host?.removalFocus ?? 'next-then-previous';\n const hiddenValues = host?.hiddenValues;\n const dimDisabled = host?.dimDisabled ?? true;\n\n const rootRef = React.useRef<HTMLDivElement>(null);\n // In list mode the roving container is not attached, so the controls keep their own Tab stops\n // (and the roving hook hands back the tab indices it stamped while the group was a toolbar).\n const { containerProps } = useToolbarNavigation({ orientation });\n const mergedRef = useMergedRefs<HTMLDivElement>(\n rootRef,\n isToolbar ? containerProps.ref : null,\n ref,\n );\n\n // Selection (D38): the values of the pressed InteractionTags, in selection order. The callback\n // receives a copy of its own, so the app may keep or change it.\n const [values, setValues, isControlled] = useControllable<readonly string[]>(\n valueProp,\n defaultValue ?? EMPTY_VALUES,\n (next) => onValueChange?.([...next]),\n );\n // Any of the three makes the group select; `onValueChange` alone selects from `[]`.\n const selecting = isControlled || defaultValue !== undefined || onValueChange !== undefined;\n const selectedValues = React.useMemo(() => new Set(values), [values]);\n const isSelected = React.useCallback(\n (value: string) => selecting && selectedValues.has(value),\n [selecting, selectedValues],\n );\n const toggle = useEventCallback((value: string) => {\n if (!selecting || disabled) return;\n setValues((current) => toggleValue(current, value).values);\n });\n // A dismissed InteractionTag leaves the selection (D39); a value that is not selected changes\n // nothing (the same array is no change, so `onValueChange` is not called).\n const deselect = useEventCallback((value: string) => {\n if (!selecting) return;\n setValues((current) =>\n current.includes(value) ? current.filter((selected) => selected !== value) : current,\n );\n });\n\n // A form reset restores `defaultValue`, compared by content (C-FORMS): an unchanged reset\n // reports nothing, also with a new inline default array.\n useFormReset(\n rootRef,\n () => {\n if (!selecting) return;\n setValues((current) => {\n const initial = defaultValue ?? EMPTY_VALUES;\n return sameValues(current, initial) ? current : [...initial];\n });\n },\n form,\n );\n // An empty required selection: the browser reports it at the group's Tab stop (the toolbar's\n // last focused control, or a list's first control).\n const focusTabStop = () => {\n const root = rootRef.current;\n if (root) getFirstTabbable(root)?.focus();\n };\n\n const reportsDismissals = onDismiss !== undefined;\n const register = useValueRegistry(reportsDismissals || selecting);\n const dismiss = useEventCallback((value: string) => {\n onDismiss?.(value);\n });\n\n const context = React.useMemo<TagGroupContextValue>(\n () => ({\n mode,\n size,\n appearance,\n shape,\n disabled,\n dismissible,\n dismiss,\n reportsDismissals,\n selecting,\n isSelected,\n toggle,\n deselect,\n register,\n rootRef,\n fallbackFocusRef,\n removalFocus,\n hiddenValues,\n dimDisabled,\n }),\n [\n mode,\n size,\n appearance,\n shape,\n disabled,\n dismissible,\n dismiss,\n reportsDismissals,\n selecting,\n isSelected,\n toggle,\n deselect,\n register,\n fallbackFocusRef,\n removalFocus,\n hiddenValues,\n dimDisabled,\n ],\n );\n\n const isListbox = role === 'listbox';\n React.useEffect(() => {\n if (isListbox) warnOnce('TagGroup:listbox-role', LISTBOX_ROLE_MESSAGE);\n }, [isListbox]);\n\n // A toolbar of tags that only display has nothing to move to: it should be a list. Checked\n // after every commit (the tags are found in the DOM, so a tag written in a Server Component\n // counts too), in development only.\n React.useEffect(() => {\n if (!isDev || !isToolbar || disabled || hasWarned('TagGroup:static-toolbar')) return;\n const root = rootRef.current;\n if (root && hasOwnTags(root) && !hasToolbarControl(root)) {\n warnOnce('TagGroup:static-toolbar', STATIC_TOOLBAR_MESSAGE);\n }\n });\n\n return (\n <TagGroupContext.Provider value={context}>\n <TagGroupHostContext.Provider value={null}>\n <div\n data-orientation={orientation}\n data-size={size}\n data-appearance={appearance}\n data-shape={shape}\n data-disabled={disabled ? '' : undefined}\n data-wave-tag-group=\"\"\n {...rest}\n ref={mergedRef}\n role={role}\n // `aria-orientation` is allowed on a toolbar, not on a list (axe `aria-allowed-attr`);\n // `aria-disabled` is supported on widget and group roles, not on a list (ARIA 1.2).\n aria-orientation={role === 'toolbar' ? (ariaOrientation ?? orientation) : ariaOrientation}\n aria-disabled={isToolbar && disabled ? true : ariaDisabled}\n data-roving-container={isToolbar ? '' : undefined}\n onKeyDown={\n isToolbar ? composeEventHandlers(onKeyDown, containerProps.onKeyDown) : onKeyDown\n }\n onFocus={\n isToolbar\n ? composeEventHandlers(onFocus, containerProps.onFocus, {\n checkDefaultPrevented: false,\n })\n : onFocus\n }\n className={cn(\n tagGroupClassNames.root,\n 'relative inline-flex flex-wrap items-center',\n tagGroupGapClasses[size],\n orientation === 'vertical' && 'flex-col items-start',\n className,\n )}\n >\n {children}\n {/* Inside the root (`relative`): a required input sits at its corner, and a focused one\n hands focus back to the group. Only a selecting group has values to submit. */}\n {selecting && (\n <HiddenInput\n type=\"text\"\n name={name}\n form={form}\n disabled={disabled}\n value={values}\n required={required}\n onInvalid={focusTabStop}\n />\n )}\n </div>\n </TagGroupHostContext.Provider>\n </TagGroupContext.Provider>\n );\n};\nTagGroup.displayName = 'TagGroup';\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/dev.cjs"),n=require("../../hooks/useId.cjs"),r=require("../../lib/focus.cjs"),i=require("../../hooks/usePreserveFocus.cjs");let a=require("react");a=e.__toESM(a,1);var o=`[data-wave-tag-group]`,s=`[data-wave-tag-item]`,c=`[data-wave-tag-dismiss]`,l=a.createContext(null);l.displayName=`TagGroupHostContext`;var u=a.createContext(null);u.displayName=`TagHostContext`;var d=a.createContext(null);d.displayName=`TagGroupContext`;function f(){return a.useContext(d)}function p(e){return(e.key===`Delete`||e.key===`Backspace`)&&!e.defaultPrevented&&!e.repeat&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&!e.nativeEvent.isComposing}function m(e){return e.key===`Enter`&&e.repeat}function h(e){return r.isFocusable(e)?e.getAttribute(`aria-disabled`)!==`true`||e.hasAttribute(`data-disabled-focusable`):!1}function g(e){if(e.matches(r.FOCUSABLE_SELECTOR)&&h(e))return e;for(let t of e.querySelectorAll(r.FOCUSABLE_SELECTOR))if(h(t))return t;return null}function _(e,t){if(t){let t=e.querySelector(c);if(t&&h(t))return t}return g(e)}function v(e,t,n){let r=e.ownerDocument.activeElement?.closest(c),i=!!r&&e.contains(r),a=t!==null&&t!==e?[t]:[],l=t?.parentElement?.closest(`[data-wave-tag-group]`)??null;if(t&&l){let e=Array.from(l.querySelectorAll(s)).filter(e=>e.parentElement?.closest(o)===l),r=e.indexOf(t);r!==-1&&(a.push(...e.slice(r+1)),(n?.removalFocus??`next-then-previous`)===`next-then-previous`&&a.push(...e.slice(0,r).reverse()))}return{candidates:a,fromDismiss:i,fallbackFocusRef:n?.fallbackFocusRef,groupRoot:n?l:null}}var y='TagGroup: the tag that had focus was removed and no other tag could take focus, so focus fell to the page. Pass `fallbackFocusRef` (for example an "Add" or "Reset filters" button outside the group).';function b(e){if(!e)return null;for(let t of e.candidates){if(!t.isConnected)continue;let n=_(t,e.fromDismiss);if(n)return n}let n=e.fallbackFocusRef?.current;return n&&r.isConnectedAndFocusable(n)?n:(e.groupRoot?.isConnected&&t.warnOnce(`TagGroup:focus-lost`,y),null)}function x(e,t,n,r){let o=a.useRef({node:null,group:t,isControl:n}),c=a.useRef(null);a.useLayoutEffect(()=>{o.current={node:e.current,group:t,isControl:n}}),a.useLayoutEffect(()=>{let e=o,t=c;return t.current=null,()=>{let{node:n,group:r,isControl:i}=e.current,a=n?.ownerDocument.activeElement;if(!n||!a||!n.contains(a))return;let o=i?n.parentElement?.closest(s):n;t.current=v(n,o??null,r)}},[]),i.usePreserveFocus(e,()=>b(c.current),{enabled:r})}var S="TagGroup: a tag without `value` is in a group with `onDismiss` or a selection, so it is reported by a generated id. Give every tag a `value`.";function C(e,r,i){let o=a.useContext(d),s=n.useId(`tag`),c=o?.register;a.useLayoutEffect(()=>{if(c&&r!==void 0)return c(r)},[c,r]);let l=i?.reported??((o?.reportsDismissals??!1)||(o?.selecting??!1)),u=o!==null&&l&&r===void 0;return a.useEffect(()=>{u&&t.warnOnce(`TagGroup:missing-value`,S)},[u]),x(e,o,!1,o!==null),r??s}function w(e){x(e,a.useContext(d),!0,!0)}function T(e){let[t,n]=a.useState(!1),[r,i]=a.useState(!1),[o,s]=a.useState(e),c=r;return o!==e&&(s(e),c=e&&t,c!==r&&i(c)),{held:e&&c,onFocus:e=>{e.target===e.currentTarget&&n(!0)},onBlur:e=>{e.target===e.currentTarget&&(e.relatedTarget!==null||e.currentTarget.ownerDocument.hasFocus())&&(n(!1),i(!1))}}}exports.TAG_DISMISS_SELECTOR=c,exports.TAG_GROUP_SELECTOR=o,exports.TAG_ITEM_SELECTOR=s,exports.TagGroupContext=d,exports.TagGroupHostContext=l,exports.TagHostContext=u,exports.isDismissKey=p,exports.isRepeatedActivation=m,exports.useDismissControlFocus=w,exports.useHeldFocus=T,exports.useTagGroupContext=f,exports.useTagGroupItem=C;
2
+ //# sourceMappingURL=TagGroup.context.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TagGroup.context.cjs","names":["React","isFocusable","FOCUSABLE_SELECTOR","isConnectedAndFocusable","useId","require_runtime","require_dev","require_useId","require_focus","require_usePreserveFocus","react","TAG_GROUP_SELECTOR","TAG_ITEM_SELECTOR","TAG_DISMISS_SELECTOR","TagGroupHostContext","TagHostContext","TagGroupContext","useTagGroupContext","isDismissKey","event","isRepeatedActivation","isEnabledControl","element","firstEnabledControl","root","correspondingControl","fromDismiss","dismiss","candidate","recordRemoval","dismissControl","node","candidates","item","groupRoot","items","index","group","FOCUS_LOST_MESSAGE","resolveRemovalTarget","record","control","fallback","useRemovalFocus","stateRef","recordRef","ref","state","currentGroup","active","MISSING_VALUE_MESSAGE","useTagGroupItem","generatedId","register","value","reported","options","missingValue","rootRef","useDismissControlFocus","controlRef","useHeldFocus","focused","setFocused","held","setHeld","prevDisabled","setPrevDisabled","disabled","isHeld"],"sources":["../../../src/components/data-display/TagGroup.context.ts"],"sourcesContent":["import * as React from 'react';\nimport { warnOnce } from '../../lib/dev';\nimport { FOCUSABLE_SELECTOR, isConnectedAndFocusable, isFocusable } from '../../lib/focus';\nimport type { TagAppearance, TagShape, TagSize } from '../../lib/types';\nimport { useId } from '../../hooks/useId';\nimport { usePreserveFocus } from '../../hooks/usePreserveFocus';\n\n/*\n * The internal state of the tag family: the TagGroup context its tags read, the host seams\n * TagPicker gives the TagGroup and the Tags it renders, the tag item hooks (value registration and\n * focus after a removal), the held focus of a control disabled while it has focus, and the\n * Delete/Backspace key test. Module-only (never exported from a barrel), and it imports no\n * component, so a module that reads it (Tag) holds no TagGroup or InteractionTag code.\n */\n\n// ---------------------------------------------------------------------------\n// Markers\n// ---------------------------------------------------------------------------\n\n/** Every TagGroup root (`data-wave-tag-group`, always rendered). */\nexport const TAG_GROUP_SELECTOR = '[data-wave-tag-group]';\n/** Every Tag and InteractionTag root (`data-wave-tag-item`, always rendered). */\nexport const TAG_ITEM_SELECTOR = '[data-wave-tag-item]';\n/** Every Tag Dismiss button and InteractionTag Secondary (`data-wave-tag-dismiss`, always rendered). */\nexport const TAG_DISMISS_SELECTOR = '[data-wave-tag-dismiss]';\n\n// ---------------------------------------------------------------------------\n// Host seams (frozen, spec §4.1 T-T7)\n// ---------------------------------------------------------------------------\n\n/** Options a host component (TagPicker.Group) gives the TagGroup it renders; not public props. */\nexport interface TagGroupHostOptions {\n /**\n * Where focus goes after the tag that had it is removed: `'next-then-previous'` (TagGroup's own\n * order: next tag, else previous, else `fallbackFocusRef`) or `'next'` (next tag, else\n * `fallbackFocusRef`; never a previous tag).\n * @default 'next-then-previous'\n */\n removalFocus?: 'next-then-previous' | 'next';\n /** Values of the tags (Tag or InteractionTag) that render `hidden` (TagPicker's `overflow=\"count\"`). */\n hiddenValues?: ReadonlySet<string>;\n /**\n * Whether a disabled tag dims itself. `false`: the host dims the area that holds the tags as a\n * whole (TagPicker's control while the picker is disabled), so a disabled Tag drops its own\n * dimming and its focus-ring lift, and an InteractionTag's buttons theirs; the native\n * `disabled`, the not-allowed cursor, the held focus of T-T6 and the forced-colours GrayText\n * stay.\n * @default true\n */\n dimDisabled?: boolean;\n}\n\n/** Read by the nearest TagGroup, which passes the options to its tags and provides `null` to its own subtree. */\nexport const TagGroupHostContext = React.createContext<TagGroupHostOptions | null>(null);\nTagGroupHostContext.displayName = 'TagGroupHostContext';\n\n/** What a host component (TagPicker) gives a Tag it renders itself. */\nexport interface TagHostContextValue {\n /**\n * The whole accessible name of the Tag's Dismiss button: rendered as `aria-label`, with no\n * `aria-labelledby` and no visually hidden `dismissLabel` text (the tag's `textContent` is its\n * own text). The 0.10 `Tag:dismissIcon-name` rule for slot names is unchanged.\n */\n dismissName?: string;\n /** Classes merged after the Dismiss button's own (TagPicker's `removeButton` part class). */\n dismissButtonClassName?: string;\n}\n\n/** Read by every Tag below it (TagPicker wraps each default tag in its own provider). */\nexport const TagHostContext = React.createContext<TagHostContextValue | null>(null);\nTagHostContext.displayName = 'TagHostContext';\n\n// ---------------------------------------------------------------------------\n// TagGroup context (internal to the tag family)\n// ---------------------------------------------------------------------------\n\n/** The keyboard model of a TagGroup: an APG toolbar (the default) or a list of static tags. */\nexport type TagGroupMode = 'toolbar' | 'list';\n\n/** What a TagGroup gives the tags inside it (resolved values, never `undefined`). */\nexport interface TagGroupContextValue {\n /** `'toolbar'`: one Tab stop and arrow keys; `'list'`: every tag root is a list item. */\n mode: TagGroupMode;\n /** The default `size` of its tags (a tag's own wins). */\n size: TagSize;\n /** The default `appearance` of its tags (a tag's own wins). */\n appearance: TagAppearance;\n /** The default `shape` of its tags (a tag's own wins). */\n shape: TagShape;\n /** Whether the group is disabled: it disables every tag, whatever the tag's own `disabled`. */\n disabled: boolean;\n /** The default `dismissible` of its Tags (a Tag's own value wins). */\n dismissible: boolean;\n /**\n * Reports a dismissal to the group (its `onDismiss(value)`). Called synchronously inside the\n * Dismiss control's click handler, after the tag's own handler.\n */\n dismiss: (value: string) => void;\n /** Whether the group reports dismissals (`onDismiss` is set): a dismissible tag then needs a `value`. */\n reportsDismissals: boolean;\n /**\n * Whether the group selects (`value`, `defaultValue` or `onValueChange` is set): its\n * InteractionTags are toggles, and each needs a `value`.\n */\n selecting: boolean;\n /** Whether `value` is in the group's selection (always `false` in a group that does not select). */\n isSelected: (value: string) => boolean;\n /**\n * Adds `value` to the selection, or removes it, keeping selection order (an InteractionTag\n * Primary's click). Does nothing in a group that does not select or while it is disabled.\n */\n toggle: (value: string) => void;\n /**\n * Removes `value` from the selection when it is in it: a dismissed InteractionTag leaves the\n * selection, after the group's `onDismiss(value)` (D39). Does nothing otherwise.\n */\n deselect: (value: string) => void;\n /**\n * Counts a mounted tag's explicit `value`; a second registration of one value warns once while\n * the group reports values (C-DEV). Call it from a layout effect and the returned remover in its\n * cleanup.\n */\n register: (value: string) => () => void;\n /** The group's root element. */\n rootRef: React.RefObject<HTMLElement | null>;\n /** Where focus goes when the removed tag that had it leaves no other tag to take it. */\n fallbackFocusRef: React.RefObject<HTMLElement | null> | undefined;\n /** The order of D35's focus move, from the host (`'next-then-previous'` without one). */\n removalFocus: 'next-then-previous' | 'next';\n /** The values whose tags render `hidden`, from the host. */\n hiddenValues: ReadonlySet<string> | undefined;\n /** Whether a disabled tag dims itself, from the host (`true` without one). */\n dimDisabled: boolean;\n}\n\n/** Internal: read with {@link useTagGroupContext}. `null` outside a TagGroup. */\nexport const TagGroupContext = React.createContext<TagGroupContextValue | null>(null);\nTagGroupContext.displayName = 'TagGroupContext';\n\n/**\n * The nearest TagGroup's context, or `null` outside one. Membership is optional (a Tag works on\n * its own), so a missing group is not reported (as `useFieldContext`).\n */\nexport function useTagGroupContext(): TagGroupContextValue | null {\n return React.useContext(TagGroupContext);\n}\n\n// ---------------------------------------------------------------------------\n// Delete and Backspace (D33)\n// ---------------------------------------------------------------------------\n\n/**\n * Whether a key press dismisses the tag it belongs to (D33): Delete or Backspace, not prevented\n * already (a merged slot's or the consumer's handler that ran first), not repeated (a held key\n * dismisses one tag), without Ctrl, Alt or Meta (Shift is allowed) and not while an input method\n * composes text. Each handler adds its own target rule (the control itself; on a Tag root, a target\n * in the primary text that is not an editable field).\n */\nexport function isDismissKey(event: React.KeyboardEvent): boolean {\n return (\n (event.key === 'Delete' || event.key === 'Backspace') &&\n !event.defaultPrevented &&\n !event.repeat &&\n !event.ctrlKey &&\n !event.altKey &&\n !event.metaKey &&\n !event.nativeEvent.isComposing\n );\n}\n\n/**\n * Whether a key press on a Dismiss control (Tag's Dismiss button, `InteractionTag.Secondary`) is a\n * repeated Enter: a held Enter, which the browser turns into a click on every repeat. The control\n * cancels it (`preventDefault()`), so a held Enter dismisses one tag, as a held Delete, Backspace\n * or Space does; otherwise, once focus moved to the next tag's Dismiss control, the repeats would\n * dismiss that one too.\n */\nexport function isRepeatedActivation(event: React.KeyboardEvent): boolean {\n return event.key === 'Enter' && event.repeat;\n}\n\n// ---------------------------------------------------------------------------\n// Focus after a removal (D35)\n// ---------------------------------------------------------------------------\n\n/** What a tag, or a tag's Dismiss control, records when it unmounts while it holds focus. */\ninterface RemovalRecord {\n /** The tag roots focus may move to, in the order they are tried. */\n candidates: HTMLElement[];\n /** Whether the control that had focus was a Dismiss control (the corresponding-control rule). */\n fromDismiss: boolean;\n /** The group's fallback, read when the move runs. */\n fallbackFocusRef: React.RefObject<HTMLElement | null> | undefined;\n /**\n * The root of the group the removed element was in. A lost focus warns only while it is still\n * in the document: a tag removed from a group that stays, not a whole group that unmounts.\n */\n groupRoot: HTMLElement | null;\n}\n\n/**\n * Whether `element` is an enabled control focus may move to: it can take focus, and it is not\n * `aria-disabled` unless it is a focusable disabled control (`data-disabled-focusable`), which a\n * toolbar keeps in its arrow order too. A disabled link chip has no `href` but may keep the\n * `tabindex` a toolbar gave it while it was enabled: focusable, yet no candidate.\n */\nfunction isEnabledControl(element: HTMLElement): boolean {\n if (!isFocusable(element)) return false;\n return (\n element.getAttribute('aria-disabled') !== 'true' ||\n element.hasAttribute('data-disabled-focusable')\n );\n}\n\n/** The first enabled control of `root` (itself first), or `null`. */\nfunction firstEnabledControl(root: HTMLElement): HTMLElement | null {\n if (root.matches(FOCUSABLE_SELECTOR) && isEnabledControl(root)) return root;\n for (const element of root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)) {\n if (isEnabledControl(element)) return element;\n }\n return null;\n}\n\n/**\n * The control of `candidate` that takes focus: its enabled Dismiss control when the removed\n * control was one, else its first enabled control (`null`: the candidate does not count).\n */\nfunction correspondingControl(candidate: HTMLElement, fromDismiss: boolean): HTMLElement | null {\n if (fromDismiss) {\n const dismiss = candidate.querySelector<HTMLElement>(TAG_DISMISS_SELECTOR);\n if (dismiss && isEnabledControl(dismiss)) return dismiss;\n }\n return firstEnabledControl(candidate);\n}\n\n/**\n * The record of `node` (a tag root, or a Dismiss control) taken while it still holds focus, in\n * its layout cleanup, before React removes its nodes: the tags of its group after it in DOM order,\n * then (order `'next-then-previous'`) those before it, nearest first; a Dismiss control's own tag\n * comes first. A tag outside a group has only its own tag (for a Dismiss control) or nothing.\n */\nfunction recordRemoval(\n node: HTMLElement,\n item: HTMLElement | null,\n group: TagGroupContextValue | null,\n): RemovalRecord {\n const active = node.ownerDocument.activeElement;\n const dismissControl = active?.closest(TAG_DISMISS_SELECTOR);\n const fromDismiss = !!dismissControl && node.contains(dismissControl);\n const ownItemFirst = item !== null && item !== node;\n const candidates: HTMLElement[] = ownItemFirst ? [item] : [];\n\n const groupRoot = item?.parentElement?.closest<HTMLElement>(TAG_GROUP_SELECTOR) ?? null;\n if (item && groupRoot) {\n const items = Array.from(groupRoot.querySelectorAll<HTMLElement>(TAG_ITEM_SELECTOR)).filter(\n (element) => element.parentElement?.closest(TAG_GROUP_SELECTOR) === groupRoot,\n );\n const index = items.indexOf(item);\n if (index !== -1) {\n candidates.push(...items.slice(index + 1));\n if ((group?.removalFocus ?? 'next-then-previous') === 'next-then-previous') {\n candidates.push(...items.slice(0, index).reverse());\n }\n }\n }\n\n return {\n candidates,\n fromDismiss,\n fallbackFocusRef: group?.fallbackFocusRef,\n groupRoot: group ? groupRoot : null,\n };\n}\n\nconst FOCUS_LOST_MESSAGE =\n 'TagGroup: the tag that had focus was removed and no other tag could take focus, so focus fell to the page. Pass `fallbackFocusRef` (for example an \"Add\" or \"Reset filters\" button outside the group).';\n\n/**\n * Where focus goes after the removal `record` describes (run in the microtask after the commit):\n * the corresponding control of the first candidate still connected that has one, else the group's\n * `fallbackFocusRef` when it is connected and focusable, else nothing (a development warning while\n * the group stays in the document).\n */\nfunction resolveRemovalTarget(record: RemovalRecord | null): HTMLElement | null {\n if (!record) return null;\n for (const candidate of record.candidates) {\n if (!candidate.isConnected) continue;\n const control = correspondingControl(candidate, record.fromDismiss);\n if (control) return control;\n }\n const fallback = record.fallbackFocusRef?.current;\n if (fallback && isConnectedAndFocusable(fallback)) return fallback;\n if (record.groupRoot?.isConnected) warnOnce('TagGroup:focus-lost', FOCUS_LOST_MESSAGE);\n return null;\n}\n\n/** The latest render's view of a removable element, read by its unmount cleanup. */\ninterface RemovalState {\n node: HTMLElement | null;\n group: TagGroupContextValue | null;\n /** Whether the element is a Dismiss control (its own tag is then tried first). */\n isControl: boolean;\n}\n\n/**\n * Moves focus when the element `ref` holds unmounts while focus is inside it: the record is taken\n * in the layout cleanup (the nodes are still in the document) and the move runs in a microtask,\n * through `usePreserveFocus` (a remount cancels it, and focus placed elsewhere first wins).\n */\nfunction useRemovalFocus(\n ref: React.RefObject<HTMLElement | null>,\n group: TagGroupContextValue | null,\n isControl: boolean,\n enabled: boolean,\n): void {\n const stateRef = React.useRef<RemovalState>({ node: null, group, isControl });\n const recordRef = React.useRef<RemovalRecord | null>(null);\n\n React.useLayoutEffect(() => {\n stateRef.current = { node: ref.current, group, isControl };\n });\n\n React.useLayoutEffect(() => {\n const state = stateRef;\n const record = recordRef;\n record.current = null;\n return () => {\n const { node, group: currentGroup, isControl: control } = state.current;\n const active = node?.ownerDocument.activeElement;\n if (!node || !active || !node.contains(active)) return;\n const item = control ? node.parentElement?.closest<HTMLElement>(TAG_ITEM_SELECTOR) : node;\n record.current = recordRemoval(node, item ?? null, currentGroup);\n };\n }, []);\n\n usePreserveFocus(ref, () => resolveRemovalTarget(recordRef.current), { enabled });\n}\n\nconst MISSING_VALUE_MESSAGE =\n 'TagGroup: a tag without `value` is in a group with `onDismiss` or a selection, so it is reported by a generated id. Give every tag a `value`.';\n\n/** Options of {@link useTagGroupItem}. */\nexport interface UseTagGroupItemOptions {\n /**\n * Whether the group reports this tag's value: a tag without `value` then warns once\n * (`TagGroup:missing-value`). A Tag passes whether it is dismissible in a group with\n * `onDismiss` (a Tag is never selected).\n * @default whether the group reports dismissals (it has `onDismiss`) or selects\n */\n reported?: boolean;\n}\n\n/**\n * The tag-family item hook (D35, D37), for a Tag or InteractionTag root:\n *\n * - registers the tag's explicit `value` with the nearest TagGroup from a layout effect (the\n * duplicate-value warning);\n * - warns once when the group reports the value (`options.reported`: it has `onDismiss` or\n * selects) and the tag has none;\n * - in a group, keeps focus in the group when the tag unmounts while focus is inside it: in a\n * microtask after the commit, to the corresponding control of the next tag that has an enabled\n * one, else (order `'next-then-previous'`) of the previous one, else to the group's\n * `fallbackFocusRef`, else nowhere (`TagGroup:focus-lost`). A tag outside a group moves no focus\n * when it goes.\n *\n * @param rootRef The tag's root element.\n * @param value The tag's `value`.\n * @param options See {@link UseTagGroupItemOptions}.\n * @returns The value the group reports for the tag: `value`, else a generated id.\n */\nexport function useTagGroupItem(\n rootRef: React.RefObject<HTMLElement | null>,\n value: string | undefined,\n options?: UseTagGroupItemOptions,\n): string {\n const group = React.useContext(TagGroupContext);\n const generatedId = useId('tag');\n const register = group?.register;\n\n React.useLayoutEffect(() => {\n if (!register || value === undefined) return undefined;\n return register(value);\n }, [register, value]);\n\n const reported =\n options?.reported ?? ((group?.reportsDismissals ?? false) || (group?.selecting ?? false));\n const missingValue = group !== null && reported && value === undefined;\n React.useEffect(() => {\n if (missingValue) warnOnce('TagGroup:missing-value', MISSING_VALUE_MESSAGE);\n }, [missingValue]);\n\n useRemovalFocus(rootRef, group, false, group !== null);\n return value ?? generatedId;\n}\n\n/**\n * For a tag's Dismiss control (Tag's Dismiss button, `InteractionTag.Secondary`): when it unmounts\n * while it has focus and its tag stays (`dismissible` turning `false`, a Secondary rendered\n * conditionally), focus moves to its own tag's first enabled control (a link in a Tag's text, the\n * Primary), else, in a group, as {@link useTagGroupItem} moves it. When its tag goes with it, the\n * tag's own move applies.\n *\n * @param controlRef The Dismiss control.\n */\nexport function useDismissControlFocus(controlRef: React.RefObject<HTMLElement | null>): void {\n const group = React.useContext(TagGroupContext);\n useRemovalFocus(controlRef, group, true, true);\n}\n\n// ---------------------------------------------------------------------------\n// Held focus (D25)\n// ---------------------------------------------------------------------------\n\n/** What {@link useHeldFocus} returns for one tag control. */\nexport interface HeldFocus {\n /**\n * Whether the control is held: it became disabled while it had focus, so it renders the\n * focusable disabled state (`aria-disabled`, `data-disabled`, `data-disabled-focusable`, its\n * click blocked) instead of the native `disabled` until focus leaves it. Always `false` while\n * the control is enabled.\n */\n held: boolean;\n /**\n * Records that the control has focus (an event whose target is the control itself). Compose it\n * after the consumer's handler with `{ checkDefaultPrevented: false }`.\n */\n onFocus: (event: React.FocusEvent<HTMLElement>) => void;\n /**\n * Records that focus left the control, which ends the hold. A blur after which focus went\n * nowhere (`relatedTarget` is `null` and the document has no focus: the window lost focus to\n * another app or tab) leaves the control the active element, so it keeps the hold. Compose it\n * after the consumer's handler with `{ checkDefaultPrevented: false }`.\n */\n onBlur: (event: React.FocusEvent<HTMLElement>) => void;\n}\n\n/**\n * Keeps a tag control that is disabled while it has focus focusable until focus leaves it (Tag's\n * Dismiss button, its link or button root, InteractionTag's buttons): the browser drops focus to\n * the page from a control that becomes natively disabled, and a standalone tag has no neighbour to\n * move it to. Render the native `disabled` while `disabled && !held`, the focusable disabled\n * state while `held`. The hold is adjusted during render from the previous `disabled`, never in\n * an effect.\n *\n * @param disabled The control's effective `disabled` (its own, its tag's, its group's).\n *\n * @example\n * const hold = useHeldFocus(disabled);\n * <button\n * type=\"button\"\n * {...(disabled ? (hold.held ? MERGED_DISABLED_FOCUSABLE_PROPS : { disabled: true }) : null)}\n * onFocus={composeEventHandlers(onFocus, hold.onFocus, { checkDefaultPrevented: false })}\n * onBlur={composeEventHandlers(onBlur, hold.onBlur, { checkDefaultPrevented: false })}\n * />\n */\nexport function useHeldFocus(disabled: boolean): HeldFocus {\n const [focused, setFocused] = React.useState(false);\n const [held, setHeld] = React.useState(false);\n // A control that turns disabled while it has focus is held; one that turns enabled is not.\n const [prevDisabled, setPrevDisabled] = React.useState(disabled);\n let isHeld = held;\n if (prevDisabled !== disabled) {\n setPrevDisabled(disabled);\n isHeld = disabled && focused;\n if (isHeld !== held) setHeld(isHeld);\n }\n\n const onFocus = (event: React.FocusEvent<HTMLElement>) => {\n if (event.target === event.currentTarget) setFocused(true);\n };\n const onBlur = (event: React.FocusEvent<HTMLElement>) => {\n if (event.target !== event.currentTarget) return;\n // As in `useRestoreFocus`'s focus tracker, a blur after which the control is still the\n // active element (the window lost focus) is no departure.\n const departed = event.relatedTarget !== null || event.currentTarget.ownerDocument.hasFocus();\n if (!departed) return;\n setFocused(false);\n setHeld(false);\n };\n\n return { held: disabled && isHeld, onFocus, onBlur };\n}\n"],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ "use client";
2
+ import { warnOnce as e } from "../../lib/dev.mjs";
3
+ import { useId as t } from "../../hooks/useId.mjs";
4
+ import { FOCUSABLE_SELECTOR as n, isConnectedAndFocusable as r, isFocusable as i } from "../../lib/focus.mjs";
5
+ import { usePreserveFocus as a } from "../../hooks/usePreserveFocus.mjs";
6
+ import * as o from "react";
7
+ //#region src/components/data-display/TagGroup.context.ts
8
+ var s = "[data-wave-tag-group]", c = "[data-wave-tag-item]", l = "[data-wave-tag-dismiss]", u = o.createContext(null);
9
+ u.displayName = "TagGroupHostContext";
10
+ var d = o.createContext(null);
11
+ d.displayName = "TagHostContext";
12
+ var f = o.createContext(null);
13
+ f.displayName = "TagGroupContext";
14
+ function p() {
15
+ return o.useContext(f);
16
+ }
17
+ function m(e) {
18
+ return (e.key === "Delete" || e.key === "Backspace") && !e.defaultPrevented && !e.repeat && !e.ctrlKey && !e.altKey && !e.metaKey && !e.nativeEvent.isComposing;
19
+ }
20
+ function h(e) {
21
+ return e.key === "Enter" && e.repeat;
22
+ }
23
+ function g(e) {
24
+ return i(e) ? e.getAttribute("aria-disabled") !== "true" || e.hasAttribute("data-disabled-focusable") : !1;
25
+ }
26
+ function _(e) {
27
+ if (e.matches(n) && g(e)) return e;
28
+ for (let t of e.querySelectorAll(n)) if (g(t)) return t;
29
+ return null;
30
+ }
31
+ function v(e, t) {
32
+ if (t) {
33
+ let t = e.querySelector(l);
34
+ if (t && g(t)) return t;
35
+ }
36
+ return _(e);
37
+ }
38
+ function y(e, t, n) {
39
+ let r = e.ownerDocument.activeElement?.closest(l), i = !!r && e.contains(r), a = t !== null && t !== e ? [t] : [], o = t?.parentElement?.closest("[data-wave-tag-group]") ?? null;
40
+ if (t && o) {
41
+ let e = Array.from(o.querySelectorAll(c)).filter((e) => e.parentElement?.closest(s) === o), r = e.indexOf(t);
42
+ r !== -1 && (a.push(...e.slice(r + 1)), (n?.removalFocus ?? "next-then-previous") === "next-then-previous" && a.push(...e.slice(0, r).reverse()));
43
+ }
44
+ return {
45
+ candidates: a,
46
+ fromDismiss: i,
47
+ fallbackFocusRef: n?.fallbackFocusRef,
48
+ groupRoot: n ? o : null
49
+ };
50
+ }
51
+ var b = "TagGroup: the tag that had focus was removed and no other tag could take focus, so focus fell to the page. Pass `fallbackFocusRef` (for example an \"Add\" or \"Reset filters\" button outside the group).";
52
+ function x(t) {
53
+ if (!t) return null;
54
+ for (let e of t.candidates) {
55
+ if (!e.isConnected) continue;
56
+ let n = v(e, t.fromDismiss);
57
+ if (n) return n;
58
+ }
59
+ let n = t.fallbackFocusRef?.current;
60
+ return n && r(n) ? n : (t.groupRoot?.isConnected && e("TagGroup:focus-lost", b), null);
61
+ }
62
+ function S(e, t, n, r) {
63
+ let i = o.useRef({
64
+ node: null,
65
+ group: t,
66
+ isControl: n
67
+ }), s = o.useRef(null);
68
+ o.useLayoutEffect(() => {
69
+ i.current = {
70
+ node: e.current,
71
+ group: t,
72
+ isControl: n
73
+ };
74
+ }), o.useLayoutEffect(() => {
75
+ let e = i, t = s;
76
+ return t.current = null, () => {
77
+ let { node: n, group: r, isControl: i } = e.current, a = n?.ownerDocument.activeElement;
78
+ if (!n || !a || !n.contains(a)) return;
79
+ let o = i ? n.parentElement?.closest(c) : n;
80
+ t.current = y(n, o ?? null, r);
81
+ };
82
+ }, []), a(e, () => x(s.current), { enabled: r });
83
+ }
84
+ var C = "TagGroup: a tag without `value` is in a group with `onDismiss` or a selection, so it is reported by a generated id. Give every tag a `value`.";
85
+ function w(n, r, i) {
86
+ let a = o.useContext(f), s = t("tag"), c = a?.register;
87
+ o.useLayoutEffect(() => {
88
+ if (c && r !== void 0) return c(r);
89
+ }, [c, r]);
90
+ let l = i?.reported ?? ((a?.reportsDismissals ?? !1) || (a?.selecting ?? !1)), u = a !== null && l && r === void 0;
91
+ return o.useEffect(() => {
92
+ u && e("TagGroup:missing-value", C);
93
+ }, [u]), S(n, a, !1, a !== null), r ?? s;
94
+ }
95
+ function T(e) {
96
+ S(e, o.useContext(f), !0, !0);
97
+ }
98
+ function E(e) {
99
+ let [t, n] = o.useState(!1), [r, i] = o.useState(!1), [a, s] = o.useState(e), c = r;
100
+ return a !== e && (s(e), c = e && t, c !== r && i(c)), {
101
+ held: e && c,
102
+ onFocus: (e) => {
103
+ e.target === e.currentTarget && n(!0);
104
+ },
105
+ onBlur: (e) => {
106
+ e.target === e.currentTarget && (e.relatedTarget !== null || e.currentTarget.ownerDocument.hasFocus()) && (n(!1), i(!1));
107
+ }
108
+ };
109
+ }
110
+ //#endregion
111
+ export { l as TAG_DISMISS_SELECTOR, s as TAG_GROUP_SELECTOR, c as TAG_ITEM_SELECTOR, f as TagGroupContext, u as TagGroupHostContext, d as TagHostContext, m as isDismissKey, h as isRepeatedActivation, T as useDismissControlFocus, E as useHeldFocus, p as useTagGroupContext, w as useTagGroupItem };
112
+
113
+ //# sourceMappingURL=TagGroup.context.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TagGroup.context.mjs","names":["useId","useId$1","React","TAG_GROUP_SELECTOR","TAG_ITEM_SELECTOR","TAG_DISMISS_SELECTOR","TagGroupHostContext","TagHostContext","TagGroupContext","useTagGroupContext","isDismissKey","event","isRepeatedActivation","isEnabledControl","isFocusable","element","firstEnabledControl","root","FOCUSABLE_SELECTOR","correspondingControl","fromDismiss","dismiss","candidate","recordRemoval","dismissControl","node","candidates","item","groupRoot","items","index","group","FOCUS_LOST_MESSAGE","resolveRemovalTarget","record","control","fallback","isConnectedAndFocusable","warnOnce","useRemovalFocus","stateRef","recordRef","ref","state","currentGroup","active","usePreserveFocus","MISSING_VALUE_MESSAGE","useTagGroupItem","generatedId","register","value","reported","options","missingValue","rootRef","useDismissControlFocus","controlRef","useHeldFocus","focused","setFocused","held","setHeld","prevDisabled","setPrevDisabled","disabled","isHeld"],"sources":["../../../src/components/data-display/TagGroup.context.ts"],"sourcesContent":["import * as React from 'react';\nimport { warnOnce } from '../../lib/dev';\nimport { FOCUSABLE_SELECTOR, isConnectedAndFocusable, isFocusable } from '../../lib/focus';\nimport type { TagAppearance, TagShape, TagSize } from '../../lib/types';\nimport { useId } from '../../hooks/useId';\nimport { usePreserveFocus } from '../../hooks/usePreserveFocus';\n\n/*\n * The internal state of the tag family: the TagGroup context its tags read, the host seams\n * TagPicker gives the TagGroup and the Tags it renders, the tag item hooks (value registration and\n * focus after a removal), the held focus of a control disabled while it has focus, and the\n * Delete/Backspace key test. Module-only (never exported from a barrel), and it imports no\n * component, so a module that reads it (Tag) holds no TagGroup or InteractionTag code.\n */\n\n// ---------------------------------------------------------------------------\n// Markers\n// ---------------------------------------------------------------------------\n\n/** Every TagGroup root (`data-wave-tag-group`, always rendered). */\nexport const TAG_GROUP_SELECTOR = '[data-wave-tag-group]';\n/** Every Tag and InteractionTag root (`data-wave-tag-item`, always rendered). */\nexport const TAG_ITEM_SELECTOR = '[data-wave-tag-item]';\n/** Every Tag Dismiss button and InteractionTag Secondary (`data-wave-tag-dismiss`, always rendered). */\nexport const TAG_DISMISS_SELECTOR = '[data-wave-tag-dismiss]';\n\n// ---------------------------------------------------------------------------\n// Host seams (frozen, spec §4.1 T-T7)\n// ---------------------------------------------------------------------------\n\n/** Options a host component (TagPicker.Group) gives the TagGroup it renders; not public props. */\nexport interface TagGroupHostOptions {\n /**\n * Where focus goes after the tag that had it is removed: `'next-then-previous'` (TagGroup's own\n * order: next tag, else previous, else `fallbackFocusRef`) or `'next'` (next tag, else\n * `fallbackFocusRef`; never a previous tag).\n * @default 'next-then-previous'\n */\n removalFocus?: 'next-then-previous' | 'next';\n /** Values of the tags (Tag or InteractionTag) that render `hidden` (TagPicker's `overflow=\"count\"`). */\n hiddenValues?: ReadonlySet<string>;\n /**\n * Whether a disabled tag dims itself. `false`: the host dims the area that holds the tags as a\n * whole (TagPicker's control while the picker is disabled), so a disabled Tag drops its own\n * dimming and its focus-ring lift, and an InteractionTag's buttons theirs; the native\n * `disabled`, the not-allowed cursor, the held focus of T-T6 and the forced-colours GrayText\n * stay.\n * @default true\n */\n dimDisabled?: boolean;\n}\n\n/** Read by the nearest TagGroup, which passes the options to its tags and provides `null` to its own subtree. */\nexport const TagGroupHostContext = React.createContext<TagGroupHostOptions | null>(null);\nTagGroupHostContext.displayName = 'TagGroupHostContext';\n\n/** What a host component (TagPicker) gives a Tag it renders itself. */\nexport interface TagHostContextValue {\n /**\n * The whole accessible name of the Tag's Dismiss button: rendered as `aria-label`, with no\n * `aria-labelledby` and no visually hidden `dismissLabel` text (the tag's `textContent` is its\n * own text). The 0.10 `Tag:dismissIcon-name` rule for slot names is unchanged.\n */\n dismissName?: string;\n /** Classes merged after the Dismiss button's own (TagPicker's `removeButton` part class). */\n dismissButtonClassName?: string;\n}\n\n/** Read by every Tag below it (TagPicker wraps each default tag in its own provider). */\nexport const TagHostContext = React.createContext<TagHostContextValue | null>(null);\nTagHostContext.displayName = 'TagHostContext';\n\n// ---------------------------------------------------------------------------\n// TagGroup context (internal to the tag family)\n// ---------------------------------------------------------------------------\n\n/** The keyboard model of a TagGroup: an APG toolbar (the default) or a list of static tags. */\nexport type TagGroupMode = 'toolbar' | 'list';\n\n/** What a TagGroup gives the tags inside it (resolved values, never `undefined`). */\nexport interface TagGroupContextValue {\n /** `'toolbar'`: one Tab stop and arrow keys; `'list'`: every tag root is a list item. */\n mode: TagGroupMode;\n /** The default `size` of its tags (a tag's own wins). */\n size: TagSize;\n /** The default `appearance` of its tags (a tag's own wins). */\n appearance: TagAppearance;\n /** The default `shape` of its tags (a tag's own wins). */\n shape: TagShape;\n /** Whether the group is disabled: it disables every tag, whatever the tag's own `disabled`. */\n disabled: boolean;\n /** The default `dismissible` of its Tags (a Tag's own value wins). */\n dismissible: boolean;\n /**\n * Reports a dismissal to the group (its `onDismiss(value)`). Called synchronously inside the\n * Dismiss control's click handler, after the tag's own handler.\n */\n dismiss: (value: string) => void;\n /** Whether the group reports dismissals (`onDismiss` is set): a dismissible tag then needs a `value`. */\n reportsDismissals: boolean;\n /**\n * Whether the group selects (`value`, `defaultValue` or `onValueChange` is set): its\n * InteractionTags are toggles, and each needs a `value`.\n */\n selecting: boolean;\n /** Whether `value` is in the group's selection (always `false` in a group that does not select). */\n isSelected: (value: string) => boolean;\n /**\n * Adds `value` to the selection, or removes it, keeping selection order (an InteractionTag\n * Primary's click). Does nothing in a group that does not select or while it is disabled.\n */\n toggle: (value: string) => void;\n /**\n * Removes `value` from the selection when it is in it: a dismissed InteractionTag leaves the\n * selection, after the group's `onDismiss(value)` (D39). Does nothing otherwise.\n */\n deselect: (value: string) => void;\n /**\n * Counts a mounted tag's explicit `value`; a second registration of one value warns once while\n * the group reports values (C-DEV). Call it from a layout effect and the returned remover in its\n * cleanup.\n */\n register: (value: string) => () => void;\n /** The group's root element. */\n rootRef: React.RefObject<HTMLElement | null>;\n /** Where focus goes when the removed tag that had it leaves no other tag to take it. */\n fallbackFocusRef: React.RefObject<HTMLElement | null> | undefined;\n /** The order of D35's focus move, from the host (`'next-then-previous'` without one). */\n removalFocus: 'next-then-previous' | 'next';\n /** The values whose tags render `hidden`, from the host. */\n hiddenValues: ReadonlySet<string> | undefined;\n /** Whether a disabled tag dims itself, from the host (`true` without one). */\n dimDisabled: boolean;\n}\n\n/** Internal: read with {@link useTagGroupContext}. `null` outside a TagGroup. */\nexport const TagGroupContext = React.createContext<TagGroupContextValue | null>(null);\nTagGroupContext.displayName = 'TagGroupContext';\n\n/**\n * The nearest TagGroup's context, or `null` outside one. Membership is optional (a Tag works on\n * its own), so a missing group is not reported (as `useFieldContext`).\n */\nexport function useTagGroupContext(): TagGroupContextValue | null {\n return React.useContext(TagGroupContext);\n}\n\n// ---------------------------------------------------------------------------\n// Delete and Backspace (D33)\n// ---------------------------------------------------------------------------\n\n/**\n * Whether a key press dismisses the tag it belongs to (D33): Delete or Backspace, not prevented\n * already (a merged slot's or the consumer's handler that ran first), not repeated (a held key\n * dismisses one tag), without Ctrl, Alt or Meta (Shift is allowed) and not while an input method\n * composes text. Each handler adds its own target rule (the control itself; on a Tag root, a target\n * in the primary text that is not an editable field).\n */\nexport function isDismissKey(event: React.KeyboardEvent): boolean {\n return (\n (event.key === 'Delete' || event.key === 'Backspace') &&\n !event.defaultPrevented &&\n !event.repeat &&\n !event.ctrlKey &&\n !event.altKey &&\n !event.metaKey &&\n !event.nativeEvent.isComposing\n );\n}\n\n/**\n * Whether a key press on a Dismiss control (Tag's Dismiss button, `InteractionTag.Secondary`) is a\n * repeated Enter: a held Enter, which the browser turns into a click on every repeat. The control\n * cancels it (`preventDefault()`), so a held Enter dismisses one tag, as a held Delete, Backspace\n * or Space does; otherwise, once focus moved to the next tag's Dismiss control, the repeats would\n * dismiss that one too.\n */\nexport function isRepeatedActivation(event: React.KeyboardEvent): boolean {\n return event.key === 'Enter' && event.repeat;\n}\n\n// ---------------------------------------------------------------------------\n// Focus after a removal (D35)\n// ---------------------------------------------------------------------------\n\n/** What a tag, or a tag's Dismiss control, records when it unmounts while it holds focus. */\ninterface RemovalRecord {\n /** The tag roots focus may move to, in the order they are tried. */\n candidates: HTMLElement[];\n /** Whether the control that had focus was a Dismiss control (the corresponding-control rule). */\n fromDismiss: boolean;\n /** The group's fallback, read when the move runs. */\n fallbackFocusRef: React.RefObject<HTMLElement | null> | undefined;\n /**\n * The root of the group the removed element was in. A lost focus warns only while it is still\n * in the document: a tag removed from a group that stays, not a whole group that unmounts.\n */\n groupRoot: HTMLElement | null;\n}\n\n/**\n * Whether `element` is an enabled control focus may move to: it can take focus, and it is not\n * `aria-disabled` unless it is a focusable disabled control (`data-disabled-focusable`), which a\n * toolbar keeps in its arrow order too. A disabled link chip has no `href` but may keep the\n * `tabindex` a toolbar gave it while it was enabled: focusable, yet no candidate.\n */\nfunction isEnabledControl(element: HTMLElement): boolean {\n if (!isFocusable(element)) return false;\n return (\n element.getAttribute('aria-disabled') !== 'true' ||\n element.hasAttribute('data-disabled-focusable')\n );\n}\n\n/** The first enabled control of `root` (itself first), or `null`. */\nfunction firstEnabledControl(root: HTMLElement): HTMLElement | null {\n if (root.matches(FOCUSABLE_SELECTOR) && isEnabledControl(root)) return root;\n for (const element of root.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)) {\n if (isEnabledControl(element)) return element;\n }\n return null;\n}\n\n/**\n * The control of `candidate` that takes focus: its enabled Dismiss control when the removed\n * control was one, else its first enabled control (`null`: the candidate does not count).\n */\nfunction correspondingControl(candidate: HTMLElement, fromDismiss: boolean): HTMLElement | null {\n if (fromDismiss) {\n const dismiss = candidate.querySelector<HTMLElement>(TAG_DISMISS_SELECTOR);\n if (dismiss && isEnabledControl(dismiss)) return dismiss;\n }\n return firstEnabledControl(candidate);\n}\n\n/**\n * The record of `node` (a tag root, or a Dismiss control) taken while it still holds focus, in\n * its layout cleanup, before React removes its nodes: the tags of its group after it in DOM order,\n * then (order `'next-then-previous'`) those before it, nearest first; a Dismiss control's own tag\n * comes first. A tag outside a group has only its own tag (for a Dismiss control) or nothing.\n */\nfunction recordRemoval(\n node: HTMLElement,\n item: HTMLElement | null,\n group: TagGroupContextValue | null,\n): RemovalRecord {\n const active = node.ownerDocument.activeElement;\n const dismissControl = active?.closest(TAG_DISMISS_SELECTOR);\n const fromDismiss = !!dismissControl && node.contains(dismissControl);\n const ownItemFirst = item !== null && item !== node;\n const candidates: HTMLElement[] = ownItemFirst ? [item] : [];\n\n const groupRoot = item?.parentElement?.closest<HTMLElement>(TAG_GROUP_SELECTOR) ?? null;\n if (item && groupRoot) {\n const items = Array.from(groupRoot.querySelectorAll<HTMLElement>(TAG_ITEM_SELECTOR)).filter(\n (element) => element.parentElement?.closest(TAG_GROUP_SELECTOR) === groupRoot,\n );\n const index = items.indexOf(item);\n if (index !== -1) {\n candidates.push(...items.slice(index + 1));\n if ((group?.removalFocus ?? 'next-then-previous') === 'next-then-previous') {\n candidates.push(...items.slice(0, index).reverse());\n }\n }\n }\n\n return {\n candidates,\n fromDismiss,\n fallbackFocusRef: group?.fallbackFocusRef,\n groupRoot: group ? groupRoot : null,\n };\n}\n\nconst FOCUS_LOST_MESSAGE =\n 'TagGroup: the tag that had focus was removed and no other tag could take focus, so focus fell to the page. Pass `fallbackFocusRef` (for example an \"Add\" or \"Reset filters\" button outside the group).';\n\n/**\n * Where focus goes after the removal `record` describes (run in the microtask after the commit):\n * the corresponding control of the first candidate still connected that has one, else the group's\n * `fallbackFocusRef` when it is connected and focusable, else nothing (a development warning while\n * the group stays in the document).\n */\nfunction resolveRemovalTarget(record: RemovalRecord | null): HTMLElement | null {\n if (!record) return null;\n for (const candidate of record.candidates) {\n if (!candidate.isConnected) continue;\n const control = correspondingControl(candidate, record.fromDismiss);\n if (control) return control;\n }\n const fallback = record.fallbackFocusRef?.current;\n if (fallback && isConnectedAndFocusable(fallback)) return fallback;\n if (record.groupRoot?.isConnected) warnOnce('TagGroup:focus-lost', FOCUS_LOST_MESSAGE);\n return null;\n}\n\n/** The latest render's view of a removable element, read by its unmount cleanup. */\ninterface RemovalState {\n node: HTMLElement | null;\n group: TagGroupContextValue | null;\n /** Whether the element is a Dismiss control (its own tag is then tried first). */\n isControl: boolean;\n}\n\n/**\n * Moves focus when the element `ref` holds unmounts while focus is inside it: the record is taken\n * in the layout cleanup (the nodes are still in the document) and the move runs in a microtask,\n * through `usePreserveFocus` (a remount cancels it, and focus placed elsewhere first wins).\n */\nfunction useRemovalFocus(\n ref: React.RefObject<HTMLElement | null>,\n group: TagGroupContextValue | null,\n isControl: boolean,\n enabled: boolean,\n): void {\n const stateRef = React.useRef<RemovalState>({ node: null, group, isControl });\n const recordRef = React.useRef<RemovalRecord | null>(null);\n\n React.useLayoutEffect(() => {\n stateRef.current = { node: ref.current, group, isControl };\n });\n\n React.useLayoutEffect(() => {\n const state = stateRef;\n const record = recordRef;\n record.current = null;\n return () => {\n const { node, group: currentGroup, isControl: control } = state.current;\n const active = node?.ownerDocument.activeElement;\n if (!node || !active || !node.contains(active)) return;\n const item = control ? node.parentElement?.closest<HTMLElement>(TAG_ITEM_SELECTOR) : node;\n record.current = recordRemoval(node, item ?? null, currentGroup);\n };\n }, []);\n\n usePreserveFocus(ref, () => resolveRemovalTarget(recordRef.current), { enabled });\n}\n\nconst MISSING_VALUE_MESSAGE =\n 'TagGroup: a tag without `value` is in a group with `onDismiss` or a selection, so it is reported by a generated id. Give every tag a `value`.';\n\n/** Options of {@link useTagGroupItem}. */\nexport interface UseTagGroupItemOptions {\n /**\n * Whether the group reports this tag's value: a tag without `value` then warns once\n * (`TagGroup:missing-value`). A Tag passes whether it is dismissible in a group with\n * `onDismiss` (a Tag is never selected).\n * @default whether the group reports dismissals (it has `onDismiss`) or selects\n */\n reported?: boolean;\n}\n\n/**\n * The tag-family item hook (D35, D37), for a Tag or InteractionTag root:\n *\n * - registers the tag's explicit `value` with the nearest TagGroup from a layout effect (the\n * duplicate-value warning);\n * - warns once when the group reports the value (`options.reported`: it has `onDismiss` or\n * selects) and the tag has none;\n * - in a group, keeps focus in the group when the tag unmounts while focus is inside it: in a\n * microtask after the commit, to the corresponding control of the next tag that has an enabled\n * one, else (order `'next-then-previous'`) of the previous one, else to the group's\n * `fallbackFocusRef`, else nowhere (`TagGroup:focus-lost`). A tag outside a group moves no focus\n * when it goes.\n *\n * @param rootRef The tag's root element.\n * @param value The tag's `value`.\n * @param options See {@link UseTagGroupItemOptions}.\n * @returns The value the group reports for the tag: `value`, else a generated id.\n */\nexport function useTagGroupItem(\n rootRef: React.RefObject<HTMLElement | null>,\n value: string | undefined,\n options?: UseTagGroupItemOptions,\n): string {\n const group = React.useContext(TagGroupContext);\n const generatedId = useId('tag');\n const register = group?.register;\n\n React.useLayoutEffect(() => {\n if (!register || value === undefined) return undefined;\n return register(value);\n }, [register, value]);\n\n const reported =\n options?.reported ?? ((group?.reportsDismissals ?? false) || (group?.selecting ?? false));\n const missingValue = group !== null && reported && value === undefined;\n React.useEffect(() => {\n if (missingValue) warnOnce('TagGroup:missing-value', MISSING_VALUE_MESSAGE);\n }, [missingValue]);\n\n useRemovalFocus(rootRef, group, false, group !== null);\n return value ?? generatedId;\n}\n\n/**\n * For a tag's Dismiss control (Tag's Dismiss button, `InteractionTag.Secondary`): when it unmounts\n * while it has focus and its tag stays (`dismissible` turning `false`, a Secondary rendered\n * conditionally), focus moves to its own tag's first enabled control (a link in a Tag's text, the\n * Primary), else, in a group, as {@link useTagGroupItem} moves it. When its tag goes with it, the\n * tag's own move applies.\n *\n * @param controlRef The Dismiss control.\n */\nexport function useDismissControlFocus(controlRef: React.RefObject<HTMLElement | null>): void {\n const group = React.useContext(TagGroupContext);\n useRemovalFocus(controlRef, group, true, true);\n}\n\n// ---------------------------------------------------------------------------\n// Held focus (D25)\n// ---------------------------------------------------------------------------\n\n/** What {@link useHeldFocus} returns for one tag control. */\nexport interface HeldFocus {\n /**\n * Whether the control is held: it became disabled while it had focus, so it renders the\n * focusable disabled state (`aria-disabled`, `data-disabled`, `data-disabled-focusable`, its\n * click blocked) instead of the native `disabled` until focus leaves it. Always `false` while\n * the control is enabled.\n */\n held: boolean;\n /**\n * Records that the control has focus (an event whose target is the control itself). Compose it\n * after the consumer's handler with `{ checkDefaultPrevented: false }`.\n */\n onFocus: (event: React.FocusEvent<HTMLElement>) => void;\n /**\n * Records that focus left the control, which ends the hold. A blur after which focus went\n * nowhere (`relatedTarget` is `null` and the document has no focus: the window lost focus to\n * another app or tab) leaves the control the active element, so it keeps the hold. Compose it\n * after the consumer's handler with `{ checkDefaultPrevented: false }`.\n */\n onBlur: (event: React.FocusEvent<HTMLElement>) => void;\n}\n\n/**\n * Keeps a tag control that is disabled while it has focus focusable until focus leaves it (Tag's\n * Dismiss button, its link or button root, InteractionTag's buttons): the browser drops focus to\n * the page from a control that becomes natively disabled, and a standalone tag has no neighbour to\n * move it to. Render the native `disabled` while `disabled && !held`, the focusable disabled\n * state while `held`. The hold is adjusted during render from the previous `disabled`, never in\n * an effect.\n *\n * @param disabled The control's effective `disabled` (its own, its tag's, its group's).\n *\n * @example\n * const hold = useHeldFocus(disabled);\n * <button\n * type=\"button\"\n * {...(disabled ? (hold.held ? MERGED_DISABLED_FOCUSABLE_PROPS : { disabled: true }) : null)}\n * onFocus={composeEventHandlers(onFocus, hold.onFocus, { checkDefaultPrevented: false })}\n * onBlur={composeEventHandlers(onBlur, hold.onBlur, { checkDefaultPrevented: false })}\n * />\n */\nexport function useHeldFocus(disabled: boolean): HeldFocus {\n const [focused, setFocused] = React.useState(false);\n const [held, setHeld] = React.useState(false);\n // A control that turns disabled while it has focus is held; one that turns enabled is not.\n const [prevDisabled, setPrevDisabled] = React.useState(disabled);\n let isHeld = held;\n if (prevDisabled !== disabled) {\n setPrevDisabled(disabled);\n isHeld = disabled && focused;\n if (isHeld !== held) setHeld(isHeld);\n }\n\n const onFocus = (event: React.FocusEvent<HTMLElement>) => {\n if (event.target === event.currentTarget) setFocused(true);\n };\n const onBlur = (event: React.FocusEvent<HTMLElement>) => {\n if (event.target !== event.currentTarget) return;\n // As in `useRestoreFocus`'s focus tracker, a blur after which the control is still the\n // active element (the window lost focus) is no departure.\n const departed = event.relatedTarget !== null || event.currentTarget.ownerDocument.hasFocus();\n if (!departed) return;\n setFocused(false);\n setHeld(false);\n };\n\n return { held: disabled && isHeld, onFocus, onBlur };\n}\n"],"mappings":"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"}
@@ -0,0 +1,149 @@
1
+ "use client";
2
+ import { cn as e } from "../../lib/cn.mjs";
3
+ import { hasWarned as t, isDev as n, warnOnce as r } from "../../lib/dev.mjs";
4
+ import { composeEventHandlers as i } from "../../lib/composeEventHandlers.mjs";
5
+ import { tagGroupClassNames as ee } from "../../lib/classNames.mjs";
6
+ import { useEventCallback as a } from "../../hooks/useEventCallback.mjs";
7
+ import { useControllable as te } from "../../hooks/useControllable.mjs";
8
+ import { getFirstTabbable as ne, isHiddenInput as o } from "../../lib/focus.mjs";
9
+ import { useMergedRefs as re } from "../../hooks/useMergedRefs.mjs";
10
+ import { TOOLBAR_ITEM_SELECTOR as s, useToolbarNavigation as ie } from "../button/Toolbar.navigation.mjs";
11
+ import { useFormReset as ae } from "../../hooks/useFormReset.mjs";
12
+ import { HiddenInput as oe } from "../internal/HiddenInput.mjs";
13
+ import { EMPTY_VALUES as c, sameValues as se, toggleValue as ce } from "../input/pickerValues.mjs";
14
+ import { TAG_ITEM_SELECTOR as l, TagGroupContext as le, TagGroupHostContext as u } from "./TagGroup.context.mjs";
15
+ import * as d from "react";
16
+ import { jsx as f, jsxs as ue } from "react/jsx-runtime";
17
+ //#region src/components/data-display/TagGroup.tsx
18
+ var p = {
19
+ medium: "gap-2",
20
+ small: "gap-1.5",
21
+ "extra-small": "gap-1"
22
+ }, m = "medium", h = "filled", g = "circular", de = /* @__PURE__ */ new Set([
23
+ "filled",
24
+ "outline",
25
+ "brand"
26
+ ]), _ = /* @__PURE__ */ new Set(["circular", "rounded"]);
27
+ function v(e) {
28
+ return `TagGroup: several tags share the value "${e}". Give every tag of a group its own \`value\`: \`onDismiss\` and the selection cannot tell them apart.`;
29
+ }
30
+ var fe = "TagGroup: `role=\"listbox\"` is not supported (an option cannot hold a Dismiss button); the group keeps the toolbar keyboard. Use InteractionTags in the default toolbar for selectable tags, or Listbox for a list of values.", pe = "TagGroup: this toolbar has tags but no control (no Dismiss button, InteractionTag or link). Pass `role=\"list\"` for tags that only display.";
31
+ function me(e) {
32
+ let [t] = d.useState(() => /* @__PURE__ */ new Map()), n = d.useRef(e);
33
+ return d.useLayoutEffect(() => {
34
+ if (n.current = e, e) for (let [e, n] of t) n > 1 && r(`TagGroup:duplicate-value:${e}`, v(e));
35
+ }, [t, e]), d.useCallback((e) => {
36
+ let i = (t.get(e) ?? 0) + 1;
37
+ return t.set(e, i), i > 1 && n.current && r(`TagGroup:duplicate-value:${e}`, v(e)), () => {
38
+ let n = (t.get(e) ?? 1) - 1;
39
+ n > 0 ? t.set(e, n) : t.delete(e);
40
+ };
41
+ }, [t]);
42
+ }
43
+ function he(e) {
44
+ for (let t of e.querySelectorAll(l)) if (t.parentElement?.closest("[data-wave-tag-group]") === e) return !0;
45
+ return !1;
46
+ }
47
+ function ge(e) {
48
+ for (let t of e.querySelectorAll(s)) if (!o(t) && !t.hasAttribute("data-wave-hidden-input")) return !0;
49
+ return !1;
50
+ }
51
+ var y = ({ role: o, orientation: s = "horizontal", size: l = m, appearance: v = h, shape: y = g, disabled: b = !1, dismissible: x = !1, onDismiss: S, fallbackFocusRef: C, value: _e, defaultValue: w, onValueChange: T, name: ve, form: E, required: ye = !1, className: be, onKeyDown: D, onFocus: O, "aria-orientation": k, "aria-disabled": xe, ref: A, children: Se, ...Ce }) => {
52
+ let j = o ?? "toolbar", M = j === "list" ? "list" : "toolbar", N = M === "toolbar", P = Object.hasOwn(p, l) ? l : m, F = de.has(v) ? v : h, I = _.has(y) ? y : g, L = d.useContext(u), R = L?.removalFocus ?? "next-then-previous", z = L?.hiddenValues, B = L?.dimDisabled ?? !0, V = d.useRef(null), { containerProps: H } = ie({ orientation: s }), we = re(V, N ? H.ref : null, A), [U, W, Te] = te(_e, w ?? c, (e) => T?.([...e])), G = Te || w !== void 0 || T !== void 0, K = d.useMemo(() => new Set(U), [U]), q = d.useCallback((e) => G && K.has(e), [G, K]), J = a((e) => {
53
+ G && !b && W((t) => ce(t, e).values);
54
+ }), Y = a((e) => {
55
+ G && W((t) => t.includes(e) ? t.filter((t) => t !== e) : t);
56
+ });
57
+ ae(V, () => {
58
+ G && W((e) => {
59
+ let t = w ?? c;
60
+ return se(e, t) ? e : [...t];
61
+ });
62
+ }, E);
63
+ let Ee = () => {
64
+ let e = V.current;
65
+ e && ne(e)?.focus();
66
+ }, X = S !== void 0, Z = me(X || G), Q = a((e) => {
67
+ S?.(e);
68
+ }), De = d.useMemo(() => ({
69
+ mode: M,
70
+ size: P,
71
+ appearance: F,
72
+ shape: I,
73
+ disabled: b,
74
+ dismissible: x,
75
+ dismiss: Q,
76
+ reportsDismissals: X,
77
+ selecting: G,
78
+ isSelected: q,
79
+ toggle: J,
80
+ deselect: Y,
81
+ register: Z,
82
+ rootRef: V,
83
+ fallbackFocusRef: C,
84
+ removalFocus: R,
85
+ hiddenValues: z,
86
+ dimDisabled: B
87
+ }), [
88
+ M,
89
+ P,
90
+ F,
91
+ I,
92
+ b,
93
+ x,
94
+ Q,
95
+ X,
96
+ G,
97
+ q,
98
+ J,
99
+ Y,
100
+ Z,
101
+ C,
102
+ R,
103
+ z,
104
+ B
105
+ ]), $ = j === "listbox";
106
+ return d.useEffect(() => {
107
+ $ && r("TagGroup:listbox-role", fe);
108
+ }, [$]), d.useEffect(() => {
109
+ if (!n || !N || b || t("TagGroup:static-toolbar")) return;
110
+ let e = V.current;
111
+ e && he(e) && !ge(e) && r("TagGroup:static-toolbar", pe);
112
+ }), /* @__PURE__ */ f(le.Provider, {
113
+ value: De,
114
+ children: /* @__PURE__ */ f(u.Provider, {
115
+ value: null,
116
+ children: /* @__PURE__ */ ue("div", {
117
+ "data-orientation": s,
118
+ "data-size": P,
119
+ "data-appearance": F,
120
+ "data-shape": I,
121
+ "data-disabled": b ? "" : void 0,
122
+ "data-wave-tag-group": "",
123
+ ...Ce,
124
+ ref: we,
125
+ role: j,
126
+ "aria-orientation": j === "toolbar" ? k ?? s : k,
127
+ "aria-disabled": N && b ? !0 : xe,
128
+ "data-roving-container": N ? "" : void 0,
129
+ onKeyDown: N ? i(D, H.onKeyDown) : D,
130
+ onFocus: N ? i(O, H.onFocus, { checkDefaultPrevented: !1 }) : O,
131
+ className: e(ee.root, "relative inline-flex flex-wrap items-center", p[P], s === "vertical" && "flex-col items-start", be),
132
+ children: [Se, G && /* @__PURE__ */ f(oe, {
133
+ type: "text",
134
+ name: ve,
135
+ form: E,
136
+ disabled: b,
137
+ value: U,
138
+ required: ye,
139
+ onInvalid: Ee
140
+ })]
141
+ })
142
+ })
143
+ });
144
+ };
145
+ y.displayName = "TagGroup";
146
+ //#endregion
147
+ export { y as TagGroup };
148
+
149
+ //# sourceMappingURL=TagGroup.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TagGroup.mjs","names":["React","tagGroupGapClasses","DEFAULT_SIZE","DEFAULT_APPEARANCE","DEFAULT_SHAPE","KNOWN_APPEARANCES","KNOWN_SHAPES","duplicateValueMessage","value","LISTBOX_ROLE_MESSAGE","STATIC_TOOLBAR_MESSAGE","useValueRegistry","counts","reportsRef","reportsValues","count","warnOnce","remaining","hasOwnTags","item","root","TAG_ITEM_SELECTOR","hasToolbarControl","element","TOOLBAR_ITEM_SELECTOR","isHiddenInput","TagGroup","roleProp","sizeProp","appearanceProp","shapeProp","valueProp","ariaOrientation","ariaDisabled","rest","role","mode","isToolbar","size","appearance","shape","host","TagGroupHostContext","removalFocus","hiddenValues","dimDisabled","rootRef","useToolbarNavigation","mergedRef","useMergedRefs","containerProps","ref","values","setValues","isControlled","useControllable","defaultValue","EMPTY_VALUES","onValueChange","next","selecting","selectedValues","isSelected","toggle","useEventCallback","toggleValue","current","deselect","selected","initial","sameValues","form","focusTabStop","getFirstTabbable","reportsDismissals","onDismiss","register","dismiss","context","disabled","dismissible","fallbackFocusRef","isListbox","isDev","hasWarned","TagGroupContext","orientation","composeEventHandlers","onKeyDown","onFocus","cn","tagGroupClassNames","className","children","HiddenInput"],"sources":["../../../src/components/data-display/TagGroup.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { tagGroupClassNames } from '../../lib/classNames';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { hasWarned, isDev, warnOnce } from '../../lib/dev';\nimport { getFirstTabbable, isHiddenInput } from '../../lib/focus';\nimport type { Orientation, TagAppearance, TagShape, TagSize } from '../../lib/types';\nimport { useControllable } from '../../hooks/useControllable';\nimport { useEventCallback } from '../../hooks/useEventCallback';\nimport { useFormReset } from '../../hooks/useFormReset';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { TOOLBAR_ITEM_SELECTOR, useToolbarNavigation } from '../button/Toolbar.navigation';\nimport { HiddenInput } from '../internal/HiddenInput';\nimport { EMPTY_VALUES, sameValues, toggleValue } from '../input/pickerValues';\nimport {\n TAG_GROUP_SELECTOR,\n TAG_ITEM_SELECTOR,\n TagGroupContext,\n TagGroupHostContext,\n} from './TagGroup.context';\nimport type { TagGroupContextValue, TagGroupMode } from './TagGroup.context';\n\n/** Props of {@link TagGroup}: the element that holds the tags (a `<div>`). */\nexport interface TagGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'defaultValue'> {\n /**\n * `'toolbar'`: one Tab stop and arrow keys, for tags that are actions (dismissible tags,\n * InteractionTags, links). `'list'`: a list (every tag gets `role=\"listitem\"`), each control\n * its own Tab stop and no arrow keys, for tags that only display. Another role keeps the\n * toolbar keyboard; `'listbox'` is not supported (an option cannot hold a Dismiss button).\n * Name the group with `aria-label` or `aria-labelledby`.\n * @default 'toolbar'\n */\n role?: React.AriaRole;\n /**\n * Layout and arrow-key axis, as on Toolbar: Left/Right (mirrored in RTL) when horizontal,\n * Up/Down when vertical; a wrapping set moves in DOM order. `aria-orientation` (toolbar role)\n * and `data-orientation`.\n * @default 'horizontal'\n */\n orientation?: Orientation;\n /** Default `size` of its tags (a tag's own wins) and the gap between them. @default 'medium' */\n size?: TagSize;\n /** Default `appearance` of its tags (a tag's own wins). @default 'filled' */\n appearance?: TagAppearance;\n /** Default `shape` of its tags (a tag's own wins). @default 'circular' */\n shape?: TagShape;\n /**\n * Disables every tag: a tag cannot re-enable itself. A control that has focus keeps it,\n * unavailable, until focus leaves it. `aria-disabled` (not with `role=\"list\"`), `data-disabled`.\n * @default false\n */\n disabled?: boolean;\n /**\n * Default `dismissible` of its Tags (a Tag's own value wins). An InteractionTag is dismissible\n * when it has an `InteractionTag.Secondary`.\n * @default false\n */\n dismissible?: boolean;\n /**\n * Called with the tag's `value` on every dismissal: a click, Enter or Space on a Dismiss\n * button, or Delete or Backspace on a tag (Fluent's `onDismiss(event, { value })`). Remove the\n * tag from your state; when it goes, focus moves to the next tag, else the previous one, else\n * `fallbackFocusRef`. Fluent's TagGroup moves focus as it calls `onDismiss`, whether or not the\n * tag goes, and has no fallback. Dismissing a selected tag also removes its value from the\n * selection; Fluent's TagGroup leaves the selection alone.\n */\n onDismiss?: (value: string) => void;\n /**\n * Where focus goes when the tag that had it is removed and no other tag can take it, for\n * example an \"Add\" or \"Reset filters\" button outside the group. WaveUI-only.\n */\n fallbackFocusRef?: React.RefObject<HTMLElement | null>;\n /**\n * The selected values (controlled), in selection order: the `value`s of the pressed\n * InteractionTags. Passing `value`, `defaultValue` or `onValueChange` makes the group select\n * (Fluent's `selectedValues`). Plain Tags are never selected.\n */\n value?: readonly string[];\n /** Initially selected values (uncontrolled; Fluent's `defaultSelectedValues`). @default [] */\n defaultValue?: readonly string[];\n /** Called with the new selected values when they change (Fluent's `onTagSelect`). */\n onValueChange?: (value: string[]) => void;\n /**\n * Form field name: one hidden input per selected value (a selecting group only). Fluent's\n * TagGroup submits nothing.\n */\n name?: string;\n /** The id of the form the hidden inputs belong to, when the group is outside it. */\n form?: string;\n /**\n * An empty selection blocks the form's submission (native validation only: a toolbar has no\n * `aria-required`).\n * @default false\n */\n required?: boolean;\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** The gap between the tags per size: 8, 6 or 4px. */\nconst tagGroupGapClasses: Readonly<Record<TagSize, string>> = {\n medium: 'gap-2',\n small: 'gap-1.5',\n 'extra-small': 'gap-1',\n};\n\nconst DEFAULT_SIZE: TagSize = 'medium';\nconst DEFAULT_APPEARANCE: TagAppearance = 'filled';\nconst DEFAULT_SHAPE: TagShape = 'circular';\nconst KNOWN_APPEARANCES: ReadonlySet<string> = new Set(['filled', 'outline', 'brand']);\nconst KNOWN_SHAPES: ReadonlySet<string> = new Set(['circular', 'rounded']);\n\nfunction duplicateValueMessage(value: string): string {\n return `TagGroup: several tags share the value \"${value}\". Give every tag of a group its own \\`value\\`: \\`onDismiss\\` and the selection cannot tell them apart.`;\n}\n\nconst LISTBOX_ROLE_MESSAGE =\n 'TagGroup: `role=\"listbox\"` is not supported (an option cannot hold a Dismiss button); the group keeps the toolbar keyboard. Use InteractionTags in the default toolbar for selectable tags, or Listbox for a list of values.';\n\nconst STATIC_TOOLBAR_MESSAGE =\n 'TagGroup: this toolbar has tags but no control (no Dismiss button, InteractionTag or link). Pass `role=\"list\"` for tags that only display.';\n\n/**\n * The value registry of one TagGroup: a count per explicit tag `value` (registered from the tags'\n * layout effects). A second tag with one value warns once per value while the group reports\n * values (`reportsValues`), also when it starts reporting them after the tags mounted.\n */\nfunction useValueRegistry(reportsValues: boolean): TagGroupContextValue['register'] {\n const [counts] = React.useState(() => new Map<string, number>());\n const reportsRef = React.useRef(reportsValues);\n\n React.useLayoutEffect(() => {\n reportsRef.current = reportsValues;\n if (!reportsValues) return;\n for (const [value, count] of counts) {\n if (count > 1) warnOnce(`TagGroup:duplicate-value:${value}`, duplicateValueMessage(value));\n }\n }, [counts, reportsValues]);\n\n return React.useCallback(\n (value: string) => {\n const count = (counts.get(value) ?? 0) + 1;\n counts.set(value, count);\n if (count > 1 && reportsRef.current) {\n warnOnce(`TagGroup:duplicate-value:${value}`, duplicateValueMessage(value));\n }\n return () => {\n const remaining = (counts.get(value) ?? 1) - 1;\n if (remaining > 0) counts.set(value, remaining);\n else counts.delete(value);\n };\n },\n [counts],\n );\n}\n\n/** Whether `root` holds tags of its own (not only those of a nested group). */\nfunction hasOwnTags(root: HTMLElement): boolean {\n for (const item of root.querySelectorAll(TAG_ITEM_SELECTOR)) {\n if (item.parentElement?.closest(TAG_GROUP_SELECTOR) === root) return true;\n }\n return false;\n}\n\n/**\n * Whether `root` holds a control a toolbar can move to: a form input of the group's own (a\n * `type=\"hidden\"` input, or the invisible input of a required group) is none.\n */\nfunction hasToolbarControl(root: HTMLElement): boolean {\n for (const element of root.querySelectorAll(TOOLBAR_ITEM_SELECTOR)) {\n if (!isHiddenInput(element) && !element.hasAttribute('data-wave-hidden-input')) return true;\n }\n return false;\n}\n\n/**\n * A set of tags with one keyboard model: by default an APG toolbar (`role=\"toolbar\"`), or with\n * `role=\"list\"` a list of tags that only display.\n *\n * - **Toolbar** (the default): one Tab stop, the last focused control (at first the first enabled\n * one). Left/Right move between the controls when horizontal (mirrored in RTL), Up/Down when\n * `orientation=\"vertical\"`, the other axis does nothing; the arrows wrap, in DOM order across\n * wrapped rows, and Home/End jump to the first and last control, the same rule as Toolbar.\n * Natively disabled controls are skipped. Use it for tags that are actions: dismissible tags,\n * InteractionTags (both of their buttons join the arrow order), link chips. Renders\n * `aria-orientation`, and `aria-disabled` while disabled.\n * - **List** (`role=\"list\"`): every tag is a list item (`role=\"listitem\"`), every control keeps\n * its own Tab stop and the group handles no arrow key. A tag rendered as a link or a button\n * takes no list item role: put the link in the tag's content (`<Tag><a href>…</a></Tag>`), or\n * wrap the tag in an element with `role=\"listitem\"`.\n * - **Delete and Backspace** on a tag's Dismiss button, on either button of an InteractionTag with\n * an `InteractionTag.Secondary`, or on a link in a dismissible tag's text, dismiss the tag (a\n * held key dismisses one tag). Enter, Space and a click on the Dismiss button do too.\n * - **Defaults for its tags**: `size`, `appearance`, `shape` and, for Tags, `dismissible` (a tag's\n * own value wins; an InteractionTag is dismissible when it has a Secondary), and `disabled`,\n * which disables every tag (a tag cannot re-enable itself).\n * - **`onDismiss(value)`** is called on every dismissal, after the Tag's own `onDismiss` or the\n * Secondary's `onClick` (whose `preventDefault()` keeps the group from being called). Remove\n * the tag from your state: when the tag that had focus goes, focus moves to the next tag, else\n * the previous one, else `fallbackFocusRef`, so it never falls to the page. Give every tag a\n * `value`. A dismissal you refuse (the tag stays) moves no focus.\n * - **Selection**: with `value`, `defaultValue` or `onValueChange` (Fluent's `selectedValues`,\n * `defaultSelectedValues` and `onTagSelect`) the group selects. Each `InteractionTag.Primary`\n * is then a toggle (`aria-pressed`): a click, Enter or Space adds its tag's `value` to the\n * selection or removes it, after the Primary's `onClick` (whose `preventDefault()` cancels the\n * toggle); the arrow keys only move. `onValueChange` receives the values in selection order,\n * and values without a tag are kept. Dismissing a selected InteractionTag calls `onDismiss`\n * and then `onValueChange` without its value. Plain Tags are never selected; selection is\n * multiple only (RadioGroup or `Toolbar.RadioGroup` for a single choice).\n * - **Forms**: a selecting group with `name` submits one entry per selected value, also from\n * outside its form with `form`; `required` blocks the submission of an empty selection and\n * moves focus to the group's Tab stop (native validation only, no `aria-required`); a form\n * reset restores `defaultValue`. A disabled group submits nothing, and a group that does not\n * select renders no form input.\n * - No accessible name by default: name the group with `aria-label` or `aria-labelledby`.\n * - Renders `data-orientation`, `data-size`, `data-appearance`, `data-shape` and, while\n * disabled, `data-disabled`.\n *\n * @example\n * const [filters, setFilters] = React.useState(['Red', 'Blue', 'Large']);\n * const reset = React.useRef<HTMLButtonElement>(null);\n * <TagGroup\n * aria-label=\"Active filters\"\n * dismissible\n * fallbackFocusRef={reset}\n * onDismiss={(value) => setFilters((current) => current.filter((f) => f !== value))}\n * >\n * {filters.map((filter) => (\n * <Tag key={filter} value={filter}>\n * {filter}\n * </Tag>\n * ))}\n * </TagGroup>\n * <Button ref={reset} onClick={() => setFilters(['Red', 'Blue', 'Large'])}>Reset filters</Button>\n *\n * @example\n * // Selectable chips, submitted with a form as \"fruit\".\n * const [fruit, setFruit] = React.useState<string[]>(['plum']);\n * <TagGroup aria-label=\"Fruit\" name=\"fruit\" value={fruit} onValueChange={setFruit}>\n * <InteractionTag value=\"cherry\">\n * <InteractionTag.Primary>Cherry</InteractionTag.Primary>\n * </InteractionTag>\n * <InteractionTag value=\"plum\">\n * <InteractionTag.Primary>Plum</InteractionTag.Primary>\n * </InteractionTag>\n * </TagGroup>\n */\nexport const TagGroup = ({\n role: roleProp,\n orientation = 'horizontal',\n size: sizeProp = DEFAULT_SIZE,\n appearance: appearanceProp = DEFAULT_APPEARANCE,\n shape: shapeProp = DEFAULT_SHAPE,\n disabled = false,\n dismissible = false,\n onDismiss,\n fallbackFocusRef,\n value: valueProp,\n defaultValue,\n onValueChange,\n name,\n form,\n required = false,\n className,\n onKeyDown,\n onFocus,\n 'aria-orientation': ariaOrientation,\n 'aria-disabled': ariaDisabled,\n ref,\n children,\n ...rest\n}: TagGroupProps) => {\n const role = roleProp ?? 'toolbar';\n const mode: TagGroupMode = role === 'list' ? 'list' : 'toolbar';\n const isToolbar = mode === 'toolbar';\n // An untyped value outside the unions falls back to the default look.\n const size: TagSize = Object.hasOwn(tagGroupGapClasses, sizeProp) ? sizeProp : DEFAULT_SIZE;\n const appearance: TagAppearance = KNOWN_APPEARANCES.has(appearanceProp)\n ? appearanceProp\n : DEFAULT_APPEARANCE;\n const shape: TagShape = KNOWN_SHAPES.has(shapeProp) ? shapeProp : DEFAULT_SHAPE;\n\n // The host options apply to this group only: a nested group reads `null` (provided below).\n const host = React.useContext(TagGroupHostContext);\n const removalFocus = host?.removalFocus ?? 'next-then-previous';\n const hiddenValues = host?.hiddenValues;\n const dimDisabled = host?.dimDisabled ?? true;\n\n const rootRef = React.useRef<HTMLDivElement>(null);\n // In list mode the roving container is not attached, so the controls keep their own Tab stops\n // (and the roving hook hands back the tab indices it stamped while the group was a toolbar).\n const { containerProps } = useToolbarNavigation({ orientation });\n const mergedRef = useMergedRefs<HTMLDivElement>(\n rootRef,\n isToolbar ? containerProps.ref : null,\n ref,\n );\n\n // Selection (D38): the values of the pressed InteractionTags, in selection order. The callback\n // receives a copy of its own, so the app may keep or change it.\n const [values, setValues, isControlled] = useControllable<readonly string[]>(\n valueProp,\n defaultValue ?? EMPTY_VALUES,\n (next) => onValueChange?.([...next]),\n );\n // Any of the three makes the group select; `onValueChange` alone selects from `[]`.\n const selecting = isControlled || defaultValue !== undefined || onValueChange !== undefined;\n const selectedValues = React.useMemo(() => new Set(values), [values]);\n const isSelected = React.useCallback(\n (value: string) => selecting && selectedValues.has(value),\n [selecting, selectedValues],\n );\n const toggle = useEventCallback((value: string) => {\n if (!selecting || disabled) return;\n setValues((current) => toggleValue(current, value).values);\n });\n // A dismissed InteractionTag leaves the selection (D39); a value that is not selected changes\n // nothing (the same array is no change, so `onValueChange` is not called).\n const deselect = useEventCallback((value: string) => {\n if (!selecting) return;\n setValues((current) =>\n current.includes(value) ? current.filter((selected) => selected !== value) : current,\n );\n });\n\n // A form reset restores `defaultValue`, compared by content (C-FORMS): an unchanged reset\n // reports nothing, also with a new inline default array.\n useFormReset(\n rootRef,\n () => {\n if (!selecting) return;\n setValues((current) => {\n const initial = defaultValue ?? EMPTY_VALUES;\n return sameValues(current, initial) ? current : [...initial];\n });\n },\n form,\n );\n // An empty required selection: the browser reports it at the group's Tab stop (the toolbar's\n // last focused control, or a list's first control).\n const focusTabStop = () => {\n const root = rootRef.current;\n if (root) getFirstTabbable(root)?.focus();\n };\n\n const reportsDismissals = onDismiss !== undefined;\n const register = useValueRegistry(reportsDismissals || selecting);\n const dismiss = useEventCallback((value: string) => {\n onDismiss?.(value);\n });\n\n const context = React.useMemo<TagGroupContextValue>(\n () => ({\n mode,\n size,\n appearance,\n shape,\n disabled,\n dismissible,\n dismiss,\n reportsDismissals,\n selecting,\n isSelected,\n toggle,\n deselect,\n register,\n rootRef,\n fallbackFocusRef,\n removalFocus,\n hiddenValues,\n dimDisabled,\n }),\n [\n mode,\n size,\n appearance,\n shape,\n disabled,\n dismissible,\n dismiss,\n reportsDismissals,\n selecting,\n isSelected,\n toggle,\n deselect,\n register,\n fallbackFocusRef,\n removalFocus,\n hiddenValues,\n dimDisabled,\n ],\n );\n\n const isListbox = role === 'listbox';\n React.useEffect(() => {\n if (isListbox) warnOnce('TagGroup:listbox-role', LISTBOX_ROLE_MESSAGE);\n }, [isListbox]);\n\n // A toolbar of tags that only display has nothing to move to: it should be a list. Checked\n // after every commit (the tags are found in the DOM, so a tag written in a Server Component\n // counts too), in development only.\n React.useEffect(() => {\n if (!isDev || !isToolbar || disabled || hasWarned('TagGroup:static-toolbar')) return;\n const root = rootRef.current;\n if (root && hasOwnTags(root) && !hasToolbarControl(root)) {\n warnOnce('TagGroup:static-toolbar', STATIC_TOOLBAR_MESSAGE);\n }\n });\n\n return (\n <TagGroupContext.Provider value={context}>\n <TagGroupHostContext.Provider value={null}>\n <div\n data-orientation={orientation}\n data-size={size}\n data-appearance={appearance}\n data-shape={shape}\n data-disabled={disabled ? '' : undefined}\n data-wave-tag-group=\"\"\n {...rest}\n ref={mergedRef}\n role={role}\n // `aria-orientation` is allowed on a toolbar, not on a list (axe `aria-allowed-attr`);\n // `aria-disabled` is supported on widget and group roles, not on a list (ARIA 1.2).\n aria-orientation={role === 'toolbar' ? (ariaOrientation ?? orientation) : ariaOrientation}\n aria-disabled={isToolbar && disabled ? true : ariaDisabled}\n data-roving-container={isToolbar ? '' : undefined}\n onKeyDown={\n isToolbar ? composeEventHandlers(onKeyDown, containerProps.onKeyDown) : onKeyDown\n }\n onFocus={\n isToolbar\n ? composeEventHandlers(onFocus, containerProps.onFocus, {\n checkDefaultPrevented: false,\n })\n : onFocus\n }\n className={cn(\n tagGroupClassNames.root,\n 'relative inline-flex flex-wrap items-center',\n tagGroupGapClasses[size],\n orientation === 'vertical' && 'flex-col items-start',\n className,\n )}\n >\n {children}\n {/* Inside the root (`relative`): a required input sits at its corner, and a focused one\n hands focus back to the group. Only a selecting group has values to submit. */}\n {selecting && (\n <HiddenInput\n type=\"text\"\n name={name}\n form={form}\n disabled={disabled}\n value={values}\n required={required}\n onInvalid={focusTabStop}\n />\n )}\n </div>\n </TagGroupHostContext.Provider>\n </TagGroupContext.Provider>\n );\n};\nTagGroup.displayName = 'TagGroup';\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use client";var e={medium:24,small:20,"extra-small":16},t=`inline-flex items-center border`,n={medium:`min-h-7 gap-1 text-body-1`,small:`min-h-6 gap-1 text-caption-1`,"extra-small":`min-h-5 gap-0.5 text-caption-1`},r={medium:{text:`ps-2.75`,icon:`ps-1.75`,media:`ps-px`},small:{text:`ps-1.75`,icon:`ps-1.25`,media:`ps-px`},"extra-small":{text:`ps-1.75`,icon:`ps-1.25`,media:`ps-px`}},i={medium:{text:`pe-2.75`,dismiss:`pe-1.25`},small:{text:`pe-1.75`,dismiss:`pe-0.75`},"extra-small":{text:`pe-1.75`,dismiss:`pe-px`}},a={filled:`border-transparent bg-muted text-foreground`,outline:`border-stroke bg-transparent text-foreground`,brand:`border-transparent bg-selected text-selected-foreground`},o=`border-primary bg-primary text-primary-foreground`,s=`opacity-50 focus-visible:opacity-100 has-focus-visible:opacity-100`,c={circular:`rounded-full`,rounded:`rounded`},l={medium:`inline-flex shrink-0 me-1`,small:`inline-flex shrink-0 me-0.5`,"extra-small":`inline-flex shrink-0 me-0.5`},u={medium:`inline-flex size-5 shrink-0 items-center justify-center [&>svg]:size-full`,small:`inline-flex size-4 shrink-0 items-center justify-center [&>svg]:size-full`,"extra-small":`inline-flex size-3 shrink-0 items-center justify-center [&>svg]:size-full`},d=`inline-flex min-w-0 flex-col text-start`,f=`text-caption-1 leading-3.5`,p=`text-caption-2 leading-3`,m=`relative inline-flex shrink-0 items-center justify-center rounded-full border-0 bg-transparent p-0 text-inherit before:absolute disabled:cursor-not-allowed aria-disabled:cursor-not-allowed forced-colors:not-disabled:not-aria-disabled:hover:text-[Highlight]`,h={medium:`size-5 before:-inset-0.5`,small:`size-4 before:-inset-1`,"extra-small":`size-4 before:-inset-1`},g={medium:``,small:``,"extra-small":`focus-visible:outline-offset-0`},_={medium:12,small:12,"extra-small":10},v={filled:`not-disabled:not-aria-disabled:hover:bg-muted-hover not-disabled:not-aria-disabled:active:bg-muted-pressed`,outline:`not-disabled:not-aria-disabled:hover:bg-muted-hover not-disabled:not-aria-disabled:active:bg-muted-pressed`,brand:`not-disabled:not-aria-disabled:hover:bg-selected-hover not-disabled:not-aria-disabled:active:bg-selected-pressed`,selected:`not-disabled:not-aria-disabled:hover:bg-primary-hover not-disabled:not-aria-disabled:active:bg-primary-pressed`},y=`focus-visible:outline-primary-foreground`,b=`relative inline-flex items-center border transition-colors motion-reduce:transition-none focus-visible:z-10`,x=`py-0 text-start`,S={medium:`pe-2.75 group-has-[[data-wave-tag-dismiss]]/interaction-tag:pe-2`,small:`pe-1.75 group-has-[[data-wave-tag-dismiss]]/interaction-tag:pe-1.5`,"extra-small":`pe-1.75 group-has-[[data-wave-tag-dismiss]]/interaction-tag:pe-1`},C=`group-has-[[data-wave-tag-dismiss]]/interaction-tag:rounded-e-none group-has-[[data-wave-tag-dismiss]]/interaction-tag:border-e-0`,w=`shrink-0 justify-center p-0 forced-colors:not-disabled:not-aria-disabled:hover:text-[Highlight]`,T=`px-1.5`,E={medium:`min-w-7 text-body-1`,small:`min-w-6 text-caption-1`,"extra-small":`min-w-6 text-caption-1`},D={filled:`border-transparent bg-muted text-foreground not-disabled:not-aria-disabled:hover:bg-muted-hover not-disabled:not-aria-disabled:active:bg-muted-pressed`,outline:`border-stroke bg-transparent text-foreground not-disabled:not-aria-disabled:hover:border-stroke-hover not-disabled:not-aria-disabled:hover:bg-subtle-hover not-disabled:not-aria-disabled:active:bg-subtle-pressed`,brand:`border-transparent bg-selected text-selected-foreground not-disabled:not-aria-disabled:hover:bg-selected-hover not-disabled:not-aria-disabled:active:bg-selected-pressed`,selected:`border-primary bg-primary text-primary-foreground not-disabled:not-aria-disabled:hover:bg-primary-hover not-disabled:not-aria-disabled:active:bg-primary-pressed`},O=`forced-colors:border-[Highlight]`,k={filled:`border-s-stroke`,outline:`border-s-stroke`,brand:`border-s-stroke`,selected:`border-s-primary-foreground`},A={circular:`rounded-s-none rounded-e-full`,rounded:`rounded-s-none rounded-e`},j=`disabled:cursor-not-allowed disabled:opacity-50`,M=`disabled:cursor-not-allowed aria-disabled:cursor-not-allowed`,N=`before:absolute before:inset-x-0 before:-inset-y-0.5`;function P(e){let{size:o,appearance:s,shape:l}=e;return[t,n[o],r[o].text,i[o].text,a[s],c[l]].join(` `)}exports.INTERACTION_TAG_BUTTON_BASE_CLASSES=b,exports.INTERACTION_TAG_DISABLED_CLASSES=j,exports.INTERACTION_TAG_EXTRA_SMALL_HIT_CLASSES=N,exports.INTERACTION_TAG_PRIMARY_BASE_CLASSES=x,exports.INTERACTION_TAG_PRIMARY_JOINED_CLASSES=C,exports.INTERACTION_TAG_SECONDARY_BASE_CLASSES=w,exports.INTERACTION_TAG_SECONDARY_LABEL_CLASSES=T,exports.INTERACTION_TAG_SELECTED_FORCED_COLORS=O,exports.INTERACTION_TAG_UNDIMMED_DISABLED_CLASSES=M,exports.TAG_AVATAR_SIZE=e,exports.TAG_DISABLED_DIM_CLASSES=s,exports.TAG_DISMISS_BASE_CLASSES=m,exports.TAG_DISMISS_GLYPH_SIZE=_,exports.TAG_DISMISS_SELECTED_RING_CLASSES=y,exports.TAG_PRIMARY_TEXT_TWO_LINE_CLASSES=f,exports.TAG_ROOT_BASE_CLASSES=t,exports.TAG_SECONDARY_TEXT_CLASSES=p,exports.TAG_SELECTED_CLASSES=o,exports.TAG_TEXT_COLUMN_CLASSES=d,exports.interactionTagDividerClasses=k,exports.interactionTagLookClasses=D,exports.interactionTagPrimaryEndInsetClasses=S,exports.interactionTagSecondaryShapeClasses=A,exports.interactionTagSecondarySizeClasses=E,exports.tagAppearanceClasses=a,exports.tagDismissRingClasses=g,exports.tagDismissSizeClasses=h,exports.tagDismissStateClasses=v,exports.tagEndInsetClasses=i,exports.tagIconClasses=u,exports.tagMediaClasses=l,exports.tagRootClasses=P,exports.tagShapeClasses=c,exports.tagSizeClasses=n,exports.tagStartInsetClasses=r;
2
+ //# sourceMappingURL=tagStyles.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tagStyles.cjs","names":["TAG_AVATAR_SIZE","TAG_ROOT_BASE_CLASSES","tagSizeClasses","tagStartInsetClasses","tagEndInsetClasses","tagAppearanceClasses","TAG_SELECTED_CLASSES","TAG_DISABLED_DIM_CLASSES","tagShapeClasses","tagMediaClasses","tagIconClasses","TAG_TEXT_COLUMN_CLASSES","TAG_PRIMARY_TEXT_TWO_LINE_CLASSES","TAG_SECONDARY_TEXT_CLASSES","TAG_DISMISS_BASE_CLASSES","tagDismissSizeClasses","tagDismissRingClasses","TAG_DISMISS_GLYPH_SIZE","tagDismissStateClasses","TAG_DISMISS_SELECTED_RING_CLASSES","INTERACTION_TAG_BUTTON_BASE_CLASSES","INTERACTION_TAG_PRIMARY_BASE_CLASSES","interactionTagPrimaryEndInsetClasses","INTERACTION_TAG_PRIMARY_JOINED_CLASSES","INTERACTION_TAG_SECONDARY_BASE_CLASSES","INTERACTION_TAG_SECONDARY_LABEL_CLASSES","interactionTagSecondarySizeClasses","interactionTagLookClasses","INTERACTION_TAG_SELECTED_FORCED_COLORS","interactionTagDividerClasses","interactionTagSecondaryShapeClasses","INTERACTION_TAG_DISABLED_CLASSES","INTERACTION_TAG_UNDIMMED_DISABLED_CLASSES","INTERACTION_TAG_EXTRA_SMALL_HIT_CLASSES","tagRootClasses","options","size","appearance","shape"],"sources":["../../../src/components/data-display/tagStyles.ts"],"sourcesContent":["/**\n * The look of the tag family: the size table, the appearances, the shapes, the Dismiss button and\n * the Avatar box per size. Tag reads it, and so do InteractionTag and TagPicker's tags.\n * Module-only (not in the barrel), as `buttonStyles.ts`. Class strings stay literal for\n * Tailwind's scanner. Every inset includes the tag's 1px border, so a filled and an outline tag of\n * one size have one box.\n */\nimport type { TagAppearance, TagShape, TagSize } from '../../lib/types';\n\n/** The box (px) an Avatar without its own `size` takes in a tag's `media`, per tag size. */\nexport const TAG_AVATAR_SIZE: Readonly<Record<TagSize, 16 | 20 | 24>> = {\n medium: 24,\n small: 20,\n 'extra-small': 16,\n};\n\n/** The layout every tag root shares: its parts in a centred row, with a 1px border. */\nexport const TAG_ROOT_BASE_CLASSES = 'inline-flex items-center border';\n\n/** Height, gap between the parts and type of a tag per size: 28, 24 or 20px tall. */\nexport const tagSizeClasses: Readonly<Record<TagSize, string>> = {\n medium: 'min-h-7 gap-1 text-body-1',\n small: 'min-h-6 gap-1 text-caption-1',\n 'extra-small': 'min-h-5 gap-0.5 text-caption-1',\n};\n\n/** The part a tag starts with, which sets its start inset. */\nexport type TagLeadingPart = 'text' | 'icon' | 'media';\n\n/** The part a tag ends with, which sets its end inset. */\nexport type TagTrailingPart = 'text' | 'dismiss';\n\n/**\n * Start inset per size and leading part, the border included: 12 or 8px before the text, 8 or\n * 6px before an icon, 2px before media.\n */\nexport const tagStartInsetClasses: Readonly<\n Record<TagSize, Readonly<Record<TagLeadingPart, string>>>\n> = {\n medium: { text: 'ps-2.75', icon: 'ps-1.75', media: 'ps-px' },\n small: { text: 'ps-1.75', icon: 'ps-1.25', media: 'ps-px' },\n 'extra-small': { text: 'ps-1.75', icon: 'ps-1.25', media: 'ps-px' },\n};\n\n/**\n * End inset per size and trailing part, the border included: 12 or 8px after the text; 6, 4 or\n * 2px after the Dismiss button.\n */\nexport const tagEndInsetClasses: Readonly<\n Record<TagSize, Readonly<Record<TagTrailingPart, string>>>\n> = {\n medium: { text: 'pe-2.75', dismiss: 'pe-1.25' },\n small: { text: 'pe-1.75', dismiss: 'pe-0.75' },\n 'extra-small': { text: 'pe-1.75', dismiss: 'pe-px' },\n};\n\n/**\n * Border, fill and text of each appearance: a neutral fill, a border on the surface behind it, or\n * the light brand tint (brand 160 on brand 60 in the light theme, so it follows the brand ramp).\n */\nexport const tagAppearanceClasses: Readonly<Record<TagAppearance, string>> = {\n filled: 'border-transparent bg-muted text-foreground',\n outline: 'border-stroke bg-transparent text-foreground',\n brand: 'border-transparent bg-selected text-selected-foreground',\n};\n\n/**\n * The selected look, over every appearance (the brand fill). Compose it with\n * `forcedColors.selectedContainer`: forced colours remove the fill.\n */\nexport const TAG_SELECTED_CLASSES = 'border-primary bg-primary text-primary-foreground';\n\n/**\n * The dimming of a disabled tag, lifted while a focus ring shows on the root (a held link) or\n * inside it (a held Dismiss button), because opacity would dim the ring too.\n */\nexport const TAG_DISABLED_DIM_CLASSES =\n 'opacity-50 focus-visible:opacity-100 has-focus-visible:opacity-100';\n\n/** Corners per shape: a pill, or 4px. */\nexport const tagShapeClasses: Readonly<Record<TagShape, string>> = {\n circular: 'rounded-full',\n rounded: 'rounded',\n};\n\n/** The media span per size; with the gap, 8, 6 or 4px to the text. */\nexport const tagMediaClasses: Readonly<Record<TagSize, string>> = {\n medium: 'inline-flex shrink-0 me-1',\n small: 'inline-flex shrink-0 me-0.5',\n 'extra-small': 'inline-flex shrink-0 me-0.5',\n};\n\n/** The icon box per size (20, 16 or 12px), whose `<svg>` fills it. */\nexport const tagIconClasses: Readonly<Record<TagSize, string>> = {\n medium: 'inline-flex size-5 shrink-0 items-center justify-center [&>svg]:size-full',\n small: 'inline-flex size-4 shrink-0 items-center justify-center [&>svg]:size-full',\n 'extra-small': 'inline-flex size-3 shrink-0 items-center justify-center [&>svg]:size-full',\n};\n\n/** The layout wrapper of the primary and secondary text, while `secondaryText` renders. */\nexport const TAG_TEXT_COLUMN_CLASSES = 'inline-flex min-w-0 flex-col text-start';\n\n/** The type of the primary text next to a second line (26px of lines in the 28px box). */\nexport const TAG_PRIMARY_TEXT_TWO_LINE_CLASSES = 'text-caption-1 leading-3.5';\n\n/** The type of the second line of text; its colour is inherited. */\nexport const TAG_SECONDARY_TEXT_CLASSES = 'text-caption-2 leading-3';\n\n/**\n * What every Dismiss button draws: a transparent circle with no padding of its own (C-NATIVE),\n * the tag's text colour, a positioned pointer-target layer, the not-allowed cursor of both\n * disabled states and, in forced colours, a Highlight glyph on hover (its hover fill becomes\n * Canvas there).\n */\nexport const TAG_DISMISS_BASE_CLASSES =\n 'relative inline-flex shrink-0 items-center justify-center rounded-full border-0 bg-transparent p-0 text-inherit before:absolute disabled:cursor-not-allowed aria-disabled:cursor-not-allowed forced-colors:not-disabled:not-aria-disabled:hover:text-[Highlight]';\n\n/**\n * The Dismiss button per size: 20 or 16px, with a transparent layer that makes the pointer target\n * 24px (2px beyond each edge of the 20px button, 4px beyond the 16px one).\n */\nexport const tagDismissSizeClasses: Readonly<Record<TagSize, string>> = {\n medium: 'size-5 before:-inset-0.5',\n small: 'size-4 before:-inset-1',\n 'extra-small': 'size-4 before:-inset-1',\n};\n\n/**\n * The ring offset of the Dismiss button per size, after `focusRing`: 0 at `extra-small`, where\n * the tag leaves 2px around the button and a 2px offset would put the ring on the page.\n */\nexport const tagDismissRingClasses: Readonly<Record<TagSize, string>> = {\n medium: '',\n small: '',\n 'extra-small': 'focus-visible:outline-offset-0',\n};\n\n/** The Dismiss glyph (px) per size. */\nexport const TAG_DISMISS_GLYPH_SIZE: Readonly<Record<TagSize, 10 | 12>> = {\n medium: 12,\n small: 12,\n 'extra-small': 10,\n};\n\n/** The look a tag draws: one of its appearances, or the selected look over them. */\nexport type TagLook = TagAppearance | 'selected';\n\n/**\n * The Dismiss button's hover and pressed fills per look, behind the literal gate (C-TOKENS): the\n * neutral states on filled and outline tags (an outline tag is transparent, so its circle must\n * show on the page), the brand states on brand tags, the primary states on selected ones.\n */\nexport const tagDismissStateClasses: Readonly<Record<TagLook, string>> = {\n filled:\n 'not-disabled:not-aria-disabled:hover:bg-muted-hover not-disabled:not-aria-disabled:active:bg-muted-pressed',\n outline:\n 'not-disabled:not-aria-disabled:hover:bg-muted-hover not-disabled:not-aria-disabled:active:bg-muted-pressed',\n brand:\n 'not-disabled:not-aria-disabled:hover:bg-selected-hover not-disabled:not-aria-disabled:active:bg-selected-pressed',\n selected:\n 'not-disabled:not-aria-disabled:hover:bg-primary-hover not-disabled:not-aria-disabled:active:bg-primary-pressed',\n};\n\n/**\n * The Dismiss button's ring colour on a selected tag, after `focusRing`: the default ring colour\n * is the primary fill itself.\n */\nexport const TAG_DISMISS_SELECTED_RING_CLASSES = 'focus-visible:outline-primary-foreground';\n\n// ---------------------------------------------------------------------------\n// InteractionTag: two sibling buttons on the same table\n// ---------------------------------------------------------------------------\n\n/**\n * What both InteractionTag buttons draw: a 1px border, a colour transition switched off for\n * reduced motion, and a raised stacking level while their focus ring shows, so the neighbouring\n * button does not cover the ring.\n */\nexport const INTERACTION_TAG_BUTTON_BASE_CLASSES =\n 'relative inline-flex items-center border transition-colors motion-reduce:transition-none focus-visible:z-10';\n\n/** The Primary button's own layout: no block padding of its own (C-NATIVE), text from the start. */\nexport const INTERACTION_TAG_PRIMARY_BASE_CLASSES = 'py-0 text-start';\n\n/**\n * The Primary's end inset per size, the border included: 12 or 8px alone; next to a Secondary\n * (the root holds a Dismiss control) 8, 6 or 4px, with no end border (the Secondary's start border\n * is the divider). Read from the root's named group in CSS, so the server HTML is right without a\n * registration render.\n */\nexport const interactionTagPrimaryEndInsetClasses: Readonly<Record<TagSize, string>> = {\n medium: 'pe-2.75 group-has-[[data-wave-tag-dismiss]]/interaction-tag:pe-2',\n small: 'pe-1.75 group-has-[[data-wave-tag-dismiss]]/interaction-tag:pe-1.5',\n 'extra-small': 'pe-1.75 group-has-[[data-wave-tag-dismiss]]/interaction-tag:pe-1',\n};\n\n/** The Primary's end corners and end border next to a Secondary: square, and none. */\nexport const INTERACTION_TAG_PRIMARY_JOINED_CLASSES =\n 'group-has-[[data-wave-tag-dismiss]]/interaction-tag:rounded-e-none group-has-[[data-wave-tag-dismiss]]/interaction-tag:border-e-0';\n\n/**\n * What the Secondary (the Dismiss button) draws: its content centred, no padding of its own\n * (C-NATIVE: a glyph Secondary is exactly its minimum width), and in forced colours a Highlight\n * glyph on hover (its hover fill becomes Canvas there).\n */\nexport const INTERACTION_TAG_SECONDARY_BASE_CLASSES =\n 'shrink-0 justify-center p-0 forced-colors:not-disabled:not-aria-disabled:hover:text-[Highlight]';\n\n/**\n * The element that holds the Secondary's children while they render a text label (\"Remove\"):\n * 6px on each side, so the label does not touch the divider or the end of the tag.\n */\nexport const INTERACTION_TAG_SECONDARY_LABEL_CLASSES = 'px-1.5';\n\n/**\n * The Secondary per size: at least 28, 24 or 24px wide (exactly that with a glyph: the 12, 12 or\n * 10px glyph and the borders fit inside), as tall as the Primary (the root stretches both), with\n * the text of the tag's size for a text label.\n */\nexport const interactionTagSecondarySizeClasses: Readonly<Record<TagSize, string>> = {\n medium: 'min-w-7 text-body-1',\n small: 'min-w-6 text-caption-1',\n 'extra-small': 'min-w-6 text-caption-1',\n};\n\n/**\n * Border, fill and text of both buttons per look, with their hover and pressed fills behind the\n * literal gate (C-TOKENS): the neutral states on filled tags, Button's outline states on outline\n * tags, the brand states on brand tags and the primary states on selected ones. Compose the\n * selected look with {@link INTERACTION_TAG_SELECTED_FORCED_COLORS}.\n */\nexport const interactionTagLookClasses: Readonly<Record<TagLook, string>> = {\n filled:\n 'border-transparent bg-muted text-foreground not-disabled:not-aria-disabled:hover:bg-muted-hover not-disabled:not-aria-disabled:active:bg-muted-pressed',\n outline:\n 'border-stroke bg-transparent text-foreground not-disabled:not-aria-disabled:hover:border-stroke-hover not-disabled:not-aria-disabled:hover:bg-subtle-hover not-disabled:not-aria-disabled:active:bg-subtle-pressed',\n brand:\n 'border-transparent bg-selected text-selected-foreground not-disabled:not-aria-disabled:hover:bg-selected-hover not-disabled:not-aria-disabled:active:bg-selected-pressed',\n selected:\n 'border-primary bg-primary text-primary-foreground not-disabled:not-aria-disabled:hover:bg-primary-hover not-disabled:not-aria-disabled:active:bg-primary-pressed',\n};\n\n/**\n * The selected buttons in forced colours, after `forcedColors.selectedContainer` (a Highlight\n * outline inside): a Highlight border too, as a pressed Button draws it.\n */\nexport const INTERACTION_TAG_SELECTED_FORCED_COLORS = 'forced-colors:border-[Highlight]';\n\n/**\n * The divider between the two buttons: the Secondary's start border, after the look's border\n * colour. Decorative: the neutral stroke, or the on-primary colour on a selected tag.\n */\nexport const interactionTagDividerClasses: Readonly<Record<TagLook, string>> = {\n filled: 'border-s-stroke',\n outline: 'border-s-stroke',\n brand: 'border-s-stroke',\n selected: 'border-s-primary-foreground',\n};\n\n/** The Secondary's corners per shape: square at its start, the tag's shape at its end. */\nexport const interactionTagSecondaryShapeClasses: Readonly<Record<TagShape, string>> = {\n circular: 'rounded-s-none rounded-e-full',\n rounded: 'rounded-s-none rounded-e',\n};\n\n/**\n * The disabled look of both buttons while natively disabled; a held button (`aria-disabled`) adds\n * `mergedAriaDisabledClasses`. Each button dims itself: the root is never dimmed.\n */\nexport const INTERACTION_TAG_DISABLED_CLASSES = 'disabled:cursor-not-allowed disabled:opacity-50';\n\n/**\n * The disabled cursor alone, for a host that dims the area holding its tags as a whole (the\n * host's `dimDisabled: false`): no button dims itself then.\n */\nexport const INTERACTION_TAG_UNDIMMED_DISABLED_CLASSES =\n 'disabled:cursor-not-allowed aria-disabled:cursor-not-allowed';\n\n/**\n * The pointer target of both buttons at `extra-small`: a transparent layer 2px above and below\n * the 20px button (24px tall), as wide as the button, so two neighbouring targets never overlap.\n */\nexport const INTERACTION_TAG_EXTRA_SMALL_HIT_CLASSES =\n 'before:absolute before:inset-x-0 before:-inset-y-0.5';\n\n/**\n * The root utilities of a text-only, non-dismissible, unselected, enabled tag of a size,\n * appearance and shape, without a stable class (TagPicker's count chip carries its own).\n *\n * @example\n * <span className={cn(chipClassName, tagRootClasses({ size, appearance, shape }))}>+3</span>\n */\nexport function tagRootClasses(options: {\n size: TagSize;\n appearance: TagAppearance;\n shape: TagShape;\n}): string {\n const { size, appearance, shape } = options;\n return [\n TAG_ROOT_BASE_CLASSES,\n tagSizeClasses[size],\n tagStartInsetClasses[size].text,\n tagEndInsetClasses[size].text,\n tagAppearanceClasses[appearance],\n tagShapeClasses[shape],\n ].join(' ');\n}\n"],"mappings":"aAUA,IAAaA,EAA2D,CACtE,OAAQ,GACR,MAAO,GACP,cAAe,EACjB,EAGaC,EAAwB,kCAGxBC,EAAoD,CAC/D,OAAQ,4BACR,MAAO,+BACP,cAAe,gCACjB,EAYaC,EAET,CACF,OAAQ,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,OAAQ,EAC3D,MAAO,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,OAAQ,EAC1D,cAAe,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,OAAQ,CACpE,EAMaC,EAET,CACF,OAAQ,CAAE,KAAM,UAAW,QAAS,SAAU,EAC9C,MAAO,CAAE,KAAM,UAAW,QAAS,SAAU,EAC7C,cAAe,CAAE,KAAM,UAAW,QAAS,OAAQ,CACrD,EAMaC,EAAgE,CAC3E,OAAQ,8CACR,QAAS,+CACT,MAAO,yDACT,EAMaC,EAAuB,oDAMvBC,EACX,qEAGWC,EAAsD,CACjE,SAAU,eACV,QAAS,SACX,EAGaC,EAAqD,CAChE,OAAQ,4BACR,MAAO,8BACP,cAAe,6BACjB,EAGaC,EAAoD,CAC/D,OAAQ,4EACR,MAAO,4EACP,cAAe,2EACjB,EAGaC,EAA0B,0CAG1BC,EAAoC,6BAGpCC,EAA6B,2BAQ7BC,EACX,mQAMWC,EAA2D,CACtE,OAAQ,2BACR,MAAO,yBACP,cAAe,wBACjB,EAMaC,EAA2D,CACtE,OAAQ,GACR,MAAO,GACP,cAAe,gCACjB,EAGaC,EAA6D,CACxE,OAAQ,GACR,MAAO,GACP,cAAe,EACjB,EAUaC,EAA4D,CACvE,OACE,6GACF,QACE,6GACF,MACE,mHACF,SACE,gHACJ,EAMaC,EAAoC,2CAWpCC,EACX,8GAGWC,EAAuC,kBAQvCC,EAA0E,CACrF,OAAQ,mEACR,MAAO,qEACP,cAAe,kEACjB,EAGaC,EACX,oIAOWC,EACX,kGAMWC,EAA0C,SAO1CC,EAAwE,CACnF,OAAQ,sBACR,MAAO,yBACP,cAAe,wBACjB,EAQaC,EAA+D,CAC1E,OACE,yJACF,QACE,qNACF,MACE,2KACF,SACE,kKACJ,EAMaC,EAAyC,mCAMzCC,EAAkE,CAC7E,OAAQ,kBACR,QAAS,kBACT,MAAO,kBACP,SAAU,6BACZ,EAGaC,EAA0E,CACrF,SAAU,gCACV,QAAS,0BACX,EAMaC,EAAmC,kDAMnCC,EACX,+DAMWC,EACX,uDASF,SAAgBC,EAAe,EAIpB,CACT,GAAM,CAAE,OAAM,aAAY,SAAUC,EACpC,MAAO,CACLlC,EACAC,EAAekC,GACfjC,EAAqBiC,EAAK,CAAC,KAC3BhC,EAAmBgC,EAAK,CAAC,KACzB/B,EAAqBgC,GACrB7B,EAAgB8B,EAClB,CAAC,CAAC,KAAK,GAAG,CACZ"}