@mortenbrudvik/waveui 0.8.0 → 0.10.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 (206) hide show
  1. package/README.md +634 -167
  2. package/dist/components/data-display/InfoButton.cjs +2 -0
  3. package/dist/components/data-display/InfoButton.cjs.map +1 -0
  4. package/dist/components/data-display/InfoButton.mjs +173 -0
  5. package/dist/components/data-display/InfoButton.mjs.map +1 -0
  6. package/dist/components/data-display/InfoLabel.cjs +1 -1
  7. package/dist/components/data-display/InfoLabel.cjs.map +1 -1
  8. package/dist/components/data-display/InfoLabel.mjs +39 -96
  9. package/dist/components/data-display/InfoLabel.mjs.map +1 -1
  10. package/dist/components/input/Calendar.cjs +2 -0
  11. package/dist/components/input/Calendar.cjs.map +1 -0
  12. package/dist/components/input/Calendar.mjs +352 -0
  13. package/dist/components/input/Calendar.mjs.map +1 -0
  14. package/dist/components/input/Calendar.views.cjs +2 -0
  15. package/dist/components/input/Calendar.views.cjs.map +1 -0
  16. package/dist/components/input/Calendar.views.mjs +194 -0
  17. package/dist/components/input/Calendar.views.mjs.map +1 -0
  18. package/dist/components/input/Checkbox.cjs +1 -1
  19. package/dist/components/input/Checkbox.cjs.map +1 -1
  20. package/dist/components/input/Checkbox.mjs +91 -75
  21. package/dist/components/input/Checkbox.mjs.map +1 -1
  22. package/dist/components/input/ColorArea.cjs +1 -1
  23. package/dist/components/input/ColorArea.cjs.map +1 -1
  24. package/dist/components/input/ColorArea.mjs +18 -18
  25. package/dist/components/input/ColorArea.mjs.map +1 -1
  26. package/dist/components/input/ColorPicker.parts.cjs +1 -1
  27. package/dist/components/input/ColorPicker.parts.cjs.map +1 -1
  28. package/dist/components/input/ColorPicker.parts.mjs +1 -1
  29. package/dist/components/input/ColorPicker.parts.mjs.map +1 -1
  30. package/dist/components/input/Combobox.cjs +1 -1
  31. package/dist/components/input/Combobox.cjs.map +1 -1
  32. package/dist/components/input/Combobox.expand.cjs +1 -1
  33. package/dist/components/input/Combobox.expand.cjs.map +1 -1
  34. package/dist/components/input/Combobox.expand.mjs +20 -20
  35. package/dist/components/input/Combobox.expand.mjs.map +1 -1
  36. package/dist/components/input/Combobox.mjs +219 -138
  37. package/dist/components/input/Combobox.mjs.map +1 -1
  38. package/dist/components/input/Combobox.text.cjs +2 -0
  39. package/dist/components/input/Combobox.text.cjs.map +1 -0
  40. package/dist/components/input/Combobox.text.mjs +24 -0
  41. package/dist/components/input/Combobox.text.mjs.map +1 -0
  42. package/dist/components/input/DatePicker.cjs +1 -1
  43. package/dist/components/input/DatePicker.cjs.map +1 -1
  44. package/dist/components/input/DatePicker.mjs +205 -286
  45. package/dist/components/input/DatePicker.mjs.map +1 -1
  46. package/dist/components/input/Dropdown.cjs +1 -1
  47. package/dist/components/input/Dropdown.cjs.map +1 -1
  48. package/dist/components/input/Dropdown.mjs +137 -105
  49. package/dist/components/input/Dropdown.mjs.map +1 -1
  50. package/dist/components/input/Field.cjs +1 -1
  51. package/dist/components/input/Field.cjs.map +1 -1
  52. package/dist/components/input/Field.mjs +99 -66
  53. package/dist/components/input/Field.mjs.map +1 -1
  54. package/dist/components/input/Input.cjs +1 -1
  55. package/dist/components/input/Input.cjs.map +1 -1
  56. package/dist/components/input/Input.mjs +79 -55
  57. package/dist/components/input/Input.mjs.map +1 -1
  58. package/dist/components/input/Label.cjs +1 -1
  59. package/dist/components/input/Label.cjs.map +1 -1
  60. package/dist/components/input/Label.mjs +19 -13
  61. package/dist/components/input/Label.mjs.map +1 -1
  62. package/dist/components/input/Listbox.cjs +2 -0
  63. package/dist/components/input/Listbox.cjs.map +1 -0
  64. package/dist/components/input/Listbox.mjs +134 -0
  65. package/dist/components/input/Listbox.mjs.map +1 -0
  66. package/dist/components/input/Option.cjs +1 -1
  67. package/dist/components/input/Option.cjs.map +1 -1
  68. package/dist/components/input/Option.mjs +158 -97
  69. package/dist/components/input/Option.mjs.map +1 -1
  70. package/dist/components/input/Rating.cjs +1 -1
  71. package/dist/components/input/Rating.cjs.map +1 -1
  72. package/dist/components/input/Rating.item.cjs +2 -0
  73. package/dist/components/input/Rating.item.cjs.map +1 -0
  74. package/dist/components/input/Rating.item.mjs +255 -0
  75. package/dist/components/input/Rating.item.mjs.map +1 -0
  76. package/dist/components/input/Rating.mjs +150 -151
  77. package/dist/components/input/Rating.mjs.map +1 -1
  78. package/dist/components/input/SearchBox.cjs +1 -1
  79. package/dist/components/input/SearchBox.cjs.map +1 -1
  80. package/dist/components/input/SearchBox.mjs +146 -115
  81. package/dist/components/input/SearchBox.mjs.map +1 -1
  82. package/dist/components/input/Select.cjs +1 -1
  83. package/dist/components/input/Select.cjs.map +1 -1
  84. package/dist/components/input/Select.mjs +34 -25
  85. package/dist/components/input/Select.mjs.map +1 -1
  86. package/dist/components/input/Slider.cjs +1 -1
  87. package/dist/components/input/Slider.cjs.map +1 -1
  88. package/dist/components/input/Slider.mjs +77 -54
  89. package/dist/components/input/Slider.mjs.map +1 -1
  90. package/dist/components/input/SpinButton.cjs +1 -1
  91. package/dist/components/input/SpinButton.cjs.map +1 -1
  92. package/dist/components/input/SpinButton.mjs +198 -96
  93. package/dist/components/input/SpinButton.mjs.map +1 -1
  94. package/dist/components/input/SwatchPicker.cjs +1 -1
  95. package/dist/components/input/SwatchPicker.cjs.map +1 -1
  96. package/dist/components/input/SwatchPicker.context.cjs +2 -0
  97. package/dist/components/input/SwatchPicker.context.cjs.map +1 -0
  98. package/dist/components/input/SwatchPicker.context.mjs +35 -0
  99. package/dist/components/input/SwatchPicker.context.mjs.map +1 -0
  100. package/dist/components/input/SwatchPicker.mjs +131 -103
  101. package/dist/components/input/SwatchPicker.mjs.map +1 -1
  102. package/dist/components/input/SwatchPicker.swatches.cjs +2 -0
  103. package/dist/components/input/SwatchPicker.swatches.cjs.map +1 -0
  104. package/dist/components/input/SwatchPicker.swatches.mjs +207 -0
  105. package/dist/components/input/SwatchPicker.swatches.mjs.map +1 -0
  106. package/dist/components/input/Switch.cjs +1 -1
  107. package/dist/components/input/Switch.cjs.map +1 -1
  108. package/dist/components/input/Switch.mjs +71 -55
  109. package/dist/components/input/Switch.mjs.map +1 -1
  110. package/dist/components/input/TagPicker.cjs +1 -1
  111. package/dist/components/input/TagPicker.cjs.map +1 -1
  112. package/dist/components/input/TagPicker.mjs +154 -123
  113. package/dist/components/input/TagPicker.mjs.map +1 -1
  114. package/dist/components/input/Textarea.cjs +1 -1
  115. package/dist/components/input/Textarea.cjs.map +1 -1
  116. package/dist/components/input/Textarea.mjs +28 -21
  117. package/dist/components/input/Textarea.mjs.map +1 -1
  118. package/dist/components/input/TimePicker.cjs +1 -1
  119. package/dist/components/input/TimePicker.cjs.map +1 -1
  120. package/dist/components/input/TimePicker.mjs +204 -199
  121. package/dist/components/input/TimePicker.mjs.map +1 -1
  122. package/dist/components/input/dateUtils.cjs +1 -1
  123. package/dist/components/input/dateUtils.cjs.map +1 -1
  124. package/dist/components/input/dateUtils.mjs +114 -96
  125. package/dist/components/input/dateUtils.mjs.map +1 -1
  126. package/dist/components/input/fieldLabel.cjs +2 -0
  127. package/dist/components/input/fieldLabel.cjs.map +1 -0
  128. package/dist/components/input/fieldLabel.mjs +17 -0
  129. package/dist/components/input/fieldLabel.mjs.map +1 -0
  130. package/dist/components/input/inputLook.cjs +2 -0
  131. package/dist/components/input/inputLook.cjs.map +1 -0
  132. package/dist/components/input/inputLook.mjs +28 -0
  133. package/dist/components/input/inputLook.mjs.map +1 -0
  134. package/dist/components/input/pickerLabels.cjs +2 -0
  135. package/dist/components/input/pickerLabels.cjs.map +1 -0
  136. package/dist/components/input/pickerLabels.mjs +16 -0
  137. package/dist/components/input/pickerLabels.mjs.map +1 -0
  138. package/dist/components/input/pickerStyles.cjs +1 -1
  139. package/dist/components/input/pickerStyles.cjs.map +1 -1
  140. package/dist/components/input/pickerStyles.mjs +42 -4
  141. package/dist/components/input/pickerStyles.mjs.map +1 -1
  142. package/dist/components/input/pickerValues.cjs +2 -0
  143. package/dist/components/input/pickerValues.cjs.map +1 -0
  144. package/dist/components/input/pickerValues.mjs +24 -0
  145. package/dist/components/input/pickerValues.mjs.map +1 -0
  146. package/dist/components/navigation/Menu.popover.cjs +1 -1
  147. package/dist/components/navigation/Menu.popover.cjs.map +1 -1
  148. package/dist/components/navigation/Menu.popover.mjs +10 -10
  149. package/dist/components/navigation/Menu.root.cjs +1 -1
  150. package/dist/components/navigation/Menu.root.cjs.map +1 -1
  151. package/dist/components/navigation/Menu.root.mjs +10 -10
  152. package/dist/components/overlays/Popover.cjs +1 -1
  153. package/dist/components/overlays/Popover.cjs.map +1 -1
  154. package/dist/components/overlays/Popover.mjs +203 -208
  155. package/dist/components/overlays/Popover.mjs.map +1 -1
  156. package/dist/components/overlays/Popover.shared.cjs +1 -1
  157. package/dist/components/overlays/Popover.shared.cjs.map +1 -1
  158. package/dist/components/overlays/Popover.shared.mjs +82 -77
  159. package/dist/components/overlays/Popover.shared.mjs.map +1 -1
  160. package/dist/components/provider/WaveProvider.cjs +1 -1
  161. package/dist/components/provider/WaveProvider.cjs.map +1 -1
  162. package/dist/components/provider/WaveProvider.mjs +25 -19
  163. package/dist/components/provider/WaveProvider.mjs.map +1 -1
  164. package/dist/components/table/useGridNavigation.cjs +1 -1
  165. package/dist/components/table/useGridNavigation.cjs.map +1 -1
  166. package/dist/components/table/useGridNavigation.mjs +67 -70
  167. package/dist/components/table/useGridNavigation.mjs.map +1 -1
  168. package/dist/hooks/useActiveDescendant.cjs +2 -0
  169. package/dist/hooks/useActiveDescendant.cjs.map +1 -0
  170. package/dist/hooks/useActiveDescendant.mjs +56 -0
  171. package/dist/hooks/useActiveDescendant.mjs.map +1 -0
  172. package/dist/hooks/useFieldControl.cjs.map +1 -1
  173. package/dist/hooks/useFieldControl.mjs.map +1 -1
  174. package/dist/hooks/useListbox.cjs +1 -1
  175. package/dist/hooks/useListbox.cjs.map +1 -1
  176. package/dist/hooks/useListbox.mjs +250 -210
  177. package/dist/hooks/useListbox.mjs.map +1 -1
  178. package/dist/hooks/useRovingGrid.cjs +2 -0
  179. package/dist/hooks/useRovingGrid.cjs.map +1 -0
  180. package/dist/hooks/useRovingGrid.mjs +69 -0
  181. package/dist/hooks/useRovingGrid.mjs.map +1 -0
  182. package/dist/index.cjs +1 -1
  183. package/dist/index.d.cts +2901 -203
  184. package/dist/index.d.ts +2901 -203
  185. package/dist/index.mjs +107 -99
  186. package/dist/lib/classNames.cjs +1 -1
  187. package/dist/lib/classNames.cjs.map +1 -1
  188. package/dist/lib/classNames.mjs +111 -67
  189. package/dist/lib/classNames.mjs.map +1 -1
  190. package/dist/lib/date.cjs +2 -0
  191. package/dist/lib/date.cjs.map +1 -0
  192. package/dist/lib/date.mjs +44 -0
  193. package/dist/lib/date.mjs.map +1 -0
  194. package/dist/lib/focus.cjs +1 -1
  195. package/dist/lib/focus.cjs.map +1 -1
  196. package/dist/lib/focus.mjs +5 -2
  197. package/dist/lib/focus.mjs.map +1 -1
  198. package/dist/lib/layers.cjs.map +1 -1
  199. package/dist/lib/layers.mjs.map +1 -1
  200. package/dist/lib/styles.cjs +1 -1
  201. package/dist/lib/styles.cjs.map +1 -1
  202. package/dist/lib/styles.mjs +21 -4
  203. package/dist/lib/styles.mjs.map +1 -1
  204. package/dist/styles.css +1 -1
  205. package/package.json +1 -1
  206. package/src/styles/tokens.css +13 -0
@@ -1 +1 @@
1
- {"version":3,"file":"Rating.cjs","names":["StarIcon","useControllable","React","useRovingTabIndex","getArrowIntent","getDirection","useMergedRefs","useFieldContext","useFieldControl","cn","ratingClassNames","composeEventHandlers","focusRing","HiddenInput","ratingDisplayClassNames","require_runtime","require_cn","require_dev","require_composeEventHandlers","require_classNames","require_useControllable","require_direction","require_useRovingTabIndex","require_useMergedRefs","require_useFieldControl","require_styles","require_icons","require_useFormReset","require_HiddenInput","react","react_jsx_runtime","defaultStarLabel","value","sizeMap","targetPaddingMap","Star","filled","Rating","valueProp","ariaLabel","ariaLabelledBy","ariaDescribedBy","ariaInvalid","ariaRequired","rest","onChange","initialValue","defaultValue","setValue","onValueChange","next","hovered","setHovered","prevDisabled","setPrevDisabled","disabled","displayValue","handleKeyDown","max","intent","focusValue","rootRef","mergedRef","ref","containerProps","field","fieldProps","required","isRequired","defaultName","form","focusTabStop","starSize","size","starLabel","labels","className","onKeyDown","onFocus","onMouseLeave","starValue","i","key","getTabIndex","textSizeMap","defaultRatingLabel","formattedValue","defaultCountLabel","count","formattedCount","supportedLocale","locale","RatingDisplay","formatLocale","invalidLocale","ratingName","name","showText","showValue","compact","textSize","percent"],"sources":["../../../src/components/input/Rating.tsx"],"sourcesContent":["import * as React from 'react';\nimport { ratingClassNames, ratingDisplayClassNames } from '../../lib/classNames';\nimport { cn } from '../../lib/cn';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { warnDeprecated, warnOnce } from '../../lib/dev';\nimport { getArrowIntent, getDirection } from '../../lib/direction';\nimport { StarIcon } from '../../lib/icons';\nimport { focusRing } from '../../lib/styles';\nimport type { Size } from '../../lib/types';\nimport { useControllable } from '../../hooks/useControllable';\nimport { useFieldContext, useFieldControl } from '../../hooks/useFieldControl';\nimport { useFormReset } from '../../hooks/useFormReset';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { useRovingTabIndex } from '../../hooks/useRovingTabIndex';\nimport { HiddenInput } from '../internal/HiddenInput';\n\n/**\n * The Rating's built-in star names, for localization. Each member is optional and falls back to\n * its English default.\n */\nexport interface RatingLabels {\n /**\n * Accessible name of the star that chooses `value` (1 to `max`).\n * @default (value) => value === 1 ? '1 star' : `${value} stars`\n */\n star?: (value: number, max: number) => string;\n}\n\nconst defaultStarLabel = (value: number) => `${value} star${value !== 1 ? 's' : ''}`;\n\n/** Properties for the Rating component. */\nexport interface RatingProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue'\n> {\n /** Controlled rating value (`0` = no rating). */\n value?: number;\n /** Initial rating for uncontrolled usage (also the value a form reset restores).\n * @default 0\n */\n defaultValue?: number;\n /** Called with the new rating when it changes (not when the current star is chosen again). */\n onValueChange?: (value: number) => void;\n /**\n * Called with the new rating, exactly like `onValueChange`: only when the rating changes (0.4\n * also called it when the current star was clicked again, or a key was pressed at an end).\n * @deprecated Use `onValueChange`.\n */\n onChange?: (value: number) => void;\n /** Maximum number of stars.\n * @default 5\n */\n max?: number;\n /** Size of the star icons. Every size keeps a target of at least 24×24px.\n * @default 'medium'\n */\n size?: Size;\n /** Whether the rating is disabled and non-interactive.\n * @default false\n */\n disabled?: boolean;\n /**\n * Form field name. With a name, the chosen rating is submitted with the form (nothing while no\n * star is chosen).\n */\n name?: string;\n /** A star must be chosen before the form can be submitted (native validation). */\n required?: boolean;\n /** Id of the form the rating belongs to, when it is rendered outside that form. */\n form?: string;\n /**\n * Names of the stars (\"1 star\", \"2 stars\", …), for localization. The group's own name is\n * `aria-label` (default \"Rating\"). Unset members keep their English defaults.\n */\n labels?: RatingLabels;\n /** Ref to the `role=\"radiogroup\"` element. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Built-in texts of RatingDisplay, for localization. */\nexport interface RatingDisplayLabels {\n /**\n * Accessible name. `formattedValue` is `value` formatted with `locale` (as shown).\n * @default (value, max, formattedValue) => `Rating: ${formattedValue} out of ${max}`\n */\n rating?: (value: number, max: number, formattedValue: string) => string;\n /**\n * Count part of the name.\n * @default (count, formatted) => count === 1 ? '1 rating' : `${formatted} ratings`\n */\n count?: (count: number, formattedCount: string) => string;\n}\n\n/** Properties for the RatingDisplay (read-only) component. */\nexport interface RatingDisplayProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * Rating value to display. A fraction is drawn as a partly filled star (`4.6` fills 60% of the\n * fifth star), so the stars show the value the accessible name reports.\n */\n value: number;\n /** Maximum number of stars.\n * @default 5\n */\n max?: number;\n /** Size of the star icons (and of the value and count text).\n * @default 'medium'\n */\n size?: Size;\n /**\n * Shows the value as text after the stars, formatted with `locale` (up to one decimal).\n * @default false\n */\n showValue?: boolean;\n /**\n * Number of ratings: shown as \"(1,160)\" after the value with the locale's digit grouping, and\n * added to the accessible name (\"…, 1,160 ratings\").\n */\n count?: number;\n /**\n * One filled star followed by the value (and the count) instead of `max` stars. Implies\n * `showValue`.\n * @default false\n */\n compact?: boolean;\n /**\n * BCP 47 locale of the value and count formatting (runtime default when omitted; pass it when\n * rendering on the server). A tag `Intl` rejects (the POSIX `de_DE`, a typo) falls back to the\n * runtime default locale (development warning).\n */\n locale?: string;\n /** Built-in texts, for localization. */\n labels?: RatingDisplayLabels;\n /** Ref to the root element. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\nconst sizeMap: Record<Size, string> = {\n 'extra-small': 'h-3 w-3',\n small: 'h-4 w-4',\n medium: 'h-5 w-5',\n large: 'h-6 w-6',\n 'extra-large': 'h-8 w-8',\n};\n\n/** Padding that gives every interactive star a target of at least 24×24px (WCAG 2.5.8). */\nconst targetPaddingMap: Record<Size, string> = {\n 'extra-small': 'p-1.5', // 12px icon + 12px\n small: 'p-1', // 16px icon + 8px\n medium: 'p-0.5', // 20px icon + 4px\n large: 'p-0.5',\n 'extra-large': 'p-0.5',\n};\n\n/** A filled star, or an outlined one (the outline keeps a 3:1 non-text contrast). */\nfunction Star({ filled, className }: { filled: boolean; className: string }) {\n return filled ? (\n <StarIcon className={className} />\n ) : (\n <StarIcon className={className} fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5} />\n );\n}\n\n/**\n * A star rating input (`role=\"radiogroup\"` of `role=\"radio\"` stars).\n *\n * - One tab stop (the chosen star, else the first). Keys choose relative to the current rating and\n * move focus to the chosen star, as in 0.4: Right/Up one star more, Left/Down one fewer\n * (Left/Right mirrored under `dir=\"rtl\"`), Home/End the first/last star. From an empty rating,\n * Right/Up/Home choose 1 star. Keys at the ends (and Left/Down while empty) emit nothing.\n * - The keyboard cannot clear a rating: Left/Down stop at 1 star (APG radio group; 0.4 went down\n * to 0). Only a controlled `value={0}` (or a form reset while `defaultValue` is 0) clears it.\n * - `onValueChange` (and the deprecated `onChange` alias) fire only when the rating changes, so\n * choosing the current star again calls neither.\n * - Hovering previews a value; the preview never outlives the hover or a disabled state.\n * - Inside a `Field` it is named by the Field label (instead of the default \"Rating\") and\n * described by its hint and error. The stars are named \"1 star\", \"2 stars\", …; `labels`\n * localizes these names.\n * - With `name` (or `required`) it takes part in native forms; a form reset restores\n * `defaultValue`.\n */\nexport const Rating = ({\n value: valueProp,\n defaultValue,\n onValueChange,\n onChange,\n max = 5,\n size = 'medium',\n disabled = false,\n name,\n required,\n form,\n labels,\n className,\n id,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n onKeyDown,\n onFocus,\n onMouseLeave,\n ref,\n ...rest\n}: RatingProps) => {\n if (onChange !== undefined) warnDeprecated('Rating', 'onChange', 'onValueChange');\n const initialValue = defaultValue ?? 0;\n const [value, setValue] = useControllable(valueProp, initialValue, (next: number) => {\n onValueChange?.(next);\n onChange?.(next);\n });\n\n const [hovered, setHovered] = React.useState(0);\n // A hover preview must not survive the rating becoming disabled (adjust state during render).\n const [prevDisabled, setPrevDisabled] = React.useState(disabled);\n if (prevDisabled !== disabled) {\n setPrevDisabled(disabled);\n if (disabled) setHovered(0);\n }\n const displayValue = disabled ? value : hovered || value;\n\n // Roving tab stop (the chosen star, else the first) and focus moves; the keys are handled below.\n const { containerProps, getTabIndex, focusValue } = useRovingTabIndex({\n activeValue: value > 0 ? String(value) : null,\n });\n\n // Keys count from the current rating, not from the focused star (0.4 semantics): from an empty\n // rating, Tab lands on the unchosen first star and Right/Up/Home choose it. Never wraps.\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey) return;\n let next: number;\n if (e.key === 'Home') next = 1;\n else if (e.key === 'End') next = max;\n else if (e.key === 'ArrowUp') next = value + 1;\n else if (e.key === 'ArrowDown') next = value - 1;\n else {\n const intent = getArrowIntent(e.key, {\n orientation: 'horizontal',\n dir: getDirection(e.currentTarget),\n });\n if (!intent) return;\n next = intent === 'next' ? value + 1 : value - 1;\n }\n e.preventDefault();\n next = Math.min(next, max);\n // The ends are no-ops (nothing is emitted, focus stays), and Left/Down never clear to 0.\n if (disabled || next < 1 || next === value) return;\n focusValue(String(next));\n setValue(next);\n };\n\n const rootRef = React.useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRefs<HTMLDivElement>(ref, containerProps.ref, rootRef);\n\n const field = useFieldContext();\n const fieldProps = useFieldControl(\n {\n id,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n 'aria-invalid': ariaInvalid,\n // An explicit `required={false}` wins over a required Field, so aria-required always\n // matches the native validation below (`isRequired`).\n 'aria-required': ariaRequired ?? required,\n },\n { labelable: false },\n );\n const isRequired = required ?? field?.required ?? false;\n const defaultName =\n fieldProps['aria-label'] === undefined && fieldProps['aria-labelledby'] === undefined\n ? 'Rating'\n : undefined;\n\n useFormReset(rootRef, () => setValue(initialValue), form);\n\n const focusTabStop = () => {\n rootRef.current?.querySelector<HTMLElement>('[data-roving-value][tabindex=\"0\"]')?.focus();\n };\n\n const starSize = sizeMap[size];\n const starLabel = labels?.star ?? defaultStarLabel;\n\n return (\n <div\n role=\"radiogroup\"\n aria-label={defaultName}\n {...fieldProps}\n aria-disabled={disabled || undefined}\n className={cn(\n ratingClassNames.root,\n 'relative inline-flex items-center gap-0.5',\n disabled && 'pointer-events-none opacity-50',\n className,\n )}\n {...rest}\n ref={mergedRef}\n data-roving-container=\"\"\n onKeyDown={composeEventHandlers(onKeyDown, handleKeyDown)}\n onFocus={composeEventHandlers(onFocus, containerProps.onFocus, {\n checkDefaultPrevented: false,\n })}\n onMouseLeave={composeEventHandlers(onMouseLeave, () => setHovered(0), {\n checkDefaultPrevented: false,\n })}\n >\n {Array.from({ length: max }, (_, i) => {\n const starValue = i + 1;\n const key = String(starValue);\n const filled = displayValue >= starValue;\n return (\n <button\n key={key}\n type=\"button\"\n role=\"radio\"\n aria-checked={value === starValue}\n aria-label={starLabel(starValue, max)}\n data-roving-value={key}\n tabIndex={disabled ? -1 : getTabIndex(key)}\n disabled={disabled}\n className={cn(\n ratingClassNames.star,\n 'inline-flex cursor-pointer rounded border-0 bg-transparent transition-colors motion-reduce:transition-none disabled:cursor-not-allowed',\n targetPaddingMap[size],\n focusRing,\n filled ? 'text-rating' : 'text-stroke-accessible',\n )}\n onClick={() => {\n // Choosing the current star again is a no-op for both callbacks (useControllable).\n if (!disabled) setValue(starValue);\n }}\n onMouseEnter={() => {\n if (!disabled) setHovered(starValue);\n }}\n >\n <Star filled={filled} className={cn(ratingClassNames.icon, starSize)} />\n </button>\n );\n })}\n <HiddenInput\n type=\"radio\"\n name={name}\n form={form}\n disabled={disabled}\n value={value > 0 ? String(value) : ''}\n required={isRequired}\n onInvalid={focusTabStop}\n />\n </div>\n );\n};\nRating.displayName = 'Rating';\n\n/** Text size of the value and the count, following the star size. */\nconst textSizeMap: Record<Size, string> = {\n 'extra-small': 'text-caption-1',\n small: 'text-caption-1',\n medium: 'text-body-1',\n large: 'text-body-2',\n 'extra-large': 'text-body-2',\n};\n\nconst defaultRatingLabel = (_value: number, max: number, formattedValue: string) =>\n `Rating: ${formattedValue} out of ${max}`;\nconst defaultCountLabel = (count: number, formattedCount: string) =>\n count === 1 ? '1 rating' : `${formattedCount} ratings`;\n\n/** `locale` when `Intl` accepts it as a language tag, else `undefined` (the runtime default). */\nfunction supportedLocale(locale: string | undefined): string | undefined {\n if (locale === undefined) return undefined;\n try {\n Intl.getCanonicalLocales(locale);\n return locale;\n } catch {\n return undefined;\n }\n}\n\n/**\n * A read-only star rating (`role=\"img\"` named \"Rating: <value> out of <max>\"). A fractional value\n * is drawn with a partly filled star, so the stars and the name show the same value.\n *\n * - `showValue` adds the value as text after the stars, `count` the number of ratings (\"(1,160)\",\n * also added to the name), and `compact` shows one filled star with the value instead of `max`\n * stars (the root carries `data-compact`). Without them only the stars show.\n * - The value (up to one decimal) and the count are formatted with `locale`, in the text and in\n * the name; `labels` localizes the name. Pass `locale` when rendering on the server.\n */\nexport const RatingDisplay = ({\n value,\n max = 5,\n size = 'medium',\n showValue = false,\n count,\n compact = false,\n locale,\n labels,\n className,\n ref,\n ...rest\n}: RatingDisplayProps) => {\n const starSize = sizeMap[size];\n const formatLocale = supportedLocale(locale);\n const invalidLocale = locale !== undefined && formatLocale === undefined;\n React.useEffect(() => {\n if (invalidLocale) {\n warnOnce(\n `RatingDisplay:invalid-locale:${locale}`,\n `RatingDisplay: \\`locale\\` \"${locale}\" is not a valid BCP 47 language tag (use hyphens, as ` +\n 'in \"en-US\"). The runtime default locale formats the value instead, so the server and ' +\n 'the browser may render different text.',\n );\n }\n }, [invalidLocale, locale]);\n\n const formattedValue = new Intl.NumberFormat(formatLocale, { maximumFractionDigits: 1 }).format(\n value,\n );\n const formattedCount =\n count === undefined ? undefined : new Intl.NumberFormat(formatLocale).format(count);\n const ratingName = (labels?.rating ?? defaultRatingLabel)(value, max, formattedValue);\n const name =\n count === undefined || formattedCount === undefined\n ? ratingName\n : `${ratingName}, ${(labels?.count ?? defaultCountLabel)(count, formattedCount)}`;\n const showText = showValue || compact || count !== undefined;\n const textSize = textSizeMap[size];\n\n return (\n <div\n ref={ref}\n role=\"img\"\n aria-label={name}\n data-compact={compact ? '' : undefined}\n className={cn(ratingDisplayClassNames.root, 'inline-flex items-center gap-0.5', className)}\n {...rest}\n >\n {compact ? (\n <span className={cn(ratingDisplayClassNames.star, 'inline-flex text-rating')}>\n <Star filled className={cn(ratingDisplayClassNames.icon, starSize)} />\n </span>\n ) : (\n Array.from({ length: max }, (_, i) => {\n // The share of this star that the value covers, in whole percent.\n const percent = Math.round(Math.min(1, Math.max(0, value - i)) * 100);\n if (percent > 0 && percent < 100) {\n // The outline of an empty star, with the filled star clipped to the fraction over it\n // from the inline start (the reading direction of the stars, also under RTL).\n return (\n <span\n key={i}\n className={cn(\n ratingDisplayClassNames.star,\n 'relative inline-flex text-stroke-accessible',\n )}\n >\n <Star filled={false} className={cn(ratingDisplayClassNames.icon, starSize)} />\n <span\n className=\"absolute inset-y-0 start-0 flex overflow-hidden text-rating\"\n style={{ width: `${percent}%` }}\n >\n <Star filled className={cn(ratingDisplayClassNames.icon, starSize, 'shrink-0')} />\n </span>\n </span>\n );\n }\n const filled = percent === 100;\n return (\n <span\n key={i}\n className={cn(\n ratingDisplayClassNames.star,\n 'inline-flex',\n filled ? 'text-rating' : 'text-stroke-accessible',\n )}\n >\n <Star filled={filled} className={cn(ratingDisplayClassNames.icon, starSize)} />\n </span>\n );\n })\n )}\n {showText && (\n <span\n className={cn(\n ratingDisplayClassNames.value,\n 'ms-1 font-semibold text-foreground',\n textSize,\n )}\n >\n {formattedValue}\n </span>\n )}\n {formattedCount !== undefined && (\n <span className={cn(ratingDisplayClassNames.count, 'ms-1 text-muted-foreground', textSize)}>\n ({formattedCount})\n </span>\n )}\n </div>\n );\n};\nRatingDisplay.displayName = 'RatingDisplay';\n"],"mappings":"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"}
1
+ {"version":3,"file":"Rating.cjs","names":["useControllable","React","useRovingTabIndex","isOwnEvent","getArrowIntent","getDirection","useMergedRefs","useFieldContext","useFieldControl","useEventCallback","resolveRatingColor","useStarGlyphs","useRatingItemRegistry","slotRendersContent","cn","ratingClassNames","composeEventHandlers","RatingContext","ratingItems","HiddenInput","INERT_RATING_CONTEXT","ratingDisplayClassNames","StarGlyph","RatingItem","require_runtime","require_cn","require_dev","require_slot","require_composeEventHandlers","require_classNames","require_useEventCallback","require_useControllable","require_direction","require_useRovingTabIndex","require_useMergedRefs","require_useFieldControl","require_useFormReset","require_HiddenInput","require_events","require_Rating_item","react","react_jsx_runtime","defaultStarLabel","value","RatingRoot","valueProp","stepProp","ariaLabel","ariaLabelledBy","ariaDescribedBy","ariaInvalid","ariaRequired","rest","onChange","initialValue","defaultValue","setValue","onValueChange","next","step","hovered","setHovered","prevDisabled","setPrevDisabled","disabled","drawnValue","up","v","down","handleKeyDown","max","intent","focusValue","rootRef","mergedRef","ref","containerProps","field","fieldProps","required","isRequired","defaultName","form","focusTabStop","focusChosen","key","resolvedColor","color","glyphs","iconFilled","iconOutline","starLabel","labels","hasItems","children","counts","context","focus","size","getTabIndex","registerItem","className","onKeyDown","onFocus","onMouseLeave","textSizeMap","defaultRatingLabel","formattedValue","defaultCountLabel","count","formattedCount","supportedLocale","locale","RatingDisplayRoot","formatLocale","invalidLocale","ratingName","name","showText","showValue","compact","textSize","Rating","RatingDisplay","RatingDisplayItem"],"sources":["../../../src/components/input/Rating.tsx"],"sourcesContent":["import * as React from 'react';\nimport { ratingClassNames, ratingDisplayClassNames } from '../../lib/classNames';\nimport { cn } from '../../lib/cn';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { warnDeprecated, warnOnce } from '../../lib/dev';\nimport { getArrowIntent, getDirection } from '../../lib/direction';\nimport { isOwnEvent } from '../../lib/events';\nimport { slotRendersContent, type Slot } from '../../lib/slot';\nimport type { Size } from '../../lib/types';\nimport { useControllable } from '../../hooks/useControllable';\nimport { useEventCallback } from '../../hooks/useEventCallback';\nimport { useFieldContext, useFieldControl } from '../../hooks/useFieldControl';\nimport { useFormReset } from '../../hooks/useFormReset';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { useRovingTabIndex } from '../../hooks/useRovingTabIndex';\nimport { HiddenInput } from '../internal/HiddenInput';\nimport {\n INERT_RATING_CONTEXT,\n RatingContext,\n RatingItem,\n ratingItems,\n resolveRatingColor,\n StarGlyph,\n useRatingItemChecks,\n useRatingItemRegistry,\n useStarGlyphs,\n type RatingColor,\n type RatingContextValue,\n} from './Rating.item';\n\n/**\n * The Rating's built-in star names, for localization. Each member is optional and falls back to\n * its English default.\n */\nexport interface RatingLabels {\n /**\n * Accessible name of the radio that chooses `value`: a whole star (1 to `max`), and with\n * `step={0.5}` a half value too (\"2.5 stars\").\n * @default (value) => value === 1 ? '1 star' : `${value} stars`\n */\n star?: (value: number, max: number) => string;\n}\n\nconst defaultStarLabel = (value: number) => `${value} star${value !== 1 ? 's' : ''}`;\n\n/** Properties for the Rating component. */\nexport interface RatingProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue' | 'color'\n> {\n /**\n * Controlled rating value (`0` = no rating): a whole number of stars, or with `step={0.5}` a\n * multiple of 0.5. A value between two steps (2.7) checks no radio, is drawn as the step below\n * it (2.5) and holds the tab stop there; the keys move it to the next or previous step.\n */\n value?: number;\n /** Initial rating for uncontrolled usage (also the value a form reset restores).\n * @default 0\n */\n defaultValue?: number;\n /** Called with the new rating when it changes (not when the current star is chosen again). */\n onValueChange?: (value: number) => void;\n /**\n * Called with the new rating, exactly like `onValueChange`: only when the rating changes (0.4\n * also called it when the current star was clicked again, or a key was pressed at an end).\n * @deprecated Use `onValueChange`.\n */\n onChange?: (value: number) => void;\n /** Maximum number of stars.\n * @default 5\n */\n max?: number;\n /**\n * The grain of the rating: `1` (whole stars) or `0.5` (half stars). With `0.5`, each star stays\n * one pointer target of at least 24×24px (a larger `size` keeps a larger one): the half is\n * chosen by the pointer's position over it (the half at the inline start, which mirrors under\n * `dir=\"rtl\"`), and two transparent radios per star, the half value then the full value, serve\n * the keyboard and screen readers. The keys then move by half a star. A value other than `0.5`\n * (from untyped code) counts as `1`. RatingDisplay has no `step`: it draws any fraction.\n * @default 1\n */\n step?: 0.5 | 1;\n /** Size of the star icons. Every size keeps a target of at least 24×24px.\n * @default 'medium'\n */\n size?: Size;\n /**\n * Color of the filled stars: `marigold` (default), `brand` or `neutral`; unfilled stars keep a\n * 3:1 outline. Fluent's default is `neutral`. A value outside the three (from untyped code)\n * renders as `marigold`; the root carries `data-color` with the resolved value.\n * @default 'marigold'\n */\n color?: RatingColor;\n /**\n * The glyph of a filled star. `iconFilled` and `iconOutline` are the glyphs of a filled and an\n * unfilled star, as a pair; decorative. Fluent's RatingDisplay takes one `icon`; here a filled\n * and an unfilled star must differ by shape, not by color alone.\n *\n * - Each glyph is sized by `size` (an SVG child fills the star) and takes the star's color\n * (paint it with `currentColor`). A half-filled star (`step={0.5}`) clips the filled glyph\n * over the unfilled one from the inline start.\n * - `null`, `undefined` and a value that renders nothing draw the default star. A value that\n * renders nothing, and a pair with only one of the two set, log a development warning.\n * - A `<button>` or `Button` element (or a slot object whose `as` is one) is not nested in the\n * star: its children become the glyph (the default star when they render nothing), its props\n * are dropped, and a development warning says so (in place of the warning for a value that\n * renders nothing).\n * - A `Rating.Item` that sets its own pair draws that one instead.\n */\n iconFilled?: Slot<'span'>;\n /**\n * The glyph of an unfilled star, as a pair with `iconFilled` (its rules apply); decorative.\n * Fluent's RatingDisplay takes one `icon`; here a filled and an unfilled star must differ by\n * shape, not by color alone.\n */\n iconOutline?: Slot<'span'>;\n /** Whether the rating is disabled and non-interactive.\n * @default false\n */\n disabled?: boolean;\n /**\n * Form field name. With a name, the chosen rating is submitted with the form (nothing while no\n * star is chosen).\n */\n name?: string;\n /** A star must be chosen before the form can be submitted (native validation). */\n required?: boolean;\n /** Id of the form the rating belongs to, when it is rendered outside that form. */\n form?: string;\n /**\n * Names of the stars (\"1 star\", \"2 stars\", …, and \"2.5 stars\" for a half value), for\n * localization. The group's own name is `aria-label` (default \"Rating\"). Unset members keep\n * their English defaults.\n */\n labels?: RatingLabels;\n /**\n * Custom stars: one `Rating.Item` per value from 1 to `max`, in place of the generated stars\n * (when the children render content). `max` still sets the key range and the star names.\n */\n children?: React.ReactNode;\n /** Ref to the `role=\"radiogroup\"` element. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n/** Built-in texts of RatingDisplay, for localization. */\nexport interface RatingDisplayLabels {\n /**\n * Accessible name. `formattedValue` is `value` formatted with `locale` (as shown).\n * @default (value, max, formattedValue) => `Rating: ${formattedValue} out of ${max}`\n */\n rating?: (value: number, max: number, formattedValue: string) => string;\n /**\n * Count part of the name.\n * @default (count, formatted) => count === 1 ? '1 rating' : `${formatted} ratings`\n */\n count?: (count: number, formattedCount: string) => string;\n}\n\n/** Properties for the RatingDisplay (read-only) component. */\nexport interface RatingDisplayProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {\n /**\n * Rating value to display. A fraction is drawn as a partly filled star (`4.6` fills 60% of the\n * fifth star), so the stars show the value the accessible name reports.\n */\n value: number;\n /** Maximum number of stars.\n * @default 5\n */\n max?: number;\n /** Size of the star icons (and of the value and count text).\n * @default 'medium'\n */\n size?: Size;\n /**\n * Color of the filled stars: `marigold` (default), `brand` or `neutral`; unfilled stars keep a\n * 3:1 outline. Fluent's default is `neutral`. A value outside the three (from untyped code)\n * renders as `marigold`; the root carries `data-color` with the resolved value.\n * @default 'marigold'\n */\n color?: RatingColor;\n /**\n * The glyph of a filled star. `iconFilled` and `iconOutline` are the glyphs of a filled and an\n * unfilled star, as a pair; decorative. Fluent's RatingDisplay takes one `icon`; here a filled\n * and an unfilled star must differ by shape, not by color alone.\n *\n * - Each glyph is sized by `size` (an SVG child fills the star) and takes the star's color\n * (paint it with `currentColor`). A partly filled star clips the filled glyph over the\n * unfilled one from the inline start, and the `compact` star is the filled glyph.\n * - `null`, `undefined` and a value that renders nothing draw the default star. A value that\n * renders nothing, and a pair with only one of the two set, log a development warning.\n * - A `<button>` or `Button` element (or a slot object whose `as` is one) is not nested in the\n * star: its children become the glyph (the default star when they render nothing), its props\n * are dropped, and a development warning says so (in place of the warning for a value that\n * renders nothing).\n * - A `RatingDisplay.Item` that sets its own pair draws that one instead.\n */\n iconFilled?: Slot<'span'>;\n /**\n * The glyph of an unfilled star, as a pair with `iconFilled` (its rules apply); decorative.\n * Fluent's RatingDisplay takes one `icon`; here a filled and an unfilled star must differ by\n * shape, not by color alone.\n */\n iconOutline?: Slot<'span'>;\n /**\n * Shows the value as text after the stars, formatted with `locale` (up to one decimal).\n * @default false\n */\n showValue?: boolean;\n /**\n * Number of ratings: shown as \"(1,160)\" after the value with the locale's digit grouping, and\n * added to the accessible name (\"…, 1,160 ratings\").\n */\n count?: number;\n /**\n * One filled star followed by the value (and the count) instead of `max` stars. Implies\n * `showValue`.\n * @default false\n */\n compact?: boolean;\n /**\n * BCP 47 locale of the value and count formatting (runtime default when omitted; pass it when\n * rendering on the server). A tag `Intl` rejects (the POSIX `de_DE`, a typo) falls back to the\n * runtime default locale (development warning).\n */\n locale?: string;\n /** Built-in texts, for localization. */\n labels?: RatingDisplayLabels;\n /**\n * Custom stars: one `RatingDisplay.Item` per value from 1 to `max`, in place of the generated\n * stars (when the children render content). A `compact` display ignores them.\n */\n children?: React.ReactNode;\n /** Ref to the root element. */\n ref?: React.Ref<HTMLDivElement>;\n}\n\nconst RatingRoot = ({\n value: valueProp,\n defaultValue,\n onValueChange,\n onChange,\n max = 5,\n step: stepProp,\n size = 'medium',\n color,\n iconFilled,\n iconOutline,\n disabled = false,\n name,\n required,\n form,\n labels,\n className,\n children,\n id,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n 'aria-invalid': ariaInvalid,\n 'aria-required': ariaRequired,\n onKeyDown,\n onFocus,\n onMouseLeave,\n ref,\n ...rest\n}: RatingProps) => {\n if (onChange !== undefined) warnDeprecated('Rating', 'onChange', 'onValueChange');\n const initialValue = defaultValue ?? 0;\n const [value, setValue] = useControllable(valueProp, initialValue, (next: number) => {\n onValueChange?.(next);\n onChange?.(next);\n });\n // The items draw whole or half stars only, so the keys move by one of the two (untyped code\n // may pass another number).\n const step: 0.5 | 1 = stepProp === 0.5 ? 0.5 : 1;\n\n const [hovered, setHovered] = React.useState(0);\n // A hover preview must not survive the rating becoming disabled (adjust state during render).\n const [prevDisabled, setPrevDisabled] = React.useState(disabled);\n if (prevDisabled !== disabled) {\n setPrevDisabled(disabled);\n if (disabled) setHovered(0);\n }\n const drawnValue = disabled ? value : hovered || value;\n\n // Roving tab stop (the radio of the chosen value, else the first) and focus moves; the keys are\n // handled below. A value between two steps holds the tab stop on the radio below it (D23).\n const { containerProps, getTabIndex, focusValue } = useRovingTabIndex({\n activeValue: value >= step ? String(Math.floor(value / step) * step) : null,\n });\n\n // The next and previous multiples of `step`: a value between two steps (a controlled 2.7 at\n // step 0.5) moves to the step above or below it (3 or 2.5).\n const up = (v: number) => Math.floor(v / step) * step + step;\n const down = (v: number) => Math.ceil(v / step) * step - step;\n\n // Keys count from the current rating, not from the focused radio (0.4 semantics): from an empty\n // rating, Tab lands on the unchosen first radio and Right/Up/Home choose it. Never wraps.\n // Keys from a portal rendered among the children (a popup opened from one) are not the rating's.\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey || !isOwnEvent(e)) return;\n let next: number;\n if (e.key === 'Home') next = step;\n else if (e.key === 'End') next = max;\n else if (e.key === 'ArrowUp') next = up(value);\n else if (e.key === 'ArrowDown') next = down(value);\n else {\n const intent = getArrowIntent(e.key, {\n orientation: 'horizontal',\n dir: getDirection(e.currentTarget),\n });\n if (!intent) return;\n next = intent === 'next' ? up(value) : down(value);\n }\n e.preventDefault();\n next = Math.min(next, max);\n // The ends are no-ops (nothing is emitted, focus stays), and Left/Down never clear to 0.\n if (disabled || next < step || next === value) return;\n focusValue(String(next));\n setValue(next);\n };\n\n const rootRef = React.useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRefs<HTMLDivElement>(ref, containerProps.ref, rootRef);\n\n const field = useFieldContext();\n const fieldProps = useFieldControl(\n {\n id,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n 'aria-invalid': ariaInvalid,\n // An explicit `required={false}` wins over a required Field, so aria-required always\n // matches the native validation below (`isRequired`).\n 'aria-required': ariaRequired ?? required,\n },\n { labelable: false },\n );\n const isRequired = required ?? field?.required ?? false;\n const defaultName =\n fieldProps['aria-label'] === undefined && fieldProps['aria-labelledby'] === undefined\n ? 'Rating'\n : undefined;\n\n useFormReset(rootRef, () => setValue(initialValue), form);\n\n const focusTabStop = () => {\n rootRef.current?.querySelector<HTMLElement>('[data-roving-value][tabindex=\"0\"]')?.focus();\n };\n\n // A pointer click on a half star focuses the radio it chose, so the keyboard continues from\n // there, without scrolling: the star is under the pointer already (D22).\n const focusChosen = useEventCallback((key: string) => {\n rootRef.current\n ?.querySelector<HTMLElement>(`[data-roving-value=\"${key}\"]`)\n ?.focus({ preventScroll: true });\n });\n\n const resolvedColor = resolveRatingColor(color);\n const glyphs = useStarGlyphs('Rating', iconFilled, iconOutline);\n const starLabel = labels?.star ?? defaultStarLabel;\n const { registerItem, counts } = useRatingItemRegistry();\n // Whether the root draws its own children instead of the `max` generated stars (D26): the\n // generated stars always cover every value on their own, so only a custom item set can leave\n // one missing.\n const hasItems = slotRendersContent(children);\n useRatingItemChecks(counts, max, hasItems);\n const context = React.useMemo<RatingContextValue>(\n () => ({\n kind: 'input',\n value,\n drawnValue,\n step,\n max,\n size,\n color: resolvedColor,\n glyphs,\n disabled,\n starLabel,\n getTabIndex,\n choose: (next, focus) => {\n setValue(next);\n if (focus) focusChosen(String(next));\n },\n preview: setHovered,\n registerItem,\n }),\n [\n value,\n drawnValue,\n step,\n max,\n size,\n resolvedColor,\n glyphs,\n disabled,\n starLabel,\n getTabIndex,\n setValue,\n focusChosen,\n registerItem,\n ],\n );\n\n return (\n <div\n role=\"radiogroup\"\n aria-label={defaultName}\n {...fieldProps}\n aria-disabled={disabled || undefined}\n data-color={resolvedColor}\n className={cn(\n ratingClassNames.root,\n 'relative inline-flex items-center gap-0.5',\n disabled && 'pointer-events-none opacity-50',\n className,\n )}\n {...rest}\n ref={mergedRef}\n data-roving-container=\"\"\n onKeyDown={composeEventHandlers(onKeyDown, handleKeyDown)}\n onFocus={composeEventHandlers(onFocus, containerProps.onFocus, {\n checkDefaultPrevented: false,\n })}\n onMouseLeave={composeEventHandlers(onMouseLeave, () => setHovered(0), {\n checkDefaultPrevented: false,\n })}\n >\n <RatingContext.Provider value={context}>{ratingItems(children, max)}</RatingContext.Provider>\n <HiddenInput\n type=\"radio\"\n name={name}\n form={form}\n disabled={disabled}\n value={value > 0 ? String(value) : ''}\n required={isRequired}\n onInvalid={focusTabStop}\n />\n </div>\n );\n};\nRatingRoot.displayName = 'Rating';\n\n/** Text size of the value and the count, following the star size. */\nconst textSizeMap: Record<Size, string> = {\n 'extra-small': 'text-caption-1',\n small: 'text-caption-1',\n medium: 'text-body-1',\n large: 'text-body-2',\n 'extra-large': 'text-body-2',\n};\n\nconst defaultRatingLabel = (_value: number, max: number, formattedValue: string) =>\n `Rating: ${formattedValue} out of ${max}`;\nconst defaultCountLabel = (count: number, formattedCount: string) =>\n count === 1 ? '1 rating' : `${formattedCount} ratings`;\n\n/** `locale` when `Intl` accepts it as a language tag, else `undefined` (the runtime default). */\nfunction supportedLocale(locale: string | undefined): string | undefined {\n if (locale === undefined) return undefined;\n try {\n Intl.getCanonicalLocales(locale);\n return locale;\n } catch {\n return undefined;\n }\n}\n\nconst RatingDisplayRoot = ({\n value,\n max = 5,\n size = 'medium',\n color,\n iconFilled,\n iconOutline,\n showValue = false,\n count,\n compact = false,\n locale,\n labels,\n className,\n children,\n ref,\n ...rest\n}: RatingDisplayProps) => {\n const formatLocale = supportedLocale(locale);\n const invalidLocale = locale !== undefined && formatLocale === undefined;\n React.useEffect(() => {\n if (invalidLocale) {\n warnOnce(\n `RatingDisplay:invalid-locale:${locale}`,\n `RatingDisplay: \\`locale\\` \"${locale}\" is not a valid BCP 47 language tag (use hyphens, as ` +\n 'in \"en-US\"). The runtime default locale formats the value instead, so the server and ' +\n 'the browser may render different text.',\n );\n }\n }, [invalidLocale, locale]);\n\n const formattedValue = new Intl.NumberFormat(formatLocale, { maximumFractionDigits: 1 }).format(\n value,\n );\n const formattedCount =\n count === undefined ? undefined : new Intl.NumberFormat(formatLocale).format(count);\n const ratingName = (labels?.rating ?? defaultRatingLabel)(value, max, formattedValue);\n const name =\n count === undefined || formattedCount === undefined\n ? ratingName\n : `${ratingName}, ${(labels?.count ?? defaultCountLabel)(count, formattedCount)}`;\n const showText = showValue || compact || count !== undefined;\n const textSize = textSizeMap[size];\n\n const resolvedColor = resolveRatingColor(color);\n const glyphs = useStarGlyphs('RatingDisplay', iconFilled, iconOutline);\n const { registerItem, counts } = useRatingItemRegistry();\n // A compact display ignores `children` (D26): it never renders items of its own, so a value\n // from 1 to `max` without an item is never \"missing\" on purpose.\n const hasItems = !compact && slotRendersContent(children);\n useRatingItemChecks(counts, max, hasItems);\n // Read-only: the stars take the inert actions (they choose, preview and rove nothing).\n const context = React.useMemo<RatingContextValue>(\n () => ({\n ...INERT_RATING_CONTEXT,\n kind: 'display',\n value,\n drawnValue: value,\n max,\n size,\n color: resolvedColor,\n glyphs,\n registerItem,\n }),\n [value, max, size, resolvedColor, glyphs, registerItem],\n );\n\n return (\n <div\n ref={ref}\n role=\"img\"\n aria-label={name}\n data-compact={compact ? '' : undefined}\n data-color={resolvedColor}\n className={cn(ratingDisplayClassNames.root, 'inline-flex items-center gap-0.5', className)}\n {...rest}\n >\n {compact ? (\n <StarGlyph\n starClassName={ratingDisplayClassNames.star}\n iconClassName={ratingDisplayClassNames.icon}\n fraction={1}\n size={size}\n color={resolvedColor}\n glyphs={glyphs}\n />\n ) : (\n <RatingContext.Provider value={context}>\n {ratingItems(children, max)}\n </RatingContext.Provider>\n )}\n {showText && (\n <span\n className={cn(\n ratingDisplayClassNames.value,\n 'ms-1 font-semibold text-foreground',\n textSize,\n )}\n >\n {formattedValue}\n </span>\n )}\n {formattedCount !== undefined && (\n <span className={cn(ratingDisplayClassNames.count, 'ms-1 text-muted-foreground', textSize)}>\n ({formattedCount})\n </span>\n )}\n </div>\n );\n};\nRatingDisplayRoot.displayName = 'RatingDisplay';\n\n/**\n * A star rating input (`role=\"radiogroup\"` of `role=\"radio\"` stars).\n *\n * - One tab stop (the radio of the chosen value, else the first). Keys choose relative to the\n * current rating and move focus to the chosen radio, as in 0.4: Right/Up one `step` more (a\n * star, or half a star), Left/Down one fewer (Left/Right mirrored under `dir=\"rtl\"`), Home the\n * smallest value (`step`) and End `max`. From an empty rating, Right/Up/Home choose `step`.\n * Keys at the ends (and Left/Down while empty) emit nothing.\n * - The keyboard cannot clear a rating: Left/Down stop at `step` (APG radio group; 0.4 went down\n * to 0). Only a controlled `value={0}` (or a form reset while `defaultValue` is 0) clears it.\n * - `step={0.5}` offers half stars. Each star stays one pointer target, which chooses by the\n * pointer's position (its half from the inline start, else its full value) and then focuses\n * the radio it chose; it holds two transparent radios (\"2.5 stars\", \"3 stars\") for the\n * keyboard and screen readers, and draws the focus ring of either. A controlled value between\n * two steps checks no radio and is drawn as the step below it; the keys move it to the next or\n * previous step.\n * - `onValueChange` (and the deprecated `onChange` alias) fire only when the rating changes, so\n * choosing the current star again calls neither.\n * - Hovering previews a value (half stars preview by the pointer's position); the preview never\n * outlives the hover or a disabled state.\n * - `color` colors the filled stars (`marigold`, the default, `brand` or `neutral`; the root\n * carries `data-color`); `iconFilled` and `iconOutline` replace the star glyphs, as a pair.\n * - Inside a `Field` it is named by the Field label (instead of the default \"Rating\") and\n * described by its hint and error. The stars are named \"1 star\", \"2 stars\", … (and a half value\n * \"2.5 stars\"); `labels` localizes these names.\n * - With `name` (or `required`) it takes part in native forms; a form reset restores\n * `defaultValue`.\n * - The stars are generated from `max`. Children that render content replace them: one\n * `Rating.Item` per value from 1 to `max`, chosen, previewed, roved and named as the generated\n * stars are (`max` still sets the key range and the star names).\n *\n * Sub-component: `Rating.Item` (the same component as `RatingDisplay.Item`). React Server\n * Components import its flat name `RatingItem` (dotted access needs a client file).\n */\nexport const Rating = /* @__PURE__ */ Object.assign(RatingRoot, { Item: RatingItem });\n\n/**\n * A read-only star rating (`role=\"img\"` named \"Rating: <value> out of <max>\"). A fractional value\n * is drawn with a partly filled star, so the stars and the name show the same value.\n *\n * - `showValue` adds the value as text after the stars, `count` the number of ratings (\"(1,160)\",\n * also added to the name), and `compact` shows one filled star with the value instead of `max`\n * stars (the root carries `data-compact`). Without them only the stars show.\n * - The value (up to one decimal) and the count are formatted with `locale`, in the text and in\n * the name; `labels` localizes the name. Pass `locale` when rendering on the server.\n * - `color` colors the filled stars (`marigold`, the default, `brand` or `neutral`; the root\n * carries `data-color`); `iconFilled` and `iconOutline` replace the star glyphs, as a pair.\n * - The stars are generated from `max`. Children that render content replace them: one\n * `RatingDisplay.Item` per value from 1 to `max`. A `compact` display ignores them.\n *\n * Sub-component: `RatingDisplay.Item` (the same component as `Rating.Item`). React Server\n * Components import its flat name `RatingDisplayItem`, or `RatingItem` (dotted access needs a\n * client file).\n */\nexport const RatingDisplay = /* @__PURE__ */ Object.assign(RatingDisplayRoot, {\n Item: RatingItem,\n});\n\n/**\n * Flat name of `RatingDisplay.Item` for React Server Components (the same component as\n * `RatingItem`).\n */\nexport const RatingDisplayItem = RatingItem;\n\nexport { RatingItem, type RatingItemProps, type RatingColor } from './Rating.item';\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/dev.cjs"),r=require("../../lib/slot.cjs"),i=require("../../lib/composeEventHandlers.cjs"),a=require("../../lib/classNames.cjs"),o=require("../../lib/direction.cjs"),s=require("../../lib/styles.cjs"),c=require("../button/Button.slots.cjs"),l=require("../../lib/icons.cjs"),u=require("../../lib/events.cjs");let d=require("react");d=e.__toESM(d,1);let f=require("react/jsx-runtime");var p={marigold:`text-rating`,brand:`text-primary`,neutral:`text-foreground`},m=`text-stroke-accessible`;function h(e){return e===`brand`||e===`neutral`?e:`marigold`}var g={"extra-small":`h-3 w-3`,small:`h-4 w-4`,medium:`h-5 w-5`,large:`h-6 w-6`,"extra-large":`h-8 w-8`},_={"extra-small":`p-1.5`,small:`p-1`,medium:`p-0.5`,large:`p-0.5`,"extra-large":`p-0.5`},v={filled:null,outline:null};function y(e){let{glyph:t,button:n}=c.unwrapButtonGlyph(e),i=r.slotRendersContent(t);return{glyph:i?t:null,button:n,empty:e!=null&&n===null&&!i}}function b(e,t,{button:r,empty:i}){d.useEffect(()=>{i&&n.warnOnce(`${e}:${t}-empty`,`${e}: \`${t}\` renders nothing, so the default star is used.`),r&&n.warnOnce(`${e}:${t}-button`,`${e}: \`${t}\` received ${r}; its children render as the glyph of the star and its props were dropped (buttons cannot be nested). Pass icon content instead, e.g. \`${t}={<MyIcon />}\`.`)},[e,t,i,r])}function x(e,t,r){let i=y(t),a=y(r),o=t===void 0!=(r===void 0);d.useEffect(()=>{o&&n.warnOnce(`${e}:icon-pair`,`${e}: pass both \`iconFilled\` and \`iconOutline\`: with only one of them, the other is the default star.`)},[e,o]),b(e,`iconFilled`,i),b(e,`iconOutline`,a);let s=i.glyph,c=a.glyph;return d.useMemo(()=>({filled:s,outline:c}),[s,c])}function S(e,t,n){return e?{colorClass:p[t],glyph:n.filled}:{colorClass:m,glyph:n.outline}}function C({filled:e,glyph:n,className:i}){return n==null?e?(0,f.jsx)(l.StarIcon,{className:i}):(0,f.jsx)(l.StarIcon,{className:i,fill:`none`,stroke:`currentColor`,strokeWidth:1.5}):r.renderSlot(n,`span`,t.cn(`inline-flex shrink-0 [&>svg]:size-full`,i),{"aria-hidden":!0})}C.displayName=`GlyphLayer`;var w=({fraction:e,size:n,color:r,glyphs:i,starClassName:a,iconClassName:o,className:s,ref:c,...l})=>{let u=Math.round(Math.min(1,Math.max(0,e))*100),d=g[n];if(u>0&&u<100){let e=S(!1,r,i),n=S(!0,r,i);return(0,f.jsxs)(`span`,{...l,ref:c,className:t.cn(a,`relative inline-flex`,e.colorClass,s),children:[(0,f.jsx)(C,{filled:!1,glyph:e.glyph,className:t.cn(o,d)}),(0,f.jsx)(`span`,{className:t.cn(`absolute inset-y-0 start-0 flex overflow-hidden`,n.colorClass),style:{width:`${u}%`},children:(0,f.jsx)(C,{filled:!0,glyph:n.glyph,className:t.cn(o,d,`shrink-0`)})})]})}let p=u===100,m=S(p,r,i);return(0,f.jsx)(`span`,{...l,ref:c,className:t.cn(a,`inline-flex`,m.colorClass,s),children:(0,f.jsx)(C,{filled:p,glyph:m.glyph,className:t.cn(o,d)})})};w.displayName=`StarGlyph`;var T=()=>{},E={kind:`display`,value:0,drawnValue:0,step:1,max:5,size:`medium`,color:`marigold`,glyphs:v,disabled:!0,starLabel:()=>``,getTabIndex:()=>-1,choose:T,preview:T,registerItem:()=>T},D=d.createContext(null);D.displayName=`RatingContext`;function O(){return d.useContext(D)||(n.reportMissingContext(`Rating.Item`,`a rating (Rating or RatingDisplay)`),E)}function k(){let[e]=d.useState(()=>{let e=new Map;return{counts:e,registerItem:t=>(e.set(t,(e.get(t)??0)+1),()=>{let n=(e.get(t)??1)-1;n>0?e.set(t,n):e.delete(t)})}});return e}function A(e,t,r){d.useEffect(()=>{for(let[r,i]of e)i>1&&n.warnOnce(`Rating.Item:duplicate:${r}`,`Rating.Item: two items have the value ${r}; give each star a unique value from 1 to max.`),(!Number.isInteger(r)||r<1||r>t)&&n.warnOnce(`Rating.Item:value:${r}`,`Rating.Item: the value ${r} is not a whole number from 1 to ${t}.`);if(r){for(let r=1;r<=t;r++)if(!e.has(r)){n.warnOnce(`Rating.Item:missing`,`Rating.Item: no item has the value ${r}; pass one Rating.Item per value from 1 to max.`);return}}})}var j=({value:e,iconFilled:n,iconOutline:r,className:c,ref:l,...p})=>{let m=O(),{registerItem:h}=m;d.useLayoutEffect(()=>h(e),[h,e]);let v=x(`Rating.Item`,n,r),y=n!==void 0||r!==void 0?v:m.glyphs,b=m.kind===`display`?t.cn(a.ratingItemClassNames.root,a.ratingDisplayClassNames.star):t.cn(a.ratingItemClassNames.root,a.ratingClassNames.star),T=m.kind===`display`?t.cn(a.ratingItemClassNames.icon,a.ratingDisplayClassNames.icon):t.cn(a.ratingItemClassNames.icon,a.ratingClassNames.icon);if(m.kind===`display`)return(0,f.jsx)(w,{...p,ref:l,starClassName:b,iconClassName:T,className:c,fraction:m.drawnValue-(e-1),size:m.size,color:m.color,glyphs:y});if(m.step===.5){let{onClick:n,onPointerMove:r,...a}=p,s=e-.5,d=m.drawnValue,h=d>=e?1:d>=s?.5:0,g=t=>{let n=t.currentTarget.getBoundingClientRect();return(o.getDirection(t.currentTarget)===`rtl`?n.right-t.clientX:t.clientX-n.left)<n.width/2?s:e};return(0,f.jsxs)(`span`,{...a,ref:l,className:t.cn(b,`relative inline-flex cursor-pointer rounded`,_[m.size],`has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ring`,m.disabled&&`cursor-not-allowed`,c),onClick:i.composeEventHandlers(n,e=>{if(m.disabled||e.detail===0||!u.isOwnEvent(e))return;let t=e.target.closest(`[role="radio"]`);t&&e.currentTarget.contains(t)||m.choose(g(e),!0)}),onPointerMove:i.composeEventHandlers(r,e=>{!m.disabled&&e.pointerType===`mouse`&&u.isOwnEvent(e)&&m.preview(g(e))}),children:[(0,f.jsx)(w,{iconClassName:T,fraction:h,size:m.size,color:m.color,glyphs:y}),[s,e].map((e,n)=>{let r=String(e);return(0,f.jsx)(`button`,{type:`button`,role:`radio`,"aria-checked":m.value===e,"aria-label":m.starLabel(e,m.max),"data-roving-value":r,tabIndex:m.disabled?-1:m.getTabIndex(r),disabled:m.disabled,className:t.cn(`pointer-events-none absolute inset-y-0 w-1/2 border-0 bg-transparent p-0 opacity-0`,n===0?`start-0`:`end-0`),onClick:()=>{m.disabled||m.choose(e,!1)}},r)})]})}let{onClick:E,onMouseEnter:D,...k}=p,A=String(e),j=m.drawnValue>=e,M=S(j,m.color,y);return(0,f.jsx)(`button`,{type:`button`,...k,ref:l,role:`radio`,"aria-checked":m.value===e,"aria-label":m.starLabel(e,m.max),"data-roving-value":A,tabIndex:m.disabled?-1:m.getTabIndex(A),disabled:m.disabled,className:t.cn(b,`inline-flex cursor-pointer rounded border-0 bg-transparent transition-colors motion-reduce:transition-none disabled:cursor-not-allowed`,_[m.size],s.focusRing,M.colorClass,c),onClick:i.composeEventHandlers(E,t=>{!m.disabled&&u.isOwnEvent(t)&&m.choose(e,!1)}),onMouseEnter:i.composeEventHandlers(D,t=>{!m.disabled&&u.isOwnEvent(t)&&m.preview(e)}),children:(0,f.jsx)(C,{filled:j,glyph:M.glyph,className:t.cn(T,g[m.size])})})};j.displayName=`RatingItem`;function M(e,t){let n=r.materialiseSlotContent(e);return r.slotRendersContent(n)?n:Array.from({length:t},(e,t)=>(0,f.jsx)(j,{value:t+1},t+1))}exports.FILLED_COLOR=p,exports.INERT_RATING_CONTEXT=E,exports.RatingContext=D,exports.RatingItem=j,exports.StarGlyph=w,exports.ratingItems=M,exports.resolveRatingColor=h,exports.useRatingContext=O,exports.useRatingItemChecks=A,exports.useRatingItemRegistry=k,exports.useStarGlyphs=x;
2
+ //# sourceMappingURL=Rating.item.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Rating.item.cjs","names":["unwrapButtonGlyph","slotRendersContent","React","renderSlot","cn","StarIcon","ratingItemClassNames","ratingDisplayClassNames","ratingClassNames","getDirection","composeEventHandlers","isOwnEvent","focusRing","materialiseSlotContent","require_runtime","require_cn","require_dev","require_slot","require_composeEventHandlers","require_classNames","require_direction","require_styles","require_Button_slots","require_icons","require_events","react","react_jsx_runtime","FILLED_COLOR","UNFILLED_COLOR","resolveRatingColor","color","sizeMap","targetPaddingMap","DEFAULT_GLYPHS","resolveGlyph","slot","renders","glyph","button","useGlyphWarnings","empty","component","name","useStarGlyphs","filled","iconFilled","outline","iconOutline","onlyOne","filledGlyph","outlineGlyph","starLook","glyphs","GlyphLayer","className","StarGlyph","rest","percent","fraction","starSize","size","unfilled","starClassName","iconClassName","look","noop","INERT_RATING_CONTEXT","RatingContext","useRatingContext","useRatingItemRegistry","registry","counts","value","count","useRatingItemChecks","max","hasItems","RatingItem","ctx","registerItem","ownGlyphs","starProps","half","drawn","pick","rect","event","onClick","radio","onPointerMove","radioKey","radioValue","index","radioProps","key","onMouseEnter","ratingItems","items","children","i"],"sources":["../../../src/components/input/Rating.item.tsx"],"sourcesContent":["import * as React from 'react';\nimport {\n ratingClassNames,\n ratingDisplayClassNames,\n ratingItemClassNames,\n} from '../../lib/classNames';\nimport { cn } from '../../lib/cn';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { reportMissingContext, warnOnce } from '../../lib/dev';\nimport { getDirection } from '../../lib/direction';\nimport { isOwnEvent } from '../../lib/events';\nimport { StarIcon } from '../../lib/icons';\nimport { materialiseSlotContent, renderSlot, slotRendersContent, type Slot } from '../../lib/slot';\nimport { focusRing } from '../../lib/styles';\nimport type { Size } from '../../lib/types';\nimport { unwrapButtonGlyph, type UnwrappedButton } from '../button/Button.slots';\n\n/** Color of a rating's filled stars. */\nexport type RatingColor = 'neutral' | 'brand' | 'marigold';\n\n/** Filled-star color of each `color` (unfilled stars keep {@link UNFILLED_COLOR}). */\nexport const FILLED_COLOR: Readonly<Record<RatingColor, string>> = {\n marigold: 'text-rating',\n brand: 'text-primary',\n neutral: 'text-foreground',\n};\n\n/** Unfilled-star color in every `color`: the outline token, with a 3:1 non-text contrast. */\nconst UNFILLED_COLOR = 'text-stroke-accessible';\n\n/**\n * The color a root draws (Phase 4 D24): `color` itself, or `'marigold'` (the default) for\n * `undefined` and for a value outside the union from untyped code.\n */\nexport function resolveRatingColor(color: RatingColor | undefined): RatingColor {\n return color === 'brand' || color === 'neutral' ? color : 'marigold';\n}\n\n/** Icon size of each star size. */\nconst sizeMap: Record<Size, string> = {\n 'extra-small': 'h-3 w-3',\n small: 'h-4 w-4',\n medium: 'h-5 w-5',\n large: 'h-6 w-6',\n 'extra-large': 'h-8 w-8',\n};\n\n/** Padding that gives every interactive star a target of at least 24×24px (WCAG 2.5.8). */\nconst targetPaddingMap: Record<Size, string> = {\n 'extra-small': 'p-1.5', // 12px icon + 12px\n small: 'p-1', // 16px icon + 8px\n medium: 'p-0.5', // 20px icon + 4px\n large: 'p-0.5',\n 'extra-large': 'p-0.5',\n};\n\n/**\n * The glyphs a star draws, from an `iconFilled`/`iconOutline` pair resolved by\n * {@link useStarGlyphs}: `null` draws the default star.\n */\nexport interface StarGlyphs {\n /** The glyph of a filled star, or `null` for the default star. */\n filled: Slot<'span'> | null;\n /** The glyph of an unfilled star, or `null` for the default star. */\n outline: Slot<'span'> | null;\n}\n\n/** The default stars: what a rating without an icon pair draws. */\nconst DEFAULT_GLYPHS: StarGlyphs = { filled: null, outline: null };\n\n/** One glyph of an icon pair, resolved, with what its development warnings need. */\ninterface ResolvedGlyph {\n /** What the star draws: the glyph, or `null` for the default star. */\n glyph: Slot<'span'> | null;\n /** The button unwrapped from the slot, if any. */\n button: UnwrappedButton | null;\n /** The slot is set but renders nothing (an unwrapped button warns about itself instead). */\n empty: boolean;\n}\n\n/**\n * One glyph of an icon pair (C-SLOTS: a required indicator, and a glyph inside a wired button,\n * the radio of a Rating): a button passed as the glyph is unwrapped, its children becoming the\n * glyph, and `null`, `undefined` and a value that renders nothing keep the default star.\n */\nfunction resolveGlyph(slot: Slot<'span'> | undefined): ResolvedGlyph {\n const { glyph, button } = unwrapButtonGlyph(slot);\n const renders = slotRendersContent(glyph);\n return {\n glyph: renders ? glyph : null,\n button,\n empty: slot != null && button === null && !renders,\n };\n}\n\n/** The development warnings of one glyph of a pair (C-DEV: from an effect, once per key). */\nfunction useGlyphWarnings(\n component: string,\n name: 'iconFilled' | 'iconOutline',\n { button, empty }: ResolvedGlyph,\n) {\n React.useEffect(() => {\n if (empty) {\n warnOnce(\n `${component}:${name}-empty`,\n `${component}: \\`${name}\\` renders nothing, so the default star is used.`,\n );\n }\n if (button) {\n warnOnce(\n `${component}:${name}-button`,\n `${component}: \\`${name}\\` received ${button}; its children render as the glyph of the ` +\n 'star and its props were dropped (buttons cannot be nested). Pass icon content ' +\n `instead, e.g. \\`${name}={<MyIcon />}\\`.`,\n );\n }\n }, [component, name, empty, button]);\n}\n\n/**\n * The glyphs of a component's `iconFilled`/`iconOutline` pair (Phase 4 D25), with the pair's\n * development warnings, each from an effect and once per key (C-DEV): only one of the two set\n * (`<component>:icon-pair`), a glyph that renders nothing (`<component>:iconFilled-empty`,\n * `<component>:iconOutline-empty`) and a button unwrapped (`<component>:iconFilled-button`,\n * `<component>:iconOutline-button`). The result keeps its identity while the glyphs do.\n *\n * @param component The public name that starts the keys and messages: `'Rating'`,\n * `'RatingDisplay'` or `'Rating.Item'`.\n */\nexport function useStarGlyphs(\n component: string,\n iconFilled: Slot<'span'> | undefined,\n iconOutline: Slot<'span'> | undefined,\n): StarGlyphs {\n const filled = resolveGlyph(iconFilled);\n const outline = resolveGlyph(iconOutline);\n // A custom glyph next to the default star mixes shapes.\n const onlyOne = (iconFilled === undefined) !== (iconOutline === undefined);\n React.useEffect(() => {\n if (onlyOne) {\n warnOnce(\n `${component}:icon-pair`,\n `${component}: pass both \\`iconFilled\\` and \\`iconOutline\\`: with only one of them, the ` +\n 'other is the default star.',\n );\n }\n }, [component, onlyOne]);\n useGlyphWarnings(component, 'iconFilled', filled);\n useGlyphWarnings(component, 'iconOutline', outline);\n const filledGlyph = filled.glyph;\n const outlineGlyph = outline.glyph;\n return React.useMemo(\n () => ({ filled: filledGlyph, outline: outlineGlyph }),\n [filledGlyph, outlineGlyph],\n );\n}\n\n/** How a whole star looks, filled or unfilled: see {@link starLook}. */\ninterface StarLook {\n /** The color class: the rating color when filled, else the outline token. */\n colorClass: string;\n /** The glyph of the pair to draw (`null`: the default star). */\n glyph: Slot<'span'> | null;\n}\n\n/**\n * The one look of a star (every star kind draws through it): a filled star takes the rating\n * color and the filled glyph of the pair, an unfilled star the outline token and the outline\n * glyph.\n */\nfunction starLook(filled: boolean, color: RatingColor, glyphs: StarGlyphs): StarLook {\n return filled\n ? { colorClass: FILLED_COLOR[color], glyph: glyphs.filled }\n : { colorClass: UNFILLED_COLOR, glyph: glyphs.outline };\n}\n\n/**\n * Draws only the glyph of a star, in the color its box sets: the custom `glyph` when there is\n * one, in a decorative span of the star's size (an SVG child fills it), else the default star,\n * filled or outlined. The box around it (a {@link StarGlyph}, or the radio of a whole star)\n * carries the color.\n */\nfunction GlyphLayer({\n filled,\n glyph,\n className,\n}: {\n filled: boolean;\n glyph: Slot<'span'> | null;\n className: string;\n}) {\n if (glyph != null) {\n return renderSlot(glyph, 'span', cn('inline-flex shrink-0 [&>svg]:size-full', className), {\n 'aria-hidden': true,\n });\n }\n return filled ? (\n <StarIcon className={className} />\n ) : (\n <StarIcon className={className} fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5} />\n );\n}\nGlyphLayer.displayName = 'GlyphLayer';\n\n/** Properties of {@link StarGlyph}. */\nexport interface StarGlyphProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {\n /**\n * How much of the star is filled: `1` filled, `0` unfilled, and a partly filled star in between\n * (clamped to 0…1, drawn to the whole percent).\n */\n fraction: number;\n /** The star size. */\n size: Size;\n /** The color of the filled glyph; the unfilled glyph keeps the 3:1 outline token. */\n color: RatingColor;\n /** The glyphs to draw, from {@link useStarGlyphs}. */\n glyphs: StarGlyphs;\n /**\n * The stable classes of the star's `<span>` (P3-00), first in its class list, before\n * `className`: a star part, and a RatingItem's root.\n */\n starClassName?: string;\n /** The stable classes of every glyph it draws (P3-00): the icon parts. */\n iconClassName?: string;\n /** Ref to the star's `<span>`. */\n ref?: React.Ref<HTMLSpanElement>;\n}\n\n/**\n * One star drawn filled to `fraction`, in a `<span>` that is the star's own box, so it draws the\n * same wherever it is placed: at 1 the filled glyph in the rating color, at 0 the unfilled glyph\n * in the outline color, and in between the unfilled glyph with the filled glyph clipped over it\n * from the inline start to the fraction (the reading direction of the stars, also under RTL).\n * The stars of a RatingDisplay (each item, and the compact star) and the half stars of a Rating\n * draw through it; a whole radio star is a box itself (its button carries the color) and draws\n * its glyph with {@link GlyphLayer}.\n */\nexport const StarGlyph = ({\n fraction,\n size,\n color,\n glyphs,\n starClassName,\n iconClassName,\n className,\n ref,\n ...rest\n}: StarGlyphProps) => {\n const percent = Math.round(Math.min(1, Math.max(0, fraction)) * 100);\n const starSize = sizeMap[size];\n if (percent > 0 && percent < 100) {\n const unfilled = starLook(false, color, glyphs);\n const filled = starLook(true, color, glyphs);\n return (\n <span\n {...rest}\n ref={ref}\n className={cn(starClassName, 'relative inline-flex', unfilled.colorClass, className)}\n >\n <GlyphLayer filled={false} glyph={unfilled.glyph} className={cn(iconClassName, starSize)} />\n <span\n className={cn('absolute inset-y-0 start-0 flex overflow-hidden', filled.colorClass)}\n style={{ width: `${percent}%` }}\n >\n <GlyphLayer\n filled\n glyph={filled.glyph}\n className={cn(iconClassName, starSize, 'shrink-0')}\n />\n </span>\n </span>\n );\n }\n const filled = percent === 100;\n const look = starLook(filled, color, glyphs);\n return (\n <span\n {...rest}\n ref={ref}\n className={cn(starClassName, 'inline-flex', look.colorClass, className)}\n >\n <GlyphLayer filled={filled} glyph={look.glyph} className={cn(iconClassName, starSize)} />\n </span>\n );\n};\nStarGlyph.displayName = 'StarGlyph';\n\n/**\n * What a Rating or a RatingDisplay tells its items: the stars render from it, whether the root\n * generated them or the consumer passed them as children.\n */\nexport interface RatingContextValue {\n /** `'input'`: the items are the radios of a Rating; `'display'`: the drawn stars of a RatingDisplay. */\n kind: 'input' | 'display';\n /** The chosen rating (a Rating) or the displayed value (a RatingDisplay). */\n value: number;\n /** The value the stars draw: a Rating's hover preview while there is one, else `value`. */\n drawnValue: number;\n /** The grain of the values a star chooses. */\n step: 0.5 | 1;\n /** The number of stars: the key range, and the second argument of `starLabel`. */\n max: number;\n /** The star size. */\n size: Size;\n /** The color of the filled stars (resolved: a value outside the union is `'marigold'`). */\n color: RatingColor;\n /**\n * The glyphs of the root's `iconFilled`/`iconOutline` pair, for every item (an item that sets\n * its own pair draws that one instead).\n */\n glyphs: StarGlyphs;\n /** The stars cannot be chosen or previewed (a disabled Rating, and every RatingDisplay). */\n disabled: boolean;\n /** The accessible name of the radio that chooses `value` (the root's `labels.star`). */\n starLabel: (value: number, max: number) => string;\n /** The roving `tabIndex` of the radio whose `data-roving-value` is `key`. */\n getTabIndex: (key: string) => number;\n /**\n * Chooses `value`; with `focus`, the radio of `value` also takes focus, without scrolling (a\n * pointer click on a half star).\n */\n choose: (value: number, focus: boolean) => void;\n /** Previews `value` under the mouse (`0` clears the preview). */\n preview: (value: number) => void;\n /** Counts a mounted item's value; returns the cleanup that uncounts it. */\n registerItem: (value: number) => () => void;\n}\n\nconst noop = () => {};\n\n/**\n * The context of an item outside a rating (production; development throws there): an empty,\n * read-only star. RatingDisplay starts from it, since its stars choose, preview and rove nothing.\n */\nexport const INERT_RATING_CONTEXT: RatingContextValue = {\n kind: 'display',\n value: 0,\n drawnValue: 0,\n step: 1,\n max: 5,\n size: 'medium',\n color: 'marigold',\n glyphs: DEFAULT_GLYPHS,\n disabled: true,\n starLabel: () => '',\n getTabIndex: () => -1,\n choose: noop,\n preview: noop,\n registerItem: () => noop,\n};\n\n/** Provided by Rating and RatingDisplay to their items; `null` outside them. */\nexport const RatingContext = React.createContext<RatingContextValue | null>(null);\nRatingContext.displayName = 'RatingContext';\n\n/** C-CONTEXT: throws in development, logs once and returns the inert value in production. */\nexport function useRatingContext(): RatingContextValue {\n const context = React.useContext(RatingContext);\n if (context) return context;\n reportMissingContext('Rating.Item', 'a rating (Rating or RatingDisplay)');\n return INERT_RATING_CONTEXT;\n}\n\n/** The item registry of a rating root: the values of its mounted items, counted. */\nexport interface RatingItemRegistry {\n /**\n * Counts a mounted item's value and returns the cleanup that uncounts it. Items call it from a\n * layout effect.\n */\n registerItem: (value: number) => () => void;\n /**\n * How many mounted items have each value. The items change it in their layout effects, so read\n * it in an effect of the root (a passive effect runs after every layout effect of the commit),\n * never during render.\n */\n counts: ReadonlyMap<number, number>;\n}\n\n/**\n * A rating root's item registry: one object, with the same `registerItem` and `counts`, for the\n * root's lifetime, so the memoized context keeps its identity.\n */\nexport function useRatingItemRegistry(): RatingItemRegistry {\n const [registry] = React.useState<RatingItemRegistry>(() => {\n const counts = new Map<number, number>();\n return {\n counts,\n registerItem: (value) => {\n counts.set(value, (counts.get(value) ?? 0) + 1);\n return () => {\n const count = (counts.get(value) ?? 1) - 1;\n if (count > 0) counts.set(value, count);\n else counts.delete(value);\n };\n },\n };\n });\n return registry;\n}\n\n/**\n * The item value checks of a rating root (Phase 4 D26, C-DEV): a passive effect, run after every\n * layout effect of the commit (so every item's own layout effect has already registered its\n * value, C-HOOKS: `counts` is never read during render), that warns once per problem found. No\n * dependency array: `counts` keeps its identity for the root's lifetime (`useRatingItemRegistry`)\n * and only its entries change in place, so the effect must re-run on every commit to see a later\n * change, exactly as the registration effects of `useListbox` do.\n *\n * - A value more than one item registers is not unique (`Rating.Item:duplicate:<value>`).\n * - A value that is not a whole number from 1 to `max` — untyped code, or a mistake — is not one\n * of the star positions the root can draw (`Rating.Item:value:<value>`).\n * - `hasItems`: the root draws its own children instead of the `max` generated stars, which\n * always cover every value on their own; only then can a value from 1 to `max` be left without\n * an item, and only the first one found is named (`Rating.Item:missing`).\n */\nexport function useRatingItemChecks(\n counts: ReadonlyMap<number, number>,\n max: number,\n hasItems: boolean,\n): void {\n React.useEffect(() => {\n for (const [value, count] of counts) {\n if (count > 1) {\n warnOnce(\n `Rating.Item:duplicate:${value}`,\n `Rating.Item: two items have the value ${value}; give each star a unique value from 1 ` +\n 'to max.',\n );\n }\n if (!Number.isInteger(value) || value < 1 || value > max) {\n warnOnce(\n `Rating.Item:value:${value}`,\n `Rating.Item: the value ${value} is not a whole number from 1 to ${max}.`,\n );\n }\n }\n if (!hasItems) return;\n for (let value = 1; value <= max; value++) {\n if (!counts.has(value)) {\n warnOnce(\n 'Rating.Item:missing',\n `Rating.Item: no item has the value ${value}; pass one Rating.Item per value from 1 to ` +\n 'max.',\n );\n return;\n }\n }\n });\n}\n\n/** Properties for the RatingItem component (`Rating.Item`, `RatingDisplay.Item`). */\nexport interface RatingItemProps extends Omit<\n React.HTMLAttributes<HTMLElement>,\n 'role' | 'aria-checked' | 'aria-label' | 'aria-labelledby' | 'tabIndex' | 'children' | 'onChange'\n> {\n /**\n * The star's full value, from 1 to the root's `max`: the rating it chooses (with the root's\n * `step={0.5}` also its half, `value - 0.5`), or shows.\n */\n value: number;\n /**\n * The glyph of this star when filled, as a pair with `iconOutline`, following the rules of the\n * root's `iconFilled` (a button is unwrapped, its children becoming the glyph, and a value or\n * button children that render nothing draw the default star). An item that sets either glyph\n * of the pair (`null` included) draws its own pair instead of its root's, and a glyph it leaves\n * unset is the default star (a development warning says so).\n */\n iconFilled?: Slot<'span'>;\n /**\n * The glyph of this star when unfilled, as a pair with `iconFilled`, following the rules of the\n * root's `iconOutline` (a button is unwrapped, its children becoming the glyph, and a value or\n * button children that render nothing draw the default star). An item that sets either glyph\n * of the pair (`null` included) draws its own pair instead of its root's, and a glyph it leaves\n * unset is the default star (a development warning says so).\n */\n iconOutline?: Slot<'span'>;\n /**\n * Ref to the star element: the `role=\"radio\"` button in a Rating, the star `<span>` in a Rating\n * with `step={0.5}` and in a RatingDisplay.\n */\n ref?: React.Ref<HTMLElement>;\n}\n\n/**\n * One star of a `Rating` or a `RatingDisplay`, drawn from its root: pass one per value from 1 to\n * `max` as the root's children, in place of the stars it generates.\n *\n * - In a `Rating` it is a `role=\"radio\"` button named by the root's `labels.star` (\"2 stars\"),\n * chosen, previewed and roved as a generated star is. With the root's `step={0.5}` it is a star\n * `<span>`, one pointer target that chooses by the pointer's position, holding two transparent\n * radios: its half value, then its full value (\"1.5 stars\", \"2 stars\"). In a `RatingDisplay` it\n * is a star filled, partly filled or empty by the root's value.\n * - It draws the root's `color` and glyph pair, or its own `iconFilled`/`iconOutline` pair when it\n * sets one.\n * - `className`, `style`, `data-*`, the other attributes, the handlers and `ref` go to that star\n * element, the consumer's handlers first. The role, name, checked state and tab stop of its\n * radio (or radios) come from the root.\n * - At `step={1}` the consumer's `onClick` and `onMouseEnter` compose onto the radio itself, so a\n * `preventDefault()` in either vetoes that star's own activation or hover preview. At\n * `step={0.5}` `onClick` and `onPointerMove` compose onto the star `<span>` instead: its two\n * radios run their own `onClick` first and choose their own value directly (Space, Enter, a\n * screen reader, or `radio.click()`), before the click bubbles to the span's composed handler,\n * so a `preventDefault()` there vetoes only a pointer-chosen value, never a keyboard or\n * assistive-technology activation.\n * - Outside a `Rating` or `RatingDisplay` it throws in development.\n */\nexport const RatingItem = ({\n value,\n iconFilled,\n iconOutline,\n className,\n ref,\n ...rest\n}: RatingItemProps) => {\n const ctx = useRatingContext();\n const { registerItem } = ctx;\n React.useLayoutEffect(() => registerItem(value), [registerItem, value]);\n // The item's own pair, when it sets either glyph, replaces its root's pair as a whole.\n const ownGlyphs = useStarGlyphs('Rating.Item', iconFilled, iconOutline);\n const glyphs = iconFilled !== undefined || iconOutline !== undefined ? ownGlyphs : ctx.glyphs;\n // Stable classes (P3-00): the item's own, then its rating's part classes (a public component\n // rendered inside another, D5), whether the root generated the item or the consumer passed it.\n const starClassName =\n ctx.kind === 'display'\n ? cn(ratingItemClassNames.root, ratingDisplayClassNames.star)\n : cn(ratingItemClassNames.root, ratingClassNames.star);\n const iconClassName =\n ctx.kind === 'display'\n ? cn(ratingItemClassNames.icon, ratingDisplayClassNames.icon)\n : cn(ratingItemClassNames.icon, ratingClassNames.icon);\n\n // `rest` keeps every consumer handler: a star without handlers of its own (a display star)\n // spreads them all, and a star that composes one takes that one out of `rest` itself.\n if (ctx.kind === 'display') {\n // Filled by the share of this star that the drawn value covers.\n return (\n <StarGlyph\n {...rest}\n ref={ref as React.Ref<HTMLSpanElement>}\n starClassName={starClassName}\n iconClassName={iconClassName}\n className={className}\n fraction={ctx.drawnValue - (value - 1)}\n size={ctx.size}\n color={ctx.color}\n glyphs={glyphs}\n />\n );\n }\n\n if (ctx.step === 0.5) {\n // A half star (D22): one pointer target (a span with the padding of a whole star) holding two\n // transparent radios over its halves, the half value first. The pointer chooses by its\n // position over the star; the keyboard and screen readers use the radios. The span composes\n // the consumer's click and pointer-move handlers with its own.\n const { onClick, onPointerMove, ...starProps } = rest;\n const half = value - 0.5;\n const drawn = ctx.drawnValue;\n const fraction = drawn >= value ? 1 : drawn >= half ? 0.5 : 0;\n // The value under the pointer: the half from the inline start (mirrored under RTL), else the\n // full value.\n const pick = (event: React.MouseEvent<HTMLElement>) => {\n const rect = event.currentTarget.getBoundingClientRect();\n const fromStart =\n getDirection(event.currentTarget) === 'rtl'\n ? rect.right - event.clientX\n : event.clientX - rect.left;\n return fromStart < rect.width / 2 ? half : value;\n };\n return (\n <span\n {...starProps}\n ref={ref as React.Ref<HTMLSpanElement>}\n className={cn(\n starClassName,\n 'relative inline-flex cursor-pointer rounded',\n targetPaddingMap[ctx.size],\n 'has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ring',\n ctx.disabled && 'cursor-not-allowed',\n className,\n )}\n onClick={composeEventHandlers(onClick, (event) => {\n // A click on a radio (Space, Enter, a screen reader, `click()`) chose its own value, and\n // a click without a press (`detail` 0) or from a portal inside the star is not the\n // pointer's to place. The radio search is bounded to this star: an unrelated\n // `role=\"radio\"` ancestor elsewhere in the document (the star's own radios have no\n // children, so a pointer click's target is never one of them, `pointer-events-none`)\n // must not silence every pointer pick.\n if (ctx.disabled || event.detail === 0 || !isOwnEvent(event)) return;\n const radio = (event.target as Element).closest('[role=\"radio\"]');\n if (radio && event.currentTarget.contains(radio)) return;\n ctx.choose(pick(event), true);\n })}\n onPointerMove={composeEventHandlers(onPointerMove, (event) => {\n if (!ctx.disabled && event.pointerType === 'mouse' && isOwnEvent(event)) {\n ctx.preview(pick(event));\n }\n })}\n >\n <StarGlyph\n iconClassName={iconClassName}\n fraction={fraction}\n size={ctx.size}\n color={ctx.color}\n glyphs={glyphs}\n />\n {[half, value].map((radioValue, index) => {\n const radioKey = String(radioValue);\n return (\n <button\n key={radioKey}\n type=\"button\"\n role=\"radio\"\n aria-checked={ctx.value === radioValue}\n aria-label={ctx.starLabel(radioValue, ctx.max)}\n data-roving-value={radioKey}\n tabIndex={ctx.disabled ? -1 : ctx.getTabIndex(radioKey)}\n disabled={ctx.disabled}\n className={cn(\n 'pointer-events-none absolute inset-y-0 w-1/2 border-0 bg-transparent p-0 opacity-0',\n index === 0 ? 'start-0' : 'end-0',\n )}\n onClick={() => {\n // Choosing the current value again is a no-op for both callbacks (useControllable).\n if (!ctx.disabled) ctx.choose(radioValue, false);\n }}\n />\n );\n })}\n </span>\n );\n }\n\n // The radio composes the consumer's click and mouse-enter handlers with its own. It is the\n // star's box itself (the button carries the color), so it draws only the glyph.\n const { onClick, onMouseEnter, ...radioProps } = rest;\n const key = String(value);\n const filled = ctx.drawnValue >= value;\n const look = starLook(filled, ctx.color, glyphs);\n return (\n <button\n type=\"button\"\n {...radioProps}\n ref={ref as React.Ref<HTMLButtonElement>}\n role=\"radio\"\n aria-checked={ctx.value === value}\n aria-label={ctx.starLabel(value, ctx.max)}\n data-roving-value={key}\n tabIndex={ctx.disabled ? -1 : ctx.getTabIndex(key)}\n disabled={ctx.disabled}\n className={cn(\n starClassName,\n 'inline-flex cursor-pointer rounded border-0 bg-transparent transition-colors motion-reduce:transition-none disabled:cursor-not-allowed',\n targetPaddingMap[ctx.size],\n focusRing,\n look.colorClass,\n className,\n )}\n onClick={composeEventHandlers(onClick, (event) => {\n // Choosing the current star again is a no-op for both callbacks (useControllable). A click\n // from a portal rendered inside the star (a custom glyph's popup) is not the star's.\n if (!ctx.disabled && isOwnEvent(event)) ctx.choose(value, false);\n })}\n onMouseEnter={composeEventHandlers(onMouseEnter, (event) => {\n if (!ctx.disabled && isOwnEvent(event)) ctx.preview(value);\n })}\n >\n <GlyphLayer\n filled={filled}\n glyph={look.glyph}\n className={cn(iconClassName, sizeMap[ctx.size])}\n />\n </button>\n );\n};\nRatingItem.displayName = 'RatingItem';\n\n/**\n * A root's stars: its `children` when they render content, else one generated item per value\n * from 1 to `max`.\n */\nexport function ratingItems(children: React.ReactNode, max: number): React.ReactNode {\n const items = materialiseSlotContent(children);\n return slotRendersContent(items)\n ? items\n : Array.from({ length: max }, (_, i) => <RatingItem key={i + 1} value={i + 1} />);\n}\n"],"mappings":"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"}
@@ -0,0 +1,255 @@
1
+ "use client";
2
+ import { cn as e } from "../../lib/cn.mjs";
3
+ import { reportMissingContext as t, warnOnce as n } from "../../lib/dev.mjs";
4
+ import { materialiseSlotContent as r, renderSlot as i, slotRendersContent as a } from "../../lib/slot.mjs";
5
+ import { composeEventHandlers as o } from "../../lib/composeEventHandlers.mjs";
6
+ import { ratingClassNames as s, ratingDisplayClassNames as c, ratingItemClassNames as l } from "../../lib/classNames.mjs";
7
+ import { getDirection as u } from "../../lib/direction.mjs";
8
+ import { focusRing as d } from "../../lib/styles.mjs";
9
+ import { unwrapButtonGlyph as f } from "../button/Button.slots.mjs";
10
+ import { StarIcon as p } from "../../lib/icons.mjs";
11
+ import { isOwnEvent as m } from "../../lib/events.mjs";
12
+ import * as h from "react";
13
+ import { jsx as g, jsxs as _ } from "react/jsx-runtime";
14
+ //#region src/components/input/Rating.item.tsx
15
+ var v = {
16
+ marigold: "text-rating",
17
+ brand: "text-primary",
18
+ neutral: "text-foreground"
19
+ }, y = "text-stroke-accessible";
20
+ function b(e) {
21
+ return e === "brand" || e === "neutral" ? e : "marigold";
22
+ }
23
+ var x = {
24
+ "extra-small": "h-3 w-3",
25
+ small: "h-4 w-4",
26
+ medium: "h-5 w-5",
27
+ large: "h-6 w-6",
28
+ "extra-large": "h-8 w-8"
29
+ }, S = {
30
+ "extra-small": "p-1.5",
31
+ small: "p-1",
32
+ medium: "p-0.5",
33
+ large: "p-0.5",
34
+ "extra-large": "p-0.5"
35
+ }, C = {
36
+ filled: null,
37
+ outline: null
38
+ };
39
+ function w(e) {
40
+ let { glyph: t, button: n } = f(e), r = a(t);
41
+ return {
42
+ glyph: r ? t : null,
43
+ button: n,
44
+ empty: e != null && n === null && !r
45
+ };
46
+ }
47
+ function T(e, t, { button: r, empty: i }) {
48
+ h.useEffect(() => {
49
+ i && n(`${e}:${t}-empty`, `${e}: \`${t}\` renders nothing, so the default star is used.`), r && n(`${e}:${t}-button`, `${e}: \`${t}\` received ${r}; its children render as the glyph of the star and its props were dropped (buttons cannot be nested). Pass icon content instead, e.g. \`${t}={<MyIcon />}\`.`);
50
+ }, [
51
+ e,
52
+ t,
53
+ i,
54
+ r
55
+ ]);
56
+ }
57
+ function E(e, t, r) {
58
+ let i = w(t), a = w(r), o = t === void 0 != (r === void 0);
59
+ h.useEffect(() => {
60
+ o && n(`${e}:icon-pair`, `${e}: pass both \`iconFilled\` and \`iconOutline\`: with only one of them, the other is the default star.`);
61
+ }, [e, o]), T(e, "iconFilled", i), T(e, "iconOutline", a);
62
+ let s = i.glyph, c = a.glyph;
63
+ return h.useMemo(() => ({
64
+ filled: s,
65
+ outline: c
66
+ }), [s, c]);
67
+ }
68
+ function D(e, t, n) {
69
+ return e ? {
70
+ colorClass: v[t],
71
+ glyph: n.filled
72
+ } : {
73
+ colorClass: y,
74
+ glyph: n.outline
75
+ };
76
+ }
77
+ function O({ filled: t, glyph: n, className: r }) {
78
+ return n == null ? t ? /* @__PURE__ */ g(p, { className: r }) : /* @__PURE__ */ g(p, {
79
+ className: r,
80
+ fill: "none",
81
+ stroke: "currentColor",
82
+ strokeWidth: 1.5
83
+ }) : i(n, "span", e("inline-flex shrink-0 [&>svg]:size-full", r), { "aria-hidden": !0 });
84
+ }
85
+ O.displayName = "GlyphLayer";
86
+ var k = ({ fraction: t, size: n, color: r, glyphs: i, starClassName: a, iconClassName: o, className: s, ref: c, ...l }) => {
87
+ let u = Math.round(Math.min(1, Math.max(0, t)) * 100), d = x[n];
88
+ if (u > 0 && u < 100) {
89
+ let t = D(!1, r, i), n = D(!0, r, i);
90
+ return /* @__PURE__ */ _("span", {
91
+ ...l,
92
+ ref: c,
93
+ className: e(a, "relative inline-flex", t.colorClass, s),
94
+ children: [/* @__PURE__ */ g(O, {
95
+ filled: !1,
96
+ glyph: t.glyph,
97
+ className: e(o, d)
98
+ }), /* @__PURE__ */ g("span", {
99
+ className: e("absolute inset-y-0 start-0 flex overflow-hidden", n.colorClass),
100
+ style: { width: `${u}%` },
101
+ children: /* @__PURE__ */ g(O, {
102
+ filled: !0,
103
+ glyph: n.glyph,
104
+ className: e(o, d, "shrink-0")
105
+ })
106
+ })]
107
+ });
108
+ }
109
+ let f = u === 100, p = D(f, r, i);
110
+ return /* @__PURE__ */ g("span", {
111
+ ...l,
112
+ ref: c,
113
+ className: e(a, "inline-flex", p.colorClass, s),
114
+ children: /* @__PURE__ */ g(O, {
115
+ filled: f,
116
+ glyph: p.glyph,
117
+ className: e(o, d)
118
+ })
119
+ });
120
+ };
121
+ k.displayName = "StarGlyph";
122
+ var A = () => {}, j = {
123
+ kind: "display",
124
+ value: 0,
125
+ drawnValue: 0,
126
+ step: 1,
127
+ max: 5,
128
+ size: "medium",
129
+ color: "marigold",
130
+ glyphs: C,
131
+ disabled: !0,
132
+ starLabel: () => "",
133
+ getTabIndex: () => -1,
134
+ choose: A,
135
+ preview: A,
136
+ registerItem: () => A
137
+ }, M = h.createContext(null);
138
+ M.displayName = "RatingContext";
139
+ function N() {
140
+ return h.useContext(M) || (t("Rating.Item", "a rating (Rating or RatingDisplay)"), j);
141
+ }
142
+ function P() {
143
+ let [e] = h.useState(() => {
144
+ let e = /* @__PURE__ */ new Map();
145
+ return {
146
+ counts: e,
147
+ registerItem: (t) => (e.set(t, (e.get(t) ?? 0) + 1), () => {
148
+ let n = (e.get(t) ?? 1) - 1;
149
+ n > 0 ? e.set(t, n) : e.delete(t);
150
+ })
151
+ };
152
+ });
153
+ return e;
154
+ }
155
+ function F(e, t, r) {
156
+ h.useEffect(() => {
157
+ for (let [r, i] of e) i > 1 && n(`Rating.Item:duplicate:${r}`, `Rating.Item: two items have the value ${r}; give each star a unique value from 1 to max.`), (!Number.isInteger(r) || r < 1 || r > t) && n(`Rating.Item:value:${r}`, `Rating.Item: the value ${r} is not a whole number from 1 to ${t}.`);
158
+ if (r) {
159
+ for (let r = 1; r <= t; r++) if (!e.has(r)) {
160
+ n("Rating.Item:missing", `Rating.Item: no item has the value ${r}; pass one Rating.Item per value from 1 to max.`);
161
+ return;
162
+ }
163
+ }
164
+ });
165
+ }
166
+ var I = ({ value: t, iconFilled: n, iconOutline: r, className: i, ref: a, ...f }) => {
167
+ let p = N(), { registerItem: v } = p;
168
+ h.useLayoutEffect(() => v(t), [v, t]);
169
+ let y = E("Rating.Item", n, r), b = n !== void 0 || r !== void 0 ? y : p.glyphs, C = p.kind === "display" ? e(l.root, c.star) : e(l.root, s.star), w = p.kind === "display" ? e(l.icon, c.icon) : e(l.icon, s.icon);
170
+ if (p.kind === "display") return /* @__PURE__ */ g(k, {
171
+ ...f,
172
+ ref: a,
173
+ starClassName: C,
174
+ iconClassName: w,
175
+ className: i,
176
+ fraction: p.drawnValue - (t - 1),
177
+ size: p.size,
178
+ color: p.color,
179
+ glyphs: b
180
+ });
181
+ if (p.step === .5) {
182
+ let { onClick: n, onPointerMove: r, ...s } = f, c = t - .5, l = p.drawnValue, d = l >= t ? 1 : l >= c ? .5 : 0, h = (e) => {
183
+ let n = e.currentTarget.getBoundingClientRect();
184
+ return (u(e.currentTarget) === "rtl" ? n.right - e.clientX : e.clientX - n.left) < n.width / 2 ? c : t;
185
+ };
186
+ return /* @__PURE__ */ _("span", {
187
+ ...s,
188
+ ref: a,
189
+ className: e(C, "relative inline-flex cursor-pointer rounded", S[p.size], "has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ring", p.disabled && "cursor-not-allowed", i),
190
+ onClick: o(n, (e) => {
191
+ if (p.disabled || e.detail === 0 || !m(e)) return;
192
+ let t = e.target.closest("[role=\"radio\"]");
193
+ t && e.currentTarget.contains(t) || p.choose(h(e), !0);
194
+ }),
195
+ onPointerMove: o(r, (e) => {
196
+ !p.disabled && e.pointerType === "mouse" && m(e) && p.preview(h(e));
197
+ }),
198
+ children: [/* @__PURE__ */ g(k, {
199
+ iconClassName: w,
200
+ fraction: d,
201
+ size: p.size,
202
+ color: p.color,
203
+ glyphs: b
204
+ }), [c, t].map((t, n) => {
205
+ let r = String(t);
206
+ return /* @__PURE__ */ g("button", {
207
+ type: "button",
208
+ role: "radio",
209
+ "aria-checked": p.value === t,
210
+ "aria-label": p.starLabel(t, p.max),
211
+ "data-roving-value": r,
212
+ tabIndex: p.disabled ? -1 : p.getTabIndex(r),
213
+ disabled: p.disabled,
214
+ className: e("pointer-events-none absolute inset-y-0 w-1/2 border-0 bg-transparent p-0 opacity-0", n === 0 ? "start-0" : "end-0"),
215
+ onClick: () => {
216
+ p.disabled || p.choose(t, !1);
217
+ }
218
+ }, r);
219
+ })]
220
+ });
221
+ }
222
+ let { onClick: T, onMouseEnter: A, ...j } = f, M = String(t), P = p.drawnValue >= t, F = D(P, p.color, b);
223
+ return /* @__PURE__ */ g("button", {
224
+ type: "button",
225
+ ...j,
226
+ ref: a,
227
+ role: "radio",
228
+ "aria-checked": p.value === t,
229
+ "aria-label": p.starLabel(t, p.max),
230
+ "data-roving-value": M,
231
+ tabIndex: p.disabled ? -1 : p.getTabIndex(M),
232
+ disabled: p.disabled,
233
+ className: e(C, "inline-flex cursor-pointer rounded border-0 bg-transparent transition-colors motion-reduce:transition-none disabled:cursor-not-allowed", S[p.size], d, F.colorClass, i),
234
+ onClick: o(T, (e) => {
235
+ !p.disabled && m(e) && p.choose(t, !1);
236
+ }),
237
+ onMouseEnter: o(A, (e) => {
238
+ !p.disabled && m(e) && p.preview(t);
239
+ }),
240
+ children: /* @__PURE__ */ g(O, {
241
+ filled: P,
242
+ glyph: F.glyph,
243
+ className: e(w, x[p.size])
244
+ })
245
+ });
246
+ };
247
+ I.displayName = "RatingItem";
248
+ function L(e, t) {
249
+ let n = r(e);
250
+ return a(n) ? n : Array.from({ length: t }, (e, t) => /* @__PURE__ */ g(I, { value: t + 1 }, t + 1));
251
+ }
252
+ //#endregion
253
+ export { v as FILLED_COLOR, j as INERT_RATING_CONTEXT, M as RatingContext, I as RatingItem, k as StarGlyph, L as ratingItems, b as resolveRatingColor, N as useRatingContext, F as useRatingItemChecks, P as useRatingItemRegistry, E as useStarGlyphs };
254
+
255
+ //# sourceMappingURL=Rating.item.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Rating.item.mjs","names":["React","FILLED_COLOR","UNFILLED_COLOR","resolveRatingColor","color","sizeMap","targetPaddingMap","DEFAULT_GLYPHS","resolveGlyph","unwrapButtonGlyph","slot","renders","slotRendersContent","glyph","button","useGlyphWarnings","empty","warnOnce","component","name","useStarGlyphs","filled","iconFilled","outline","iconOutline","onlyOne","filledGlyph","outlineGlyph","starLook","glyphs","GlyphLayer","StarIcon","renderSlot","cn","className","StarGlyph","rest","percent","fraction","starSize","size","unfilled","starClassName","iconClassName","look","noop","INERT_RATING_CONTEXT","RatingContext","useRatingContext","reportMissingContext","useRatingItemRegistry","registry","counts","value","count","useRatingItemChecks","max","hasItems","RatingItem","ctx","registerItem","ownGlyphs","ratingItemClassNames","ratingDisplayClassNames","ratingClassNames","starProps","half","drawn","pick","rect","event","getDirection","composeEventHandlers","onClick","isOwnEvent","radio","onPointerMove","radioKey","radioValue","index","radioProps","key","focusRing","onMouseEnter","ratingItems","items","materialiseSlotContent","children","i"],"sources":["../../../src/components/input/Rating.item.tsx"],"sourcesContent":["import * as React from 'react';\nimport {\n ratingClassNames,\n ratingDisplayClassNames,\n ratingItemClassNames,\n} from '../../lib/classNames';\nimport { cn } from '../../lib/cn';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { reportMissingContext, warnOnce } from '../../lib/dev';\nimport { getDirection } from '../../lib/direction';\nimport { isOwnEvent } from '../../lib/events';\nimport { StarIcon } from '../../lib/icons';\nimport { materialiseSlotContent, renderSlot, slotRendersContent, type Slot } from '../../lib/slot';\nimport { focusRing } from '../../lib/styles';\nimport type { Size } from '../../lib/types';\nimport { unwrapButtonGlyph, type UnwrappedButton } from '../button/Button.slots';\n\n/** Color of a rating's filled stars. */\nexport type RatingColor = 'neutral' | 'brand' | 'marigold';\n\n/** Filled-star color of each `color` (unfilled stars keep {@link UNFILLED_COLOR}). */\nexport const FILLED_COLOR: Readonly<Record<RatingColor, string>> = {\n marigold: 'text-rating',\n brand: 'text-primary',\n neutral: 'text-foreground',\n};\n\n/** Unfilled-star color in every `color`: the outline token, with a 3:1 non-text contrast. */\nconst UNFILLED_COLOR = 'text-stroke-accessible';\n\n/**\n * The color a root draws (Phase 4 D24): `color` itself, or `'marigold'` (the default) for\n * `undefined` and for a value outside the union from untyped code.\n */\nexport function resolveRatingColor(color: RatingColor | undefined): RatingColor {\n return color === 'brand' || color === 'neutral' ? color : 'marigold';\n}\n\n/** Icon size of each star size. */\nconst sizeMap: Record<Size, string> = {\n 'extra-small': 'h-3 w-3',\n small: 'h-4 w-4',\n medium: 'h-5 w-5',\n large: 'h-6 w-6',\n 'extra-large': 'h-8 w-8',\n};\n\n/** Padding that gives every interactive star a target of at least 24×24px (WCAG 2.5.8). */\nconst targetPaddingMap: Record<Size, string> = {\n 'extra-small': 'p-1.5', // 12px icon + 12px\n small: 'p-1', // 16px icon + 8px\n medium: 'p-0.5', // 20px icon + 4px\n large: 'p-0.5',\n 'extra-large': 'p-0.5',\n};\n\n/**\n * The glyphs a star draws, from an `iconFilled`/`iconOutline` pair resolved by\n * {@link useStarGlyphs}: `null` draws the default star.\n */\nexport interface StarGlyphs {\n /** The glyph of a filled star, or `null` for the default star. */\n filled: Slot<'span'> | null;\n /** The glyph of an unfilled star, or `null` for the default star. */\n outline: Slot<'span'> | null;\n}\n\n/** The default stars: what a rating without an icon pair draws. */\nconst DEFAULT_GLYPHS: StarGlyphs = { filled: null, outline: null };\n\n/** One glyph of an icon pair, resolved, with what its development warnings need. */\ninterface ResolvedGlyph {\n /** What the star draws: the glyph, or `null` for the default star. */\n glyph: Slot<'span'> | null;\n /** The button unwrapped from the slot, if any. */\n button: UnwrappedButton | null;\n /** The slot is set but renders nothing (an unwrapped button warns about itself instead). */\n empty: boolean;\n}\n\n/**\n * One glyph of an icon pair (C-SLOTS: a required indicator, and a glyph inside a wired button,\n * the radio of a Rating): a button passed as the glyph is unwrapped, its children becoming the\n * glyph, and `null`, `undefined` and a value that renders nothing keep the default star.\n */\nfunction resolveGlyph(slot: Slot<'span'> | undefined): ResolvedGlyph {\n const { glyph, button } = unwrapButtonGlyph(slot);\n const renders = slotRendersContent(glyph);\n return {\n glyph: renders ? glyph : null,\n button,\n empty: slot != null && button === null && !renders,\n };\n}\n\n/** The development warnings of one glyph of a pair (C-DEV: from an effect, once per key). */\nfunction useGlyphWarnings(\n component: string,\n name: 'iconFilled' | 'iconOutline',\n { button, empty }: ResolvedGlyph,\n) {\n React.useEffect(() => {\n if (empty) {\n warnOnce(\n `${component}:${name}-empty`,\n `${component}: \\`${name}\\` renders nothing, so the default star is used.`,\n );\n }\n if (button) {\n warnOnce(\n `${component}:${name}-button`,\n `${component}: \\`${name}\\` received ${button}; its children render as the glyph of the ` +\n 'star and its props were dropped (buttons cannot be nested). Pass icon content ' +\n `instead, e.g. \\`${name}={<MyIcon />}\\`.`,\n );\n }\n }, [component, name, empty, button]);\n}\n\n/**\n * The glyphs of a component's `iconFilled`/`iconOutline` pair (Phase 4 D25), with the pair's\n * development warnings, each from an effect and once per key (C-DEV): only one of the two set\n * (`<component>:icon-pair`), a glyph that renders nothing (`<component>:iconFilled-empty`,\n * `<component>:iconOutline-empty`) and a button unwrapped (`<component>:iconFilled-button`,\n * `<component>:iconOutline-button`). The result keeps its identity while the glyphs do.\n *\n * @param component The public name that starts the keys and messages: `'Rating'`,\n * `'RatingDisplay'` or `'Rating.Item'`.\n */\nexport function useStarGlyphs(\n component: string,\n iconFilled: Slot<'span'> | undefined,\n iconOutline: Slot<'span'> | undefined,\n): StarGlyphs {\n const filled = resolveGlyph(iconFilled);\n const outline = resolveGlyph(iconOutline);\n // A custom glyph next to the default star mixes shapes.\n const onlyOne = (iconFilled === undefined) !== (iconOutline === undefined);\n React.useEffect(() => {\n if (onlyOne) {\n warnOnce(\n `${component}:icon-pair`,\n `${component}: pass both \\`iconFilled\\` and \\`iconOutline\\`: with only one of them, the ` +\n 'other is the default star.',\n );\n }\n }, [component, onlyOne]);\n useGlyphWarnings(component, 'iconFilled', filled);\n useGlyphWarnings(component, 'iconOutline', outline);\n const filledGlyph = filled.glyph;\n const outlineGlyph = outline.glyph;\n return React.useMemo(\n () => ({ filled: filledGlyph, outline: outlineGlyph }),\n [filledGlyph, outlineGlyph],\n );\n}\n\n/** How a whole star looks, filled or unfilled: see {@link starLook}. */\ninterface StarLook {\n /** The color class: the rating color when filled, else the outline token. */\n colorClass: string;\n /** The glyph of the pair to draw (`null`: the default star). */\n glyph: Slot<'span'> | null;\n}\n\n/**\n * The one look of a star (every star kind draws through it): a filled star takes the rating\n * color and the filled glyph of the pair, an unfilled star the outline token and the outline\n * glyph.\n */\nfunction starLook(filled: boolean, color: RatingColor, glyphs: StarGlyphs): StarLook {\n return filled\n ? { colorClass: FILLED_COLOR[color], glyph: glyphs.filled }\n : { colorClass: UNFILLED_COLOR, glyph: glyphs.outline };\n}\n\n/**\n * Draws only the glyph of a star, in the color its box sets: the custom `glyph` when there is\n * one, in a decorative span of the star's size (an SVG child fills it), else the default star,\n * filled or outlined. The box around it (a {@link StarGlyph}, or the radio of a whole star)\n * carries the color.\n */\nfunction GlyphLayer({\n filled,\n glyph,\n className,\n}: {\n filled: boolean;\n glyph: Slot<'span'> | null;\n className: string;\n}) {\n if (glyph != null) {\n return renderSlot(glyph, 'span', cn('inline-flex shrink-0 [&>svg]:size-full', className), {\n 'aria-hidden': true,\n });\n }\n return filled ? (\n <StarIcon className={className} />\n ) : (\n <StarIcon className={className} fill=\"none\" stroke=\"currentColor\" strokeWidth={1.5} />\n );\n}\nGlyphLayer.displayName = 'GlyphLayer';\n\n/** Properties of {@link StarGlyph}. */\nexport interface StarGlyphProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'color'> {\n /**\n * How much of the star is filled: `1` filled, `0` unfilled, and a partly filled star in between\n * (clamped to 0…1, drawn to the whole percent).\n */\n fraction: number;\n /** The star size. */\n size: Size;\n /** The color of the filled glyph; the unfilled glyph keeps the 3:1 outline token. */\n color: RatingColor;\n /** The glyphs to draw, from {@link useStarGlyphs}. */\n glyphs: StarGlyphs;\n /**\n * The stable classes of the star's `<span>` (P3-00), first in its class list, before\n * `className`: a star part, and a RatingItem's root.\n */\n starClassName?: string;\n /** The stable classes of every glyph it draws (P3-00): the icon parts. */\n iconClassName?: string;\n /** Ref to the star's `<span>`. */\n ref?: React.Ref<HTMLSpanElement>;\n}\n\n/**\n * One star drawn filled to `fraction`, in a `<span>` that is the star's own box, so it draws the\n * same wherever it is placed: at 1 the filled glyph in the rating color, at 0 the unfilled glyph\n * in the outline color, and in between the unfilled glyph with the filled glyph clipped over it\n * from the inline start to the fraction (the reading direction of the stars, also under RTL).\n * The stars of a RatingDisplay (each item, and the compact star) and the half stars of a Rating\n * draw through it; a whole radio star is a box itself (its button carries the color) and draws\n * its glyph with {@link GlyphLayer}.\n */\nexport const StarGlyph = ({\n fraction,\n size,\n color,\n glyphs,\n starClassName,\n iconClassName,\n className,\n ref,\n ...rest\n}: StarGlyphProps) => {\n const percent = Math.round(Math.min(1, Math.max(0, fraction)) * 100);\n const starSize = sizeMap[size];\n if (percent > 0 && percent < 100) {\n const unfilled = starLook(false, color, glyphs);\n const filled = starLook(true, color, glyphs);\n return (\n <span\n {...rest}\n ref={ref}\n className={cn(starClassName, 'relative inline-flex', unfilled.colorClass, className)}\n >\n <GlyphLayer filled={false} glyph={unfilled.glyph} className={cn(iconClassName, starSize)} />\n <span\n className={cn('absolute inset-y-0 start-0 flex overflow-hidden', filled.colorClass)}\n style={{ width: `${percent}%` }}\n >\n <GlyphLayer\n filled\n glyph={filled.glyph}\n className={cn(iconClassName, starSize, 'shrink-0')}\n />\n </span>\n </span>\n );\n }\n const filled = percent === 100;\n const look = starLook(filled, color, glyphs);\n return (\n <span\n {...rest}\n ref={ref}\n className={cn(starClassName, 'inline-flex', look.colorClass, className)}\n >\n <GlyphLayer filled={filled} glyph={look.glyph} className={cn(iconClassName, starSize)} />\n </span>\n );\n};\nStarGlyph.displayName = 'StarGlyph';\n\n/**\n * What a Rating or a RatingDisplay tells its items: the stars render from it, whether the root\n * generated them or the consumer passed them as children.\n */\nexport interface RatingContextValue {\n /** `'input'`: the items are the radios of a Rating; `'display'`: the drawn stars of a RatingDisplay. */\n kind: 'input' | 'display';\n /** The chosen rating (a Rating) or the displayed value (a RatingDisplay). */\n value: number;\n /** The value the stars draw: a Rating's hover preview while there is one, else `value`. */\n drawnValue: number;\n /** The grain of the values a star chooses. */\n step: 0.5 | 1;\n /** The number of stars: the key range, and the second argument of `starLabel`. */\n max: number;\n /** The star size. */\n size: Size;\n /** The color of the filled stars (resolved: a value outside the union is `'marigold'`). */\n color: RatingColor;\n /**\n * The glyphs of the root's `iconFilled`/`iconOutline` pair, for every item (an item that sets\n * its own pair draws that one instead).\n */\n glyphs: StarGlyphs;\n /** The stars cannot be chosen or previewed (a disabled Rating, and every RatingDisplay). */\n disabled: boolean;\n /** The accessible name of the radio that chooses `value` (the root's `labels.star`). */\n starLabel: (value: number, max: number) => string;\n /** The roving `tabIndex` of the radio whose `data-roving-value` is `key`. */\n getTabIndex: (key: string) => number;\n /**\n * Chooses `value`; with `focus`, the radio of `value` also takes focus, without scrolling (a\n * pointer click on a half star).\n */\n choose: (value: number, focus: boolean) => void;\n /** Previews `value` under the mouse (`0` clears the preview). */\n preview: (value: number) => void;\n /** Counts a mounted item's value; returns the cleanup that uncounts it. */\n registerItem: (value: number) => () => void;\n}\n\nconst noop = () => {};\n\n/**\n * The context of an item outside a rating (production; development throws there): an empty,\n * read-only star. RatingDisplay starts from it, since its stars choose, preview and rove nothing.\n */\nexport const INERT_RATING_CONTEXT: RatingContextValue = {\n kind: 'display',\n value: 0,\n drawnValue: 0,\n step: 1,\n max: 5,\n size: 'medium',\n color: 'marigold',\n glyphs: DEFAULT_GLYPHS,\n disabled: true,\n starLabel: () => '',\n getTabIndex: () => -1,\n choose: noop,\n preview: noop,\n registerItem: () => noop,\n};\n\n/** Provided by Rating and RatingDisplay to their items; `null` outside them. */\nexport const RatingContext = React.createContext<RatingContextValue | null>(null);\nRatingContext.displayName = 'RatingContext';\n\n/** C-CONTEXT: throws in development, logs once and returns the inert value in production. */\nexport function useRatingContext(): RatingContextValue {\n const context = React.useContext(RatingContext);\n if (context) return context;\n reportMissingContext('Rating.Item', 'a rating (Rating or RatingDisplay)');\n return INERT_RATING_CONTEXT;\n}\n\n/** The item registry of a rating root: the values of its mounted items, counted. */\nexport interface RatingItemRegistry {\n /**\n * Counts a mounted item's value and returns the cleanup that uncounts it. Items call it from a\n * layout effect.\n */\n registerItem: (value: number) => () => void;\n /**\n * How many mounted items have each value. The items change it in their layout effects, so read\n * it in an effect of the root (a passive effect runs after every layout effect of the commit),\n * never during render.\n */\n counts: ReadonlyMap<number, number>;\n}\n\n/**\n * A rating root's item registry: one object, with the same `registerItem` and `counts`, for the\n * root's lifetime, so the memoized context keeps its identity.\n */\nexport function useRatingItemRegistry(): RatingItemRegistry {\n const [registry] = React.useState<RatingItemRegistry>(() => {\n const counts = new Map<number, number>();\n return {\n counts,\n registerItem: (value) => {\n counts.set(value, (counts.get(value) ?? 0) + 1);\n return () => {\n const count = (counts.get(value) ?? 1) - 1;\n if (count > 0) counts.set(value, count);\n else counts.delete(value);\n };\n },\n };\n });\n return registry;\n}\n\n/**\n * The item value checks of a rating root (Phase 4 D26, C-DEV): a passive effect, run after every\n * layout effect of the commit (so every item's own layout effect has already registered its\n * value, C-HOOKS: `counts` is never read during render), that warns once per problem found. No\n * dependency array: `counts` keeps its identity for the root's lifetime (`useRatingItemRegistry`)\n * and only its entries change in place, so the effect must re-run on every commit to see a later\n * change, exactly as the registration effects of `useListbox` do.\n *\n * - A value more than one item registers is not unique (`Rating.Item:duplicate:<value>`).\n * - A value that is not a whole number from 1 to `max` — untyped code, or a mistake — is not one\n * of the star positions the root can draw (`Rating.Item:value:<value>`).\n * - `hasItems`: the root draws its own children instead of the `max` generated stars, which\n * always cover every value on their own; only then can a value from 1 to `max` be left without\n * an item, and only the first one found is named (`Rating.Item:missing`).\n */\nexport function useRatingItemChecks(\n counts: ReadonlyMap<number, number>,\n max: number,\n hasItems: boolean,\n): void {\n React.useEffect(() => {\n for (const [value, count] of counts) {\n if (count > 1) {\n warnOnce(\n `Rating.Item:duplicate:${value}`,\n `Rating.Item: two items have the value ${value}; give each star a unique value from 1 ` +\n 'to max.',\n );\n }\n if (!Number.isInteger(value) || value < 1 || value > max) {\n warnOnce(\n `Rating.Item:value:${value}`,\n `Rating.Item: the value ${value} is not a whole number from 1 to ${max}.`,\n );\n }\n }\n if (!hasItems) return;\n for (let value = 1; value <= max; value++) {\n if (!counts.has(value)) {\n warnOnce(\n 'Rating.Item:missing',\n `Rating.Item: no item has the value ${value}; pass one Rating.Item per value from 1 to ` +\n 'max.',\n );\n return;\n }\n }\n });\n}\n\n/** Properties for the RatingItem component (`Rating.Item`, `RatingDisplay.Item`). */\nexport interface RatingItemProps extends Omit<\n React.HTMLAttributes<HTMLElement>,\n 'role' | 'aria-checked' | 'aria-label' | 'aria-labelledby' | 'tabIndex' | 'children' | 'onChange'\n> {\n /**\n * The star's full value, from 1 to the root's `max`: the rating it chooses (with the root's\n * `step={0.5}` also its half, `value - 0.5`), or shows.\n */\n value: number;\n /**\n * The glyph of this star when filled, as a pair with `iconOutline`, following the rules of the\n * root's `iconFilled` (a button is unwrapped, its children becoming the glyph, and a value or\n * button children that render nothing draw the default star). An item that sets either glyph\n * of the pair (`null` included) draws its own pair instead of its root's, and a glyph it leaves\n * unset is the default star (a development warning says so).\n */\n iconFilled?: Slot<'span'>;\n /**\n * The glyph of this star when unfilled, as a pair with `iconFilled`, following the rules of the\n * root's `iconOutline` (a button is unwrapped, its children becoming the glyph, and a value or\n * button children that render nothing draw the default star). An item that sets either glyph\n * of the pair (`null` included) draws its own pair instead of its root's, and a glyph it leaves\n * unset is the default star (a development warning says so).\n */\n iconOutline?: Slot<'span'>;\n /**\n * Ref to the star element: the `role=\"radio\"` button in a Rating, the star `<span>` in a Rating\n * with `step={0.5}` and in a RatingDisplay.\n */\n ref?: React.Ref<HTMLElement>;\n}\n\n/**\n * One star of a `Rating` or a `RatingDisplay`, drawn from its root: pass one per value from 1 to\n * `max` as the root's children, in place of the stars it generates.\n *\n * - In a `Rating` it is a `role=\"radio\"` button named by the root's `labels.star` (\"2 stars\"),\n * chosen, previewed and roved as a generated star is. With the root's `step={0.5}` it is a star\n * `<span>`, one pointer target that chooses by the pointer's position, holding two transparent\n * radios: its half value, then its full value (\"1.5 stars\", \"2 stars\"). In a `RatingDisplay` it\n * is a star filled, partly filled or empty by the root's value.\n * - It draws the root's `color` and glyph pair, or its own `iconFilled`/`iconOutline` pair when it\n * sets one.\n * - `className`, `style`, `data-*`, the other attributes, the handlers and `ref` go to that star\n * element, the consumer's handlers first. The role, name, checked state and tab stop of its\n * radio (or radios) come from the root.\n * - At `step={1}` the consumer's `onClick` and `onMouseEnter` compose onto the radio itself, so a\n * `preventDefault()` in either vetoes that star's own activation or hover preview. At\n * `step={0.5}` `onClick` and `onPointerMove` compose onto the star `<span>` instead: its two\n * radios run their own `onClick` first and choose their own value directly (Space, Enter, a\n * screen reader, or `radio.click()`), before the click bubbles to the span's composed handler,\n * so a `preventDefault()` there vetoes only a pointer-chosen value, never a keyboard or\n * assistive-technology activation.\n * - Outside a `Rating` or `RatingDisplay` it throws in development.\n */\nexport const RatingItem = ({\n value,\n iconFilled,\n iconOutline,\n className,\n ref,\n ...rest\n}: RatingItemProps) => {\n const ctx = useRatingContext();\n const { registerItem } = ctx;\n React.useLayoutEffect(() => registerItem(value), [registerItem, value]);\n // The item's own pair, when it sets either glyph, replaces its root's pair as a whole.\n const ownGlyphs = useStarGlyphs('Rating.Item', iconFilled, iconOutline);\n const glyphs = iconFilled !== undefined || iconOutline !== undefined ? ownGlyphs : ctx.glyphs;\n // Stable classes (P3-00): the item's own, then its rating's part classes (a public component\n // rendered inside another, D5), whether the root generated the item or the consumer passed it.\n const starClassName =\n ctx.kind === 'display'\n ? cn(ratingItemClassNames.root, ratingDisplayClassNames.star)\n : cn(ratingItemClassNames.root, ratingClassNames.star);\n const iconClassName =\n ctx.kind === 'display'\n ? cn(ratingItemClassNames.icon, ratingDisplayClassNames.icon)\n : cn(ratingItemClassNames.icon, ratingClassNames.icon);\n\n // `rest` keeps every consumer handler: a star without handlers of its own (a display star)\n // spreads them all, and a star that composes one takes that one out of `rest` itself.\n if (ctx.kind === 'display') {\n // Filled by the share of this star that the drawn value covers.\n return (\n <StarGlyph\n {...rest}\n ref={ref as React.Ref<HTMLSpanElement>}\n starClassName={starClassName}\n iconClassName={iconClassName}\n className={className}\n fraction={ctx.drawnValue - (value - 1)}\n size={ctx.size}\n color={ctx.color}\n glyphs={glyphs}\n />\n );\n }\n\n if (ctx.step === 0.5) {\n // A half star (D22): one pointer target (a span with the padding of a whole star) holding two\n // transparent radios over its halves, the half value first. The pointer chooses by its\n // position over the star; the keyboard and screen readers use the radios. The span composes\n // the consumer's click and pointer-move handlers with its own.\n const { onClick, onPointerMove, ...starProps } = rest;\n const half = value - 0.5;\n const drawn = ctx.drawnValue;\n const fraction = drawn >= value ? 1 : drawn >= half ? 0.5 : 0;\n // The value under the pointer: the half from the inline start (mirrored under RTL), else the\n // full value.\n const pick = (event: React.MouseEvent<HTMLElement>) => {\n const rect = event.currentTarget.getBoundingClientRect();\n const fromStart =\n getDirection(event.currentTarget) === 'rtl'\n ? rect.right - event.clientX\n : event.clientX - rect.left;\n return fromStart < rect.width / 2 ? half : value;\n };\n return (\n <span\n {...starProps}\n ref={ref as React.Ref<HTMLSpanElement>}\n className={cn(\n starClassName,\n 'relative inline-flex cursor-pointer rounded',\n targetPaddingMap[ctx.size],\n 'has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-ring',\n ctx.disabled && 'cursor-not-allowed',\n className,\n )}\n onClick={composeEventHandlers(onClick, (event) => {\n // A click on a radio (Space, Enter, a screen reader, `click()`) chose its own value, and\n // a click without a press (`detail` 0) or from a portal inside the star is not the\n // pointer's to place. The radio search is bounded to this star: an unrelated\n // `role=\"radio\"` ancestor elsewhere in the document (the star's own radios have no\n // children, so a pointer click's target is never one of them, `pointer-events-none`)\n // must not silence every pointer pick.\n if (ctx.disabled || event.detail === 0 || !isOwnEvent(event)) return;\n const radio = (event.target as Element).closest('[role=\"radio\"]');\n if (radio && event.currentTarget.contains(radio)) return;\n ctx.choose(pick(event), true);\n })}\n onPointerMove={composeEventHandlers(onPointerMove, (event) => {\n if (!ctx.disabled && event.pointerType === 'mouse' && isOwnEvent(event)) {\n ctx.preview(pick(event));\n }\n })}\n >\n <StarGlyph\n iconClassName={iconClassName}\n fraction={fraction}\n size={ctx.size}\n color={ctx.color}\n glyphs={glyphs}\n />\n {[half, value].map((radioValue, index) => {\n const radioKey = String(radioValue);\n return (\n <button\n key={radioKey}\n type=\"button\"\n role=\"radio\"\n aria-checked={ctx.value === radioValue}\n aria-label={ctx.starLabel(radioValue, ctx.max)}\n data-roving-value={radioKey}\n tabIndex={ctx.disabled ? -1 : ctx.getTabIndex(radioKey)}\n disabled={ctx.disabled}\n className={cn(\n 'pointer-events-none absolute inset-y-0 w-1/2 border-0 bg-transparent p-0 opacity-0',\n index === 0 ? 'start-0' : 'end-0',\n )}\n onClick={() => {\n // Choosing the current value again is a no-op for both callbacks (useControllable).\n if (!ctx.disabled) ctx.choose(radioValue, false);\n }}\n />\n );\n })}\n </span>\n );\n }\n\n // The radio composes the consumer's click and mouse-enter handlers with its own. It is the\n // star's box itself (the button carries the color), so it draws only the glyph.\n const { onClick, onMouseEnter, ...radioProps } = rest;\n const key = String(value);\n const filled = ctx.drawnValue >= value;\n const look = starLook(filled, ctx.color, glyphs);\n return (\n <button\n type=\"button\"\n {...radioProps}\n ref={ref as React.Ref<HTMLButtonElement>}\n role=\"radio\"\n aria-checked={ctx.value === value}\n aria-label={ctx.starLabel(value, ctx.max)}\n data-roving-value={key}\n tabIndex={ctx.disabled ? -1 : ctx.getTabIndex(key)}\n disabled={ctx.disabled}\n className={cn(\n starClassName,\n 'inline-flex cursor-pointer rounded border-0 bg-transparent transition-colors motion-reduce:transition-none disabled:cursor-not-allowed',\n targetPaddingMap[ctx.size],\n focusRing,\n look.colorClass,\n className,\n )}\n onClick={composeEventHandlers(onClick, (event) => {\n // Choosing the current star again is a no-op for both callbacks (useControllable). A click\n // from a portal rendered inside the star (a custom glyph's popup) is not the star's.\n if (!ctx.disabled && isOwnEvent(event)) ctx.choose(value, false);\n })}\n onMouseEnter={composeEventHandlers(onMouseEnter, (event) => {\n if (!ctx.disabled && isOwnEvent(event)) ctx.preview(value);\n })}\n >\n <GlyphLayer\n filled={filled}\n glyph={look.glyph}\n className={cn(iconClassName, sizeMap[ctx.size])}\n />\n </button>\n );\n};\nRatingItem.displayName = 'RatingItem';\n\n/**\n * A root's stars: its `children` when they render content, else one generated item per value\n * from 1 to `max`.\n */\nexport function ratingItems(children: React.ReactNode, max: number): React.ReactNode {\n const items = materialiseSlotContent(children);\n return slotRendersContent(items)\n ? items\n : Array.from({ length: max }, (_, i) => <RatingItem key={i + 1} value={i + 1} />);\n}\n"],"mappings":"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"}