@weasel-js/ui 1.0.1 → 1.0.3

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 (267) hide show
  1. package/dist/chunks/ActionBar-GThu74LC.js.map +1 -1
  2. package/dist/chunks/{ActionsBar-CAjrj_Pt.js → ActionsBar-9aR5N3lU.js} +10 -17
  3. package/dist/chunks/ActionsBar-9aR5N3lU.js.map +1 -0
  4. package/dist/chunks/Badge-DZQqAV4d.js.map +1 -1
  5. package/dist/chunks/BandEditor-13K91O9M.js +228 -0
  6. package/dist/chunks/BandEditor-13K91O9M.js.map +1 -0
  7. package/dist/chunks/Button-9QNIYugK.js.map +1 -1
  8. package/dist/chunks/{Callout-DlirfLJl.js → Callout-CFqG5xho.js} +1 -1
  9. package/dist/chunks/Callout-CFqG5xho.js.map +1 -0
  10. package/dist/chunks/{Checkbox-B3Z_u7k0.js → Checkbox-KQ5L8APP.js} +1 -1
  11. package/dist/chunks/Checkbox-KQ5L8APP.js.map +1 -0
  12. package/dist/chunks/{ColorField-B4Hd2qqS.js → ColorField-ykpiD3XU.js} +1 -1
  13. package/dist/chunks/ColorField-ykpiD3XU.js.map +1 -0
  14. package/dist/chunks/{ComboBox-tbmgND-F.js → ComboBox-DpxsFVBy.js} +2 -2
  15. package/dist/chunks/ComboBox-DpxsFVBy.js.map +1 -0
  16. package/dist/chunks/{CurveEditor-CSKqAXMp.js → CurveEditor-CqIt_Xph.js} +3 -3
  17. package/dist/chunks/CurveEditor-CqIt_Xph.js.map +1 -0
  18. package/dist/chunks/DataGrid-BQklXGRl.js.map +1 -1
  19. package/dist/chunks/{Dialog-BFuakycR.js → Dialog-3FCd_gYi.js} +1 -1
  20. package/dist/chunks/Dialog-3FCd_gYi.js.map +1 -0
  21. package/dist/chunks/{Field-DPxmJzZz.js → Field-VQwmrIna.js} +1 -1
  22. package/dist/chunks/Field-VQwmrIna.js.map +1 -0
  23. package/dist/chunks/{GradientEditor-3pYXGAPg.js → GradientEditor-DeptQpWX.js} +5 -5
  24. package/dist/chunks/GradientEditor-DeptQpWX.js.map +1 -0
  25. package/dist/chunks/{Input-Dg_CYXg4.js → Input-DpkwYtUY.js} +2 -2
  26. package/dist/chunks/Input-DpkwYtUY.js.map +1 -0
  27. package/dist/chunks/Keycaps-CFX7UAj8.js.map +1 -1
  28. package/dist/chunks/{NumberField-BhxUgi43.js → NumberField-BbzI0GtU.js} +2 -2
  29. package/dist/chunks/NumberField-BbzI0GtU.js.map +1 -0
  30. package/dist/chunks/{OptionsBar-hQN-wfd-.js → OptionsBar-CHvH4n4u.js} +10 -17
  31. package/dist/chunks/OptionsBar-CHvH4n4u.js.map +1 -0
  32. package/dist/chunks/Plot2D-CEnhzodm.js.map +1 -1
  33. package/dist/chunks/{PointPlotter-DCSjvpN0.js → PointPlotter-DD-bWmRc.js} +2 -2
  34. package/dist/chunks/PointPlotter-DD-bWmRc.js.map +1 -0
  35. package/dist/chunks/{Powerline-DkOTPnUV.js → Powerline-Dd9Ukhcl.js} +1 -1
  36. package/dist/chunks/Powerline-Dd9Ukhcl.js.map +1 -0
  37. package/dist/chunks/{Prefs-C4HfuTvm.js → Prefs-lgI4x4jF.js} +10 -10
  38. package/dist/chunks/Prefs-lgI4x4jF.js.map +1 -0
  39. package/dist/chunks/{RadioGroup-Bg3Rqxe2.js → RadioGroup-CIpHr3g6.js} +2 -2
  40. package/dist/chunks/RadioGroup-CIpHr3g6.js.map +1 -0
  41. package/dist/chunks/{RangeSlider-BvM_DGSr.js → RangeSlider-CaDogGTj.js} +1 -1
  42. package/dist/chunks/RangeSlider-CaDogGTj.js.map +1 -0
  43. package/dist/chunks/{ResizeHandle-BUkW2kZC.js → ResizeHandle-BOpmtOOj.js} +2 -2
  44. package/dist/chunks/ResizeHandle-BOpmtOOj.js.map +1 -0
  45. package/dist/chunks/{Select-DC0AjrEe.js → Select-BE3Y1NrH.js} +2 -2
  46. package/dist/chunks/Select-BE3Y1NrH.js.map +1 -0
  47. package/dist/chunks/{SelectionPanel-DoTdisRY.js → SelectionPanel-Zaw8tYQH.js} +6 -6
  48. package/dist/chunks/SelectionPanel-Zaw8tYQH.js.map +1 -0
  49. package/dist/chunks/{Sidebar-B8qig3BQ.js → Sidebar-DP_nyJze.js} +1 -1
  50. package/dist/chunks/Sidebar-DP_nyJze.js.map +1 -0
  51. package/dist/chunks/{SidebarPanel-LOt2c-wb.js → SidebarPanel-BpD0ptyg.js} +1 -1
  52. package/dist/chunks/SidebarPanel-BpD0ptyg.js.map +1 -0
  53. package/dist/chunks/{Slider-Di2kNqVy.js → Slider-CmPeFJ3x.js} +4 -4
  54. package/dist/chunks/Slider-CmPeFJ3x.js.map +1 -0
  55. package/dist/chunks/{StatusBar-Cg2LzdAm.js → StatusBar-DBO0E0Yd.js} +1 -1
  56. package/dist/chunks/StatusBar-DBO0E0Yd.js.map +1 -0
  57. package/dist/chunks/{Switch-I4putUAY.js → Switch-JyVSSAzC.js} +1 -1
  58. package/dist/chunks/{Switch-I4putUAY.js.map → Switch-JyVSSAzC.js.map} +1 -1
  59. package/dist/chunks/{Tabs-irZuG_PN.js → Tabs-Df0sDomx.js} +1 -1
  60. package/dist/chunks/Tabs-Df0sDomx.js.map +1 -0
  61. package/dist/chunks/{Toast-BGYP8Pw0.js → Toast-DFw4IrD_.js} +1 -1
  62. package/dist/chunks/Toast-DFw4IrD_.js.map +1 -0
  63. package/dist/chunks/ToggleBar-BP1zFcvg.js.map +1 -1
  64. package/dist/chunks/{ToolButton-DUeg8fPN.js → ToolButton-DpT-mukU.js} +6 -5
  65. package/dist/chunks/ToolButton-DpT-mukU.js.map +1 -0
  66. package/dist/chunks/{ToolGroup-D0CvyQhQ.js → ToolGroup-BTIwd4U4.js} +1 -1
  67. package/dist/chunks/ToolGroup-BTIwd4U4.js.map +1 -0
  68. package/dist/chunks/{ToolOptionsBar-N_MQcSo4.js → ToolOptionsBar-JvmY7iNe.js} +1 -1
  69. package/dist/chunks/ToolOptionsBar-JvmY7iNe.js.map +1 -0
  70. package/dist/chunks/{ToolPalette-BipoMdZz.js → ToolPalette-DPn0owno.js} +27 -47
  71. package/dist/chunks/ToolPalette-DPn0owno.js.map +1 -0
  72. package/dist/chunks/Tooltip-Clgn4trm.js.map +1 -1
  73. package/dist/chunks/segmentedControl.module-DCyuDbj1.js +11 -0
  74. package/dist/chunks/segmentedControl.module-DCyuDbj1.js.map +1 -0
  75. package/dist/chunks/useRovingTabIndex-DBZtq4IH.js.map +1 -1
  76. package/dist/color/oklch.d.ts +12 -0
  77. package/dist/color/oklch.d.ts.map +1 -1
  78. package/dist/components/ActionBar/ActionBar.d.ts +1 -0
  79. package/dist/components/ActionBar/ActionBar.d.ts.map +1 -1
  80. package/dist/components/ActionsBar/ActionsBar.d.ts +15 -0
  81. package/dist/components/ActionsBar/ActionsBar.d.ts.map +1 -1
  82. package/dist/components/ActionsBar/index.js +1 -1
  83. package/dist/components/Badge/Badge.d.ts +14 -0
  84. package/dist/components/Badge/Badge.d.ts.map +1 -1
  85. package/dist/components/Badge/bases/edgeProfiles.d.ts +8 -0
  86. package/dist/components/Badge/bases/edgeProfiles.d.ts.map +1 -1
  87. package/dist/components/Badge/types.d.ts +11 -0
  88. package/dist/components/Badge/types.d.ts.map +1 -1
  89. package/dist/components/BandEditor/BandEditor.d.ts +56 -0
  90. package/dist/components/BandEditor/BandEditor.d.ts.map +1 -0
  91. package/dist/components/BandEditor/bands.d.ts +45 -0
  92. package/dist/components/BandEditor/bands.d.ts.map +1 -0
  93. package/dist/components/BandEditor/index.d.ts +4 -0
  94. package/dist/components/BandEditor/index.d.ts.map +1 -0
  95. package/dist/components/BandEditor/index.js +2 -0
  96. package/dist/components/BandEditor/scale.d.ts +25 -0
  97. package/dist/components/BandEditor/scale.d.ts.map +1 -0
  98. package/dist/components/Button/Button.d.ts +13 -0
  99. package/dist/components/Button/Button.d.ts.map +1 -1
  100. package/dist/components/Callout/Callout.d.ts +2 -0
  101. package/dist/components/Callout/Callout.d.ts.map +1 -1
  102. package/dist/components/Callout/index.js +1 -1
  103. package/dist/components/Checkbox/Checkbox.d.ts +4 -0
  104. package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
  105. package/dist/components/Checkbox/index.js +1 -1
  106. package/dist/components/ColorField/ColorField.d.ts +4 -0
  107. package/dist/components/ColorField/ColorField.d.ts.map +1 -1
  108. package/dist/components/ColorField/index.js +1 -1
  109. package/dist/components/ComboBox/ComboBox.d.ts +11 -0
  110. package/dist/components/ComboBox/ComboBox.d.ts.map +1 -1
  111. package/dist/components/ComboBox/index.js +1 -1
  112. package/dist/components/CurveEditor/CurveEditor.d.ts +14 -2
  113. package/dist/components/CurveEditor/CurveEditor.d.ts.map +1 -1
  114. package/dist/components/CurveEditor/LayeredCurveEditor.d.ts +15 -0
  115. package/dist/components/CurveEditor/LayeredCurveEditor.d.ts.map +1 -1
  116. package/dist/components/CurveEditor/createFunctionLayer.d.ts +39 -0
  117. package/dist/components/CurveEditor/createFunctionLayer.d.ts.map +1 -1
  118. package/dist/components/CurveEditor/index.js +1 -1
  119. package/dist/components/CurveEditor/layerTypes.d.ts +5 -0
  120. package/dist/components/CurveEditor/layerTypes.d.ts.map +1 -1
  121. package/dist/components/CurveEditor/setCurveOp.d.ts +4 -0
  122. package/dist/components/CurveEditor/setCurveOp.d.ts.map +1 -1
  123. package/dist/components/DataGrid/DataGrid.d.ts +12 -0
  124. package/dist/components/DataGrid/DataGrid.d.ts.map +1 -1
  125. package/dist/components/Dialog/Dialog.d.ts +1 -0
  126. package/dist/components/Dialog/Dialog.d.ts.map +1 -1
  127. package/dist/components/Dialog/index.js +1 -1
  128. package/dist/components/Field/Field.d.ts +9 -0
  129. package/dist/components/Field/Field.d.ts.map +1 -1
  130. package/dist/components/Field/index.js +1 -1
  131. package/dist/components/GradientEditor/GradientEditor.d.ts +4 -0
  132. package/dist/components/GradientEditor/GradientEditor.d.ts.map +1 -1
  133. package/dist/components/GradientEditor/GradientHandles.d.ts +4 -0
  134. package/dist/components/GradientEditor/GradientHandles.d.ts.map +1 -1
  135. package/dist/components/GradientEditor/index.js +1 -1
  136. package/dist/components/Input/Input.d.ts +1 -0
  137. package/dist/components/Input/Input.d.ts.map +1 -1
  138. package/dist/components/Input/index.js +1 -1
  139. package/dist/components/Keycaps/Keycap.d.ts +10 -0
  140. package/dist/components/Keycaps/Keycap.d.ts.map +1 -1
  141. package/dist/components/Keycaps/Keycaps.d.ts +2 -0
  142. package/dist/components/Keycaps/Keycaps.d.ts.map +1 -1
  143. package/dist/components/Keycaps/keySpecsFromMods.d.ts +2 -0
  144. package/dist/components/Keycaps/keySpecsFromMods.d.ts.map +1 -1
  145. package/dist/components/NumberField/NumberField.d.ts +9 -0
  146. package/dist/components/NumberField/NumberField.d.ts.map +1 -1
  147. package/dist/components/NumberField/index.js +1 -1
  148. package/dist/components/OptionsBar/OptionsBar.d.ts +12 -0
  149. package/dist/components/OptionsBar/OptionsBar.d.ts.map +1 -1
  150. package/dist/components/OptionsBar/index.js +1 -1
  151. package/dist/components/Plot2D/Plot2D.d.ts +14 -0
  152. package/dist/components/Plot2D/Plot2D.d.ts.map +1 -1
  153. package/dist/components/Plot2D/geometry.d.ts +10 -0
  154. package/dist/components/Plot2D/geometry.d.ts.map +1 -1
  155. package/dist/components/PointPlotter/PointPlotter.d.ts +3 -2
  156. package/dist/components/PointPlotter/PointPlotter.d.ts.map +1 -1
  157. package/dist/components/PointPlotter/index.js +1 -1
  158. package/dist/components/Powerline/Powerline.d.ts +13 -0
  159. package/dist/components/Powerline/Powerline.d.ts.map +1 -1
  160. package/dist/components/Powerline/index.js +1 -1
  161. package/dist/components/Prefs/PrefsDialog.d.ts +4 -0
  162. package/dist/components/Prefs/PrefsDialog.d.ts.map +1 -1
  163. package/dist/components/Prefs/PrefsForm.d.ts +6 -0
  164. package/dist/components/Prefs/PrefsForm.d.ts.map +1 -1
  165. package/dist/components/Prefs/index.js +1 -1
  166. package/dist/components/Prefs/schema.d.ts +17 -0
  167. package/dist/components/Prefs/schema.d.ts.map +1 -1
  168. package/dist/components/RadioGroup/RadioGroup.d.ts +8 -0
  169. package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  170. package/dist/components/RadioGroup/index.js +1 -1
  171. package/dist/components/RangeSlider/RangeSlider.d.ts +1 -0
  172. package/dist/components/RangeSlider/RangeSlider.d.ts.map +1 -1
  173. package/dist/components/RangeSlider/index.js +1 -1
  174. package/dist/components/ResizeHandle/ResizeHandle.d.ts +7 -3
  175. package/dist/components/ResizeHandle/ResizeHandle.d.ts.map +1 -1
  176. package/dist/components/ResizeHandle/index.js +1 -1
  177. package/dist/components/Select/Select.d.ts +11 -0
  178. package/dist/components/Select/Select.d.ts.map +1 -1
  179. package/dist/components/Select/index.js +1 -1
  180. package/dist/components/SelectionPanel/SelectionPanel.d.ts +6 -0
  181. package/dist/components/SelectionPanel/SelectionPanel.d.ts.map +1 -1
  182. package/dist/components/SelectionPanel/index.js +1 -1
  183. package/dist/components/SelectionPanel/model.d.ts +12 -0
  184. package/dist/components/SelectionPanel/model.d.ts.map +1 -1
  185. package/dist/components/Sidebar/Sidebar.d.ts +1 -0
  186. package/dist/components/Sidebar/Sidebar.d.ts.map +1 -1
  187. package/dist/components/Sidebar/index.js +1 -1
  188. package/dist/components/SidebarPanel/SidebarPanel.d.ts +1 -0
  189. package/dist/components/SidebarPanel/SidebarPanel.d.ts.map +1 -1
  190. package/dist/components/SidebarPanel/index.js +1 -1
  191. package/dist/components/Slider/Slider.d.ts +43 -2
  192. package/dist/components/Slider/Slider.d.ts.map +1 -1
  193. package/dist/components/Slider/index.js +1 -1
  194. package/dist/components/StatusBar/StatusBar.d.ts +2 -0
  195. package/dist/components/StatusBar/StatusBar.d.ts.map +1 -1
  196. package/dist/components/StatusBar/index.js +1 -1
  197. package/dist/components/Switch/Switch.d.ts +11 -0
  198. package/dist/components/Switch/Switch.d.ts.map +1 -1
  199. package/dist/components/Switch/index.js +1 -1
  200. package/dist/components/Tabs/Tabs.d.ts +11 -0
  201. package/dist/components/Tabs/Tabs.d.ts.map +1 -1
  202. package/dist/components/Tabs/index.js +1 -1
  203. package/dist/components/Toast/Toast.d.ts +2 -0
  204. package/dist/components/Toast/Toast.d.ts.map +1 -1
  205. package/dist/components/Toast/index.js +1 -1
  206. package/dist/components/Toast/queue.d.ts +7 -0
  207. package/dist/components/Toast/queue.d.ts.map +1 -1
  208. package/dist/components/ToggleBar/ToggleBar.d.ts +16 -0
  209. package/dist/components/ToggleBar/ToggleBar.d.ts.map +1 -1
  210. package/dist/components/ToolButton/ToolButton.d.ts +5 -1
  211. package/dist/components/ToolButton/ToolButton.d.ts.map +1 -1
  212. package/dist/components/ToolButton/index.js +1 -1
  213. package/dist/components/ToolGroup/ToolGroup.d.ts +1 -0
  214. package/dist/components/ToolGroup/ToolGroup.d.ts.map +1 -1
  215. package/dist/components/ToolGroup/index.js +1 -1
  216. package/dist/components/ToolOptionsBar/ToolOptionsBar.d.ts +1 -0
  217. package/dist/components/ToolOptionsBar/ToolOptionsBar.d.ts.map +1 -1
  218. package/dist/components/ToolOptionsBar/index.js +1 -1
  219. package/dist/components/ToolPalette/ToolPalette.d.ts +9 -0
  220. package/dist/components/ToolPalette/ToolPalette.d.ts.map +1 -1
  221. package/dist/components/ToolPalette/index.js +1 -1
  222. package/dist/components/Tooltip/Tooltip.d.ts +5 -0
  223. package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
  224. package/dist/format/number.d.ts +4 -0
  225. package/dist/format/number.d.ts.map +1 -1
  226. package/dist/index.d.ts +1 -0
  227. package/dist/index.d.ts.map +1 -1
  228. package/dist/index.js +38 -37
  229. package/dist/index.js.map +1 -1
  230. package/dist/paintGradientTrack.d.ts +13 -0
  231. package/dist/paintGradientTrack.d.ts.map +1 -1
  232. package/dist/style.css +1 -1
  233. package/dist/useReorderDragList.d.ts +23 -0
  234. package/dist/useReorderDragList.d.ts.map +1 -1
  235. package/dist/useRovingTabIndex.d.ts +5 -0
  236. package/dist/useRovingTabIndex.d.ts.map +1 -1
  237. package/package.json +2 -2
  238. package/dist/chunks/ActionsBar-CAjrj_Pt.js.map +0 -1
  239. package/dist/chunks/Callout-DlirfLJl.js.map +0 -1
  240. package/dist/chunks/Checkbox-B3Z_u7k0.js.map +0 -1
  241. package/dist/chunks/ColorField-B4Hd2qqS.js.map +0 -1
  242. package/dist/chunks/ComboBox-tbmgND-F.js.map +0 -1
  243. package/dist/chunks/CurveEditor-CSKqAXMp.js.map +0 -1
  244. package/dist/chunks/Dialog-BFuakycR.js.map +0 -1
  245. package/dist/chunks/Field-DPxmJzZz.js.map +0 -1
  246. package/dist/chunks/GradientEditor-3pYXGAPg.js.map +0 -1
  247. package/dist/chunks/Input-Dg_CYXg4.js.map +0 -1
  248. package/dist/chunks/NumberField-BhxUgi43.js.map +0 -1
  249. package/dist/chunks/OptionsBar-hQN-wfd-.js.map +0 -1
  250. package/dist/chunks/PointPlotter-DCSjvpN0.js.map +0 -1
  251. package/dist/chunks/Powerline-DkOTPnUV.js.map +0 -1
  252. package/dist/chunks/Prefs-C4HfuTvm.js.map +0 -1
  253. package/dist/chunks/RadioGroup-Bg3Rqxe2.js.map +0 -1
  254. package/dist/chunks/RangeSlider-BvM_DGSr.js.map +0 -1
  255. package/dist/chunks/ResizeHandle-BUkW2kZC.js.map +0 -1
  256. package/dist/chunks/Select-DC0AjrEe.js.map +0 -1
  257. package/dist/chunks/SelectionPanel-DoTdisRY.js.map +0 -1
  258. package/dist/chunks/Sidebar-B8qig3BQ.js.map +0 -1
  259. package/dist/chunks/SidebarPanel-LOt2c-wb.js.map +0 -1
  260. package/dist/chunks/Slider-Di2kNqVy.js.map +0 -1
  261. package/dist/chunks/StatusBar-Cg2LzdAm.js.map +0 -1
  262. package/dist/chunks/Tabs-irZuG_PN.js.map +0 -1
  263. package/dist/chunks/Toast-BGYP8Pw0.js.map +0 -1
  264. package/dist/chunks/ToolButton-DUeg8fPN.js.map +0 -1
  265. package/dist/chunks/ToolGroup-D0CvyQhQ.js.map +0 -1
  266. package/dist/chunks/ToolOptionsBar-N_MQcSo4.js.map +0 -1
  267. package/dist/chunks/ToolPalette-BipoMdZz.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Slider-CmPeFJ3x.js","names":[],"sources":["../../src/components/Slider/Slider.module.css","../../src/format/number.ts","../../src/components/Slider/Slider.tsx"],"sourcesContent":[".root {\n position: relative;\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.track {\n position: relative;\n flex: 1;\n min-width: 0;\n height: var(--rp-track-height, 24px);\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: 3px;\n cursor: crosshair;\n /* No `overflow: hidden` here — thumbs at extreme values would get\n * clipped at the track edges. The gradient/track-paint clip is\n * applied to `.trackInner` instead, which lets thumbs spill freely. */\n}\n\n.trackInner {\n position: absolute;\n inset: 0;\n border-radius: 3px;\n overflow: hidden;\n}\n\n.thumb {\n position: absolute;\n /* Thumb extends 2px above + below the track for grabbability + visual\n * weight. Track itself stays its natural height; the thumb just spills. */\n top: -2px;\n bottom: -2px;\n width: 14px;\n margin-left: -7px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--wzl-fg-muted) 70%, transparent);\n border: 1px solid var(--wzl-border-strong);\n border-radius: 3px;\n cursor: ew-resize;\n /* Frosted-glass over the (often colorful) track. Cheap on modern GPUs;\n * gracefully degrades to just the partial-alpha background on engines\n * without backdrop-filter support. */\n backdrop-filter: blur(3px);\n -webkit-backdrop-filter: blur(3px);\n /* Subtle raise — sells the \"thumb is on top of the track\" hierarchy. */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n font: 500 0.65rem/1 ui-sans-serif, system-ui, sans-serif;\n font-variant-numeric: tabular-nums;\n /* Thumb text reads against `--wzl-thumb-fill`, which can differ from the\n * surrounding panel text color. Default = dark; consumers re-skin via\n * `--wzl-thumb-text` if they pick a dark thumb fill. */\n color: var(--wzl-fg-inverse);\n /* White halo so labels stay legible over wildly varying gradient\n * backgrounds (e.g. labels 'T' / 'P' / 'B' over teal, hue 200° over\n * yellow-green, etc.). */\n text-shadow: 0 0 2px rgba(255, 255, 255, 0.7);\n}\n\n.thumb:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: 1px;\n}\n\n.thumbActive {\n /* Visual marker for the dragging/focused thumb; subclass-overridable. */\n z-index: 1;\n}\n\n.readoutsBelow {\n position: relative;\n height: 14px;\n margin-top: 4px;\n}\n\n.readoutBelow {\n position: absolute;\n transform: translateX(-50%);\n font: 500 0.65rem/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color: var(--wzl-fg-muted);\n white-space: nowrap;\n}\n\n.readoutInline {\n display: inline-block;\n margin-left: 8px;\n font: 500 0.7rem/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color: var(--wzl-fg-muted);\n vertical-align: middle;\n}\n\n.notched {\n background: var(--thumb-svg, none);\n background-size: 100% 100%;\n background-repeat: no-repeat;\n background-color: transparent;\n border: none;\n /* Notched thumbs need to disable the .thumb defaults that paint to\n * the bounding box: backdrop-filter and box-shadow both ignore the\n * SVG's polygon/notch and would render through the notch cutout.\n * Use filter: drop-shadow instead — it traces the actual rendered\n * alpha (i.e. the polygon shape including the cut). */\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n box-shadow: none;\n filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25));\n /* Default notched SVG (down-pointing pentagon, matches the perceptual-color experiment). */\n --thumb-svg: url(\"data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 30' preserveAspectRatio='none'%3E%3Cpolygon points='0,0 2.52,0 7,10.3 11.48,0 14,0 14,30 0,30' fill='rgba(255,255,255,0.62)' stroke='rgba(0,0,0,0.55)' stroke-width='0.75' stroke-linejoin='miter'/%3E%3C/svg%3E\");\n}\n","/**\n * Display-formatter for numbers. Use this anywhere a number is shown to\n * a user. The whole point: negative values get prefixed with the real\n * MINUS SIGN (U+2212) instead of the ASCII HYPHEN-MINUS (U+002D) that\n * `toLocaleString` and template literals produce by default.\n *\n * U+2212 is the same visual width as `+` and reads as a sign rather\n * than a hyphen — columns of signed numbers align cleanly and the\n * glyph doesn't get confused with a bullet or list dash.\n */\nexport const MINUS_SIGN = '−';\n\n/**\n * Formats a number for display, substituting {@link MINUS_SIGN} for the ASCII\n * hyphen `toLocaleString` emits. Non-finite values stringify as-is.\n */\nexport function formatNumber(value: number, options?: Intl.NumberFormatOptions): string {\n const formatted = Number.isFinite(value)\n ? value.toLocaleString(undefined, options)\n : String(value);\n return formatted.replace(/^-/, MINUS_SIGN);\n}\n","import { useCallback, useRef, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactElement, type ReactNode } from 'react';\nimport s from './Slider.module.css';\nimport { formatNumber } from '../../format/number';\n\n/**\n * Passed to a custom thumb renderer: the thumb box in CSS px, and whether\n * this thumb is the one being dragged.\n */\nexport type ThumbRenderCtx = {\n width: number;\n height: number;\n isActive: boolean;\n};\n\n/**\n * A thumb's appearance — one of the two built-in shapes, or a custom\n * renderer.\n */\nexport type ThumbShape =\n | 'round'\n | 'notched'\n | { render: (ctx: ThumbRenderCtx) => ReactNode };\n\n/**\n * One handle on a {@link Slider}. `bounds` narrows the range this particular\n * thumb may move within, either fixed or computed from the current thumb\n * list.\n */\nexport type Thumb = {\n value: number;\n label?: string;\n shape?: ThumbShape;\n bounds?: [number, number] | ((ctx: BoundsCtx) => [number, number]);\n};\n\n/**\n * Passed to a thumb's `bounds` function: the full thumb list and this thumb's\n * index in it, so a bound can be expressed relative to its neighbors.\n */\nexport type BoundsCtx = {\n thumbs: readonly Thumb[];\n index: number;\n};\n\n/**\n * Passed to `renderTrack`: the track's width in CSS px and a mapping from a\n * slider value to its 0..1 position along the track.\n */\nexport type TrackCtx = {\n trackWidth: number;\n valueToFraction: (v: number) => number;\n};\n\n/**\n * Props for {@link Slider}.\n *\n * `onInput` fires continuously through a drag; `onChange` fires once when it\n * ends and is the one to write to history.\n *\n * `constraint: 'ordered'` keeps thumbs from crossing each other. Supplying\n * `onAddThumb` makes a click on empty track create a thumb, and supplying\n * `onRemoveThumb` lets a right-click or a drag off the track remove one —\n * both callbacks can decline by returning `null`/`false`. `allowShiftAll`\n * makes shift-drag translate every thumb together.\n */\nexport type SliderProps<T extends Thumb = Thumb> = {\n thumbs: readonly T[];\n onInput: (next: T[]) => void;\n onChange?: (next: T[]) => void;\n min: number;\n max: number;\n step?: number;\n constraint?: 'free' | 'ordered';\n onAddThumb?: (atValue: number) => T | null;\n onRemoveThumb?: (index: number) => boolean;\n allowShiftAll?: boolean;\n renderTrack?: (ctx: TrackCtx) => ReactNode;\n trackHeight?: number;\n renderReadout?: (thumb: T, index: number) => ReactNode;\n readoutPlacement?: 'none' | 'inline-after' | 'below-thumb';\n ariaLabel?: string;\n className?: string;\n};\n\nfunction snap(v: number, step: number | undefined, min: number): number {\n if (step === undefined || step <= 0) return v;\n return Math.round((v - min) / step) * step + min;\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v));\n}\n\nfunction defaultStep(step: number | undefined, min: number, max: number): number {\n if (step !== undefined && step > 0) return step;\n return (max - min) / 100;\n}\n\nfunction resolveBounds(thumb: Thumb, ctx: BoundsCtx, fallbackMin: number, fallbackMax: number): [number, number] {\n if (!thumb.bounds) return [fallbackMin, fallbackMax];\n const tuple = typeof thumb.bounds === 'function' ? thumb.bounds(ctx) : thumb.bounds;\n return [tuple[0], tuple[1]];\n}\n\nfunction defaultReadout(thumb: Thumb): string {\n return formatNumber(thumb.value, { minimumFractionDigits: 3, maximumFractionDigits: 3 });\n}\n\n/**\n * Multi-thumb slider over a shared track. The thumb list is fully controlled:\n * every change, live or committed, arrives as a whole new array.\n *\n * Thumbs are draggable, and arrow/Home/End move the focused thumb — those\n * keystrokes fire `onInput` and `onChange` together, since there is no\n * in-flight state to buffer.\n */\nexport function Slider<T extends Thumb = Thumb>(props: SliderProps<T>): ReactElement {\n const { thumbs, onInput, onChange, min, max, step, constraint, trackHeight, ariaLabel, className } = props;\n\n const trackRef = useRef<HTMLDivElement | null>(null);\n // In-flight thumb buffer during a drag; null when not dragging.\n const dragBufferRef = useRef<T[] | null>(null);\n\n const valueToFraction = useCallback(\n (v: number): number => (max === min ? 0 : clamp((v - min) / (max - min), 0, 1)),\n [min, max],\n );\n\n const fractionToValue = useCallback(\n (f: number): number => min + clamp(f, 0, 1) * (max - min),\n [min, max],\n );\n\n const beginThumbDrag = useCallback(\n (index: number) => {\n const buf: T[] = thumbs.map(t => ({ ...t }));\n dragBufferRef.current = buf;\n let droppedOff = false;\n\n const onMove = (ev: PointerEvent) => {\n const track = trackRef.current;\n const buffer = dragBufferRef.current;\n if (!track || !buffer) return;\n const rect = track.getBoundingClientRect();\n const f = clamp((ev.clientX - rect.left) / rect.width, 0, 1);\n let v = fractionToValue(f);\n v = snap(v, step, min);\n v = clamp(v, min, max);\n\n const [bLo, bHi] = resolveBounds(buffer[index], { thumbs: buffer, index }, min, max);\n v = clamp(v, bLo, bHi);\n\n if (constraint === 'ordered') {\n const gap = step !== undefined && step > 0 ? step : (max - min) / 1000;\n const lower = index > 0 ? buffer[index - 1].value + gap : min;\n const upper = index < buffer.length - 1 ? buffer[index + 1].value - gap : max;\n v = clamp(v, lower, upper);\n }\n\n // Drop-off detection: pointer exits the track vertically by more than trackHeight.\n const bandHeight = rect.height;\n if (props.onRemoveThumb) {\n if (ev.clientY < rect.top - bandHeight || ev.clientY > rect.bottom + bandHeight) {\n droppedOff = true;\n } else {\n droppedOff = false;\n }\n }\n\n buffer[index] = { ...buffer[index], value: v };\n onInput(buffer.map(t => ({ ...t })));\n };\n\n const onUp = () => {\n document.removeEventListener('pointermove', onMove);\n document.removeEventListener('pointerup', onUp);\n const buffer = dragBufferRef.current;\n dragBufferRef.current = null;\n if (!buffer) return;\n\n if (droppedOff && props.onRemoveThumb) {\n const accepted = props.onRemoveThumb(index);\n if (accepted) {\n const next = buffer.filter((_, i) => i !== index).map(t => ({ ...t })) as T[];\n onInput(next);\n onChange?.(next);\n return;\n }\n }\n\n onChange?.(buffer.map(t => ({ ...t })));\n };\n\n document.addEventListener('pointermove', onMove);\n document.addEventListener('pointerup', onUp);\n },\n [thumbs, onInput, onChange, fractionToValue, min, max, step, constraint, props],\n );\n\n const beginShiftAllDrag = useCallback(\n (anchorX: number) => {\n const buf: T[] = thumbs.map(t => ({ ...t }));\n const startValues = buf.map(t => t.value);\n dragBufferRef.current = buf;\n\n const onMove = (ev: PointerEvent) => {\n const track = trackRef.current;\n const buffer = dragBufferRef.current;\n if (!track || !buffer) return;\n const rect = track.getBoundingClientRect();\n const dxFraction = (ev.clientX - anchorX) / rect.width;\n let dValue = dxFraction * (max - min);\n dValue = snap(dValue, step, 0);\n\n // Clamp delta so no thumb leaves [min, max] (per-thumb bounds intentionally\n // not enforced — matches the experiment's hue-band shift-translate semantics).\n let allowedNeg = -Infinity;\n let allowedPos = Infinity;\n for (let i = 0; i < startValues.length; i++) {\n allowedNeg = Math.max(allowedNeg, min - startValues[i]);\n allowedPos = Math.min(allowedPos, max - startValues[i]);\n }\n dValue = clamp(dValue, allowedNeg, allowedPos);\n\n for (let i = 0; i < buffer.length; i++) {\n buffer[i] = { ...buffer[i], value: clamp(startValues[i] + dValue, min, max) };\n }\n onInput(buffer.map(t => ({ ...t })));\n };\n\n const onUp = () => {\n document.removeEventListener('pointermove', onMove);\n document.removeEventListener('pointerup', onUp);\n const buffer = dragBufferRef.current;\n dragBufferRef.current = null;\n if (buffer) onChange?.(buffer.map(t => ({ ...t })));\n };\n\n document.addEventListener('pointermove', onMove);\n document.addEventListener('pointerup', onUp);\n },\n [thumbs, onInput, onChange, min, max, step],\n );\n\n const onThumbPointerDown = (index: number) => (e: ReactPointerEvent) => {\n // Only bail on explicit non-primary buttons (button > 0). jsdom's PointerEvent\n // leaves `button` undefined; treat that as primary so tests can drive drags.\n if (typeof e.button === 'number' && e.button > 0) return;\n e.preventDefault();\n e.stopPropagation();\n if (e.shiftKey && props.allowShiftAll) {\n beginShiftAllDrag(e.clientX);\n } else {\n beginThumbDrag(index);\n }\n };\n\n const onThumbContextMenu = (index: number) => (e: ReactMouseEvent) => {\n if (!props.onRemoveThumb) return;\n e.preventDefault();\n const accepted = props.onRemoveThumb(index);\n if (!accepted) return;\n const next = thumbs.filter((_, i) => i !== index).map(t => ({ ...t })) as T[];\n onInput(next);\n onChange?.(next);\n };\n\n const onTrackPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {\n if (typeof e.button === 'number' && e.button > 0) return;\n if (!props.onAddThumb) return;\n // If the event originated on a thumb, the thumb's own handler ran first; this is a track click.\n if ((e.target as HTMLElement).closest(`.${s.thumb}`)) return;\n e.preventDefault();\n const track = trackRef.current;\n if (!track) return;\n const rect = track.getBoundingClientRect();\n const f = clamp((e.clientX - rect.left) / rect.width, 0, 1);\n let v = fractionToValue(f);\n v = snap(v, step, min);\n v = clamp(v, min, max);\n const created = props.onAddThumb(v);\n if (!created) return;\n const next = [...thumbs.map(t => ({ ...t })), created] as T[];\n onInput(next);\n onChange?.(next);\n };\n\n const onThumbKeyDown = (index: number) => (e: ReactKeyboardEvent) => {\n const stepSize = defaultStep(step, min, max);\n let delta = 0;\n let snapTo: 'home' | 'end' | null = null;\n\n switch (e.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n delta = e.shiftKey ? stepSize * 10 : stepSize;\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n delta = e.shiftKey ? -stepSize * 10 : -stepSize;\n break;\n case 'PageUp':\n delta = stepSize * 10;\n break;\n case 'PageDown':\n delta = -stepSize * 10;\n break;\n case 'Home':\n snapTo = 'home';\n break;\n case 'End':\n snapTo = 'end';\n break;\n default:\n return;\n }\n\n e.preventDefault();\n const next = thumbs.map(t => ({ ...t }));\n const [bLo, bHi] = resolveBounds(next[index], { thumbs: next, index }, min, max);\n const lo = Math.max(min, bLo);\n const hi = Math.min(max, bHi);\n let v: number;\n if (snapTo === 'home') v = lo;\n else if (snapTo === 'end') v = hi;\n else v = next[index].value + delta;\n v = snap(v, step, min);\n v = clamp(v, lo, hi);\n next[index] = { ...next[index], value: v };\n onInput(next);\n onChange?.(next);\n };\n\n const placement = props.readoutPlacement ?? 'none';\n const renderReadout = props.renderReadout;\n\n return (\n <div\n className={className ? `${s.root} ${className}` : s.root}\n style={trackHeight !== undefined ? ({ ['--rp-track-height' as string]: `${trackHeight}px` } as CSSProperties) : undefined}\n >\n <div className={s.row}>\n <div className={s.track} ref={trackRef} onPointerDown={onTrackPointerDown}>\n {props.renderTrack && (\n <div className={s.trackInner}>\n {props.renderTrack({\n trackWidth: trackRef.current?.getBoundingClientRect().width ?? 0,\n valueToFraction,\n })}\n </div>\n )}\n {thumbs.map((thumb, i) => {\n const isNotched = thumb.shape === 'notched';\n const customRender = typeof thumb.shape === 'object' && thumb.shape !== null ? thumb.shape.render : null;\n const cls = `${s.thumb}${isNotched ? ` ${s.notched}` : ''}`;\n return (\n <div\n key={i}\n role=\"slider\"\n tabIndex={0}\n aria-orientation=\"horizontal\"\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={thumb.value}\n aria-label={[ariaLabel, thumb.label].filter(Boolean).join(' ') || undefined}\n className={cls}\n style={{ left: `${valueToFraction(thumb.value) * 100}%` }}\n onPointerDown={onThumbPointerDown(i)}\n onKeyDown={onThumbKeyDown(i)}\n onContextMenu={onThumbContextMenu(i)}\n >\n {customRender ? customRender({ width: 14, height: 24, isActive: false }) : (thumb.label ?? '')}\n </div>\n );\n })}\n </div>\n {placement === 'inline-after' && (\n <span data-readout=\"inline\" className={s.readoutInline}>\n {thumbs.map((t, i) => (\n <span key={i}>{i > 0 ? ' / ' : ''}{renderReadout ? renderReadout(t, i) : defaultReadout(t)}</span>\n ))}\n </span>\n )}\n </div>\n {placement === 'below-thumb' && (\n <div className={s.readoutsBelow}>\n {thumbs.map((t, i) => (\n <span\n key={i}\n data-readout=\"below\"\n className={s.readoutBelow}\n style={{ left: `${valueToFraction(t.value) * 100}%` }}\n >\n {renderReadout ? renderReadout(t, i) : defaultReadout(t)}\n </span>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -38,4 +38,4 @@ function i() {
38
38
  //#endregion
39
39
  export { r as n, i as r, n as t };
40
40
 
41
- //# sourceMappingURL=StatusBar-Cg2LzdAm.js.map
41
+ //# sourceMappingURL=StatusBar-DBO0E0Yd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusBar-DBO0E0Yd.js","names":[],"sources":["../../src/components/StatusBar/StatusBar.module.css","../../src/components/StatusBar/StatusBar.tsx"],"sourcesContent":[".bar {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n padding: 4px 12px;\n font-family: var(--wzl-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 11px;\n color: var(--wzl-fg-muted);\n background: var(--wzl-surface);\n border-top: 1px solid var(--wzl-border);\n}\n\n.item {\n white-space: nowrap;\n}\n\n.muted {\n opacity: 0.6;\n font-variant-numeric: tabular-nums;\n}\n\n.hasTitle {\n cursor: help;\n}\n\n.spacer {\n margin-left: auto;\n}\n","import type { ReactNode } from 'react';\nimport s from './StatusBar.module.css';\n\n/** Props for {@link StatusBar}. */\nexport interface StatusBarProps {\n /**\n * Accessible label for the bar. Deliberately not a live region — a status\n * bar's readouts change on every pointer move (zoom, cursor position,\n * selection count), and announcing each one would make a screen reader\n * unusable. Screen-reader users reach the same facts through the controls\n * that own them.\n */\n ariaLabel?: string;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * The thin readout strip along the bottom of an editor window: monospace,\n * one row, items separated by a fixed gap, with an optional spacer that\n * pushes everything after it to the trailing edge.\n *\n * Pure presentation — it holds whatever the app puts in it. Compose with\n * `<StatusBarItem>` and `<StatusBarSpacer>`.\n */\nexport function StatusBar(props: StatusBarProps) {\n const { ariaLabel, children, className } = props;\n return (\n <footer className={[s.bar, className].filter(Boolean).join(' ')} aria-label={ariaLabel}>\n {children}\n </footer>\n );\n}\n\n/** Props for {@link StatusBarItem}. */\nexport interface StatusBarItemProps {\n /**\n * Hover text. Items that carry one also get a `help` cursor, so the\n * affordance is discoverable rather than hidden behind a hover delay.\n */\n title?: string;\n /**\n * Dim the item and use tabular figures. For reference material that\n * shouldn't compete with live readouts — build stamps, hints, units.\n */\n muted?: boolean;\n children?: ReactNode;\n className?: string;\n}\n\n/** One readout in a `<StatusBar>`. */\nexport function StatusBarItem(props: StatusBarItemProps) {\n const { title, muted, children, className } = props;\n const cls = [s.item, muted && s.muted, title !== undefined && s.hasTitle, className]\n .filter(Boolean).join(' ');\n return <span className={cls} title={title}>{children}</span>;\n}\n\n/**\n * Splits a status bar into leading and trailing runs — everything after the\n * spacer is pushed to the trailing edge. Decorative, so it's hidden from the\n * accessibility tree.\n */\nexport function StatusBarSpacer() {\n return <span className={s.spacer} aria-hidden=\"true\" />;\n}\n"],"mappings":";;;;;;;;;;ACyBA,SAAgB,EAAU,GAAuB;CAC/C,IAAM,EAAE,cAAW,aAAU,iBAAc;AAC3C,QACE,kBAAC,UAAD;EAAQ,WAAW,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EAAE,cAAY;EAC1E;EACM,CAAA;;AAqBb,SAAgB,EAAc,GAA2B;CACvD,IAAM,EAAE,UAAO,UAAO,aAAU,iBAAc;AAG9C,QAAO,kBAAC,QAAD;EAAM,WAFD;GAAC,EAAE;GAAM,KAAS,EAAE;GAAO,MAAU,KAAA,KAAa,EAAE;GAAU;GAAU,CACjF,OAAO,QAAQ,CAAC,KAAK,IACA;EAAY;EAAQ;EAAgB,CAAA;;AAQ9D,SAAgB,IAAkB;AAChC,QAAO,kBAAC,QAAD;EAAM,WAAW,EAAE;EAAQ,eAAY;EAAS,CAAA"}
@@ -24,4 +24,4 @@ var i = {
24
24
  //#endregion
25
25
  export { a as t };
26
26
 
27
- //# sourceMappingURL=Switch-I4putUAY.js.map
27
+ //# sourceMappingURL=Switch-JyVSSAzC.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Switch-I4putUAY.js","names":[],"sources":["../../src/components/Switch/Switch.module.css","../../src/components/Switch/Switch.tsx"],"sourcesContent":[".switch {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n font-family: var(--wzl-font-ui);\n font-size: 13px;\n color: var(--wzl-fg);\n user-select: none;\n -webkit-user-select: none;\n}\n\n.switch[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.track {\n position: relative;\n flex: 0 0 auto;\n width: 28px;\n height: 16px;\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border-strong);\n border-radius: 8px;\n transition: background-color var(--wzl-motion-fast), border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.switch[data-focus-visible] .track {\n box-shadow: 0 0 0 2px var(--wzl-focus-ring);\n}\n\n.switch[data-selected] .track {\n background: var(--wzl-accent-strong);\n border-color: var(--wzl-accent-strong);\n}\n\n.thumb {\n position: absolute;\n top: 1px;\n left: 1px;\n width: 12px;\n height: 12px;\n background: var(--wzl-fg);\n border-radius: 50%;\n transition: transform var(--wzl-motion-fast);\n}\n\n.switch[data-selected] .thumb {\n transform: translateX(12px);\n background: var(--wzl-fg-on-accent);\n}\n\n.label {\n line-height: 1.2;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n Switch as RACSwitch,\n type SwitchProps as RACSwitchProps,\n} from 'react-aria-components';\nimport s from './Switch.module.css';\n\nexport type SwitchProps = Omit<RACSwitchProps, 'children' | 'className'> & {\n children?: ReactNode;\n className?: string;\n};\n\nexport const Switch = forwardRef(function Switch(\n props: SwitchProps,\n ref: Ref<HTMLLabelElement>,\n) {\n const { children, className, ...rest } = props;\n return (\n <RACSwitch\n {...rest}\n ref={ref}\n className={[s.switch, className].filter(Boolean).join(' ')}\n >\n <span className={s.track}>\n <span className={s.thumb} />\n </span>\n {children !== undefined && <span className={s.label}>{children}</span>}\n </RACSwitch>\n );\n});\n"],"mappings":";;;;;;;;GCYa,IAAS,EAAW,SAC/B,GACA,GACA;CACA,IAAM,EAAE,aAAU,cAAW,GAAG,MAAS;AACzC,QACE,kBAAC,GAAD;EACE,GAAI;EACC;EACL,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAH5D,CAKE,kBAAC,QAAD;GAAM,WAAW,EAAE;aACjB,kBAAC,QAAD,EAAM,WAAW,EAAE,OAAS,CAAA;GACvB,CAAA,EACN,MAAa,KAAA,KAAa,kBAAC,QAAD;GAAM,WAAW,EAAE;GAAQ;GAAgB,CAAA,CAC5D;;EAEd"}
1
+ {"version":3,"file":"Switch-JyVSSAzC.js","names":[],"sources":["../../src/components/Switch/Switch.module.css","../../src/components/Switch/Switch.tsx"],"sourcesContent":[".switch {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n font-family: var(--wzl-font-ui);\n font-size: 13px;\n color: var(--wzl-fg);\n user-select: none;\n -webkit-user-select: none;\n}\n\n.switch[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.track {\n position: relative;\n flex: 0 0 auto;\n width: 28px;\n height: 16px;\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border-strong);\n border-radius: 8px;\n transition: background-color var(--wzl-motion-fast), border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.switch[data-focus-visible] .track {\n box-shadow: 0 0 0 2px var(--wzl-focus-ring);\n}\n\n.switch[data-selected] .track {\n background: var(--wzl-accent-strong);\n border-color: var(--wzl-accent-strong);\n}\n\n.thumb {\n position: absolute;\n top: 1px;\n left: 1px;\n width: 12px;\n height: 12px;\n background: var(--wzl-fg);\n border-radius: 50%;\n transition: transform var(--wzl-motion-fast);\n}\n\n.switch[data-selected] .thumb {\n transform: translateX(12px);\n background: var(--wzl-fg-on-accent);\n}\n\n.label {\n line-height: 1.2;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n Switch as RACSwitch,\n type SwitchProps as RACSwitchProps,\n} from 'react-aria-components';\nimport s from './Switch.module.css';\n\n/**\n * Props for {@link Switch}, on top of React Aria's `Switch` props. The label\n * is passed as children.\n */\nexport type SwitchProps = Omit<RACSwitchProps, 'children' | 'className'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * On/off toggle wrapping React Aria's Switch, skinned against the `--wzl-*`\n * tokens. Use for a setting that takes effect immediately; use `Checkbox`\n * for one that is submitted with a form.\n *\n * `ref` forwards to the underlying label element.\n */\nexport const Switch = forwardRef(function Switch(\n props: SwitchProps,\n ref: Ref<HTMLLabelElement>,\n) {\n const { children, className, ...rest } = props;\n return (\n <RACSwitch\n {...rest}\n ref={ref}\n className={[s.switch, className].filter(Boolean).join(' ')}\n >\n <span className={s.track}>\n <span className={s.thumb} />\n </span>\n {children !== undefined && <span className={s.label}>{children}</span>}\n </RACSwitch>\n );\n});\n"],"mappings":";;;;;;;;GCuBa,IAAS,EAAW,SAC/B,GACA,GACA;CACA,IAAM,EAAE,aAAU,cAAW,GAAG,MAAS;AACzC,QACE,kBAAC,GAAD;EACE,GAAI;EACC;EACL,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAH5D,CAKE,kBAAC,QAAD;GAAM,WAAW,EAAE;aACjB,kBAAC,QAAD,EAAM,WAAW,EAAE,OAAS,CAAA;GACvB,CAAA,EACN,MAAa,KAAA,KAAa,kBAAC,QAAD;GAAM,WAAW,EAAE;GAAQ;GAAgB,CAAA,CAC5D;;EAEd"}
@@ -38,4 +38,4 @@ function l({ children: e, className: t, ...r }) {
38
38
  //#endregion
39
39
  export { o as i, s as n, l as r, c as t };
40
40
 
41
- //# sourceMappingURL=Tabs-irZuG_PN.js.map
41
+ //# sourceMappingURL=Tabs-Df0sDomx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tabs-Df0sDomx.js","names":[],"sources":["../../src/components/Tabs/Tabs.module.css","../../src/components/Tabs/Tabs.tsx"],"sourcesContent":[".tabs {\n display: flex;\n flex-direction: column;\n gap: 0;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.tabs[data-orientation=\"horizontal\"] {\n flex-direction: column;\n}\n\n.tabs[data-orientation=\"vertical\"] {\n flex-direction: row;\n gap: 12px;\n}\n\n.list {\n display: flex;\n gap: 2px;\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.tabs[data-orientation=\"vertical\"] .list {\n flex-direction: column;\n border-bottom: none;\n border-right: 1px solid var(--wzl-border);\n padding-right: 8px;\n}\n\n.tab {\n position: relative;\n padding: 6px 10px;\n background: transparent;\n border: none;\n color: var(--wzl-fg-muted);\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n outline: none;\n transition: color var(--wzl-motion-fast);\n}\n\n.tab[data-hovered]:not([data-disabled]) {\n color: var(--wzl-fg);\n}\n\n.tab[data-selected] {\n color: var(--wzl-fg);\n}\n\n.tab[data-selected]::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n bottom: -1px;\n height: 2px;\n background: var(--wzl-accent-strong);\n}\n\n.tabs[data-orientation=\"vertical\"] .tab[data-selected]::after {\n left: auto;\n right: -9px;\n top: 0;\n bottom: 0;\n width: 2px;\n height: auto;\n}\n\n.tab[data-focus-visible] {\n box-shadow: inset 0 0 0 2px var(--wzl-focus-ring);\n border-radius: var(--wzl-radius-sm);\n}\n\n.tab[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.panel {\n padding: 12px 0;\n outline: none;\n}\n\n.panel[data-focus-visible] {\n box-shadow: inset 0 0 0 2px var(--wzl-focus-ring);\n border-radius: var(--wzl-radius-sm);\n}\n","import type { ReactNode } from 'react';\nimport {\n Tabs as RACTabs,\n TabList as RACTabList,\n Tab as RACTab,\n TabPanel as RACTabPanel,\n type TabsProps as RACTabsProps,\n type TabListProps as RACTabListProps,\n type TabProps as RACTabProps,\n type TabPanelProps as RACTabPanelProps,\n} from 'react-aria-components';\nimport s from './Tabs.module.css';\n\n/** Props for {@link Tabs}, on top of React Aria's `Tabs` props. */\nexport type TabsProps = Omit<RACTabsProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * Tab set wrapping React Aria's Tabs, skinned against the `--wzl-*` tokens.\n * Holds a {@link TabList} and one {@link TabPanel} per tab.\n */\nexport function Tabs({ children, className, ...rest }: TabsProps) {\n return (\n <RACTabs {...rest} className={[s.tabs, className].filter(Boolean).join(' ')}>\n {children}\n </RACTabs>\n );\n}\n\n/** Props for {@link TabList}, on top of React Aria's `TabList` props. */\nexport type TabListProps<T extends object> = Omit<RACTabListProps<T>, 'className'> & {\n className?: string;\n};\n\n/** The row of tab buttons inside a {@link Tabs}. */\nexport function TabList<T extends object>({ className, ...rest }: TabListProps<T>) {\n return <RACTabList<T> {...rest} className={[s.list, className].filter(Boolean).join(' ')} />;\n}\n\n/** Props for {@link Tab}, on top of React Aria's `Tab` props. */\nexport type TabProps = Omit<RACTabProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/** One tab button. Its `id` selects the {@link TabPanel} it reveals. */\nexport function Tab({ children, className, ...rest }: TabProps) {\n return (\n <RACTab {...rest} className={[s.tab, className].filter(Boolean).join(' ')}>\n {children}\n </RACTab>\n );\n}\n\n/** Props for {@link TabPanel}, on top of React Aria's `TabPanel` props. */\nexport type TabPanelProps = Omit<RACTabPanelProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/** The content shown for the {@link Tab} whose `id` matches this panel's. */\nexport function TabPanel({ children, className, ...rest }: TabPanelProps) {\n return (\n <RACTabPanel {...rest} className={[s.panel, className].filter(Boolean).join(' ')}>\n {children}\n </RACTabPanel>\n );\n}\n"],"mappings":";;;;;;;;;;ACuBA,SAAgB,EAAK,EAAE,aAAU,cAAW,GAAG,KAAmB;AAChE,QACE,kBAAC,GAAD;EAAS,GAAI;EAAM,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EACxE;EACO,CAAA;;AAUd,SAAgB,EAA0B,EAAE,cAAW,GAAG,KAAyB;AACjF,QAAO,kBAAC,GAAD;EAAe,GAAI;EAAM,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EAAI,CAAA;;AAU9F,SAAgB,EAAI,EAAE,aAAU,cAAW,GAAG,KAAkB;AAC9D,QACE,kBAAC,GAAD;EAAQ,GAAI;EAAM,WAAW,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EACtE;EACM,CAAA;;AAWb,SAAgB,EAAS,EAAE,aAAU,cAAW,GAAG,KAAuB;AACxE,QACE,kBAAC,GAAD;EAAa,GAAI;EAAM,WAAW,CAAC,EAAE,OAAO,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EAC7E;EACW,CAAA"}
@@ -125,4 +125,4 @@ function S(i) {
125
125
  //#endregion
126
126
  export { v as a, g as i, p as n, m as r, S as t };
127
127
 
128
- //# sourceMappingURL=Toast-BGYP8Pw0.js.map
128
+ //# sourceMappingURL=Toast-DFw4IrD_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toast-DFw4IrD_.js","names":[],"sources":["../../src/components/Toast/queue.ts","../../src/components/Toast/Toast.module.css","../../src/components/Toast/Toast.tsx"],"sourcesContent":["import { UNSTABLE_ToastQueue } from 'react-aria-components';\nimport { flushSync } from 'react-dom';\n\n/** Severity of a toast, driving its accent color and icon. */\nexport type ToastTone = 'info' | 'success' | 'warning' | 'error';\n\n/** Kit-owned payload rendered inside each toast. */\nexport interface ToastContent {\n title: string;\n description?: string;\n tone: ToastTone;\n}\n\n/** Options for a single toast. */\nexport interface ToastOptions {\n description?: string;\n /**\n * Auto-dismiss delay in ms. Default 8000. `null` — sticky until dismissed.\n * Must be > 0; RAC treats 0 as no timer (sticky).\n */\n ttlMs?: number | null;\n /** Stable identity: re-adding with the same id replaces the earlier toast. */\n id?: string;\n}\n\nconst DEFAULT_TTL_MS = 8000;\n\n// Tracks overlapping toast-triggered view transitions so the `:root`\n// marker class survives until the *last* one settles.\nlet activeToastTransitions = 0;\n\n/**\n * RAC's sanctioned toast-animation hook: wrap every queue mutation in a\n * view transition so entry, exit, and the surviving stack's reflow all\n * animate (choreography lives in `toastViewTransitions.css`). Falls back\n * to a plain synchronous update when the browser lacks the API or the\n * user prefers reduced motion — i.e. exactly the pre-animation behavior.\n * The jsdom test environment always takes the fallback path.\n */\nfunction viewTransitionWrapUpdate(fn: () => void): void {\n const reduceMotion =\n typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (typeof document === 'undefined' || !('startViewTransition' in document) || reduceMotion) {\n fn();\n return;\n }\n // The marker class scopes the CSS that un-names `:root` (keeps the rest\n // of the page live and interactive during the transition) to\n // toast-owned transitions only.\n const root = document.documentElement;\n activeToastTransitions++;\n root.classList.add('wzl-toast-vt');\n // flushSync: the DOM must reflect the queue change inside the\n // transition callback, before the browser snapshots the new state.\n const vt = document.startViewTransition(() => flushSync(fn));\n // `ready` rejects when the browser skips the transition — routine under\n // a rapid burst of toasts, where each new transition supersedes the\n // in-flight one. The DOM update has still applied by then, but an\n // untouched `ready` surfaces the rejection as a console error, so\n // consume it.\n vt.ready.catch(() => {});\n void vt.finished.finally(() => {\n if (--activeToastTransitions === 0) root.classList.remove('wzl-toast-vt');\n });\n}\n\n// The wrapped RAC queue lives in a module-scoped WeakMap so the public\n// class surface carries no RAC (UNSTABLE_) types — see the design spec's\n// containment rule. `racQueueOf` below is the folder-internal accessor.\nconst RAC_QUEUES = new WeakMap<ToastQueue, UNSTABLE_ToastQueue<ToastContent>>();\n\n/**\n * Kit-owned toast queue. Create isolated instances with\n * `createToastQueue()` (tests, secondary roots); most apps use the\n * module-level `defaultToastQueue` via `toast()`.\n *\n * At most 5 toasts are visible at once; adding a 6th shows immediately\n * and hides the oldest (it returns when a slot frees, with its ttl\n * restarted).\n */\nexport class ToastQueue {\n private keysById = new Map<string, string>();\n\n constructor() {\n RAC_QUEUES.set(\n this,\n new UNSTABLE_ToastQueue<ToastContent>({\n maxVisibleToasts: 5,\n wrapUpdate: viewTransitionWrapUpdate,\n }),\n );\n }\n\n add(tone: ToastTone, title: string, options: ToastOptions = {}): void {\n const { description, ttlMs = DEFAULT_TTL_MS, id } = options;\n const rac = RAC_QUEUES.get(this)!;\n if (id !== undefined) {\n const existing = this.keysById.get(id);\n if (existing !== undefined) rac.close(existing);\n }\n let key: string;\n const racOptions: { timeout?: number; onClose?: () => void } =\n ttlMs === null ? {} : { timeout: ttlMs };\n if (id !== undefined) {\n racOptions.onClose = () => {\n if (this.keysById.get(id) === key) this.keysById.delete(id);\n };\n }\n key = rac.add({ title, description, tone }, racOptions);\n if (id !== undefined) this.keysById.set(id, key);\n }\n\n /** Dismiss the toast previously added with this `id`. No-op if none is live. */\n dismiss(id: string): void {\n const key = this.keysById.get(id);\n if (key !== undefined) RAC_QUEUES.get(this)!.close(key);\n }\n\n /** Dismiss every queued toast. */\n clear(): void {\n RAC_QUEUES.get(this)!.clear();\n this.keysById.clear();\n }\n}\n\n/**\n * Creates an independent toast queue. Most apps use the module-level\n * `defaultToastQueue` that `toast()` writes to; make your own when a surface\n * needs its own stack, and pass it to `ToastRegion`.\n */\nexport function createToastQueue(): ToastQueue {\n return new ToastQueue();\n}\n\n/**\n * Folder-internal bridge to the wrapped RAC queue for `ToastRegion`.\n * Deliberately NOT exported from the Toast folder's `index.ts` — the\n * RAC toast surface is unstable and must not leak past this folder.\n */\nexport function racQueueOf(queue: ToastQueue): UNSTABLE_ToastQueue<ToastContent> {\n return RAC_QUEUES.get(queue)!;\n}\n\n/** Module-level default queue used by `toast()` and `<ToastRegion>`. */\nexport const defaultToastQueue = createToastQueue();\n\ntype ToastFn = {\n (title: string, options?: ToastOptions): void;\n info(title: string, options?: ToastOptions): void;\n success(title: string, options?: ToastOptions): void;\n warning(title: string, options?: ToastOptions): void;\n error(title: string, options?: ToastOptions): void;\n};\n\nfunction tonedAdd(tone: ToastTone) {\n return (title: string, options?: ToastOptions) => defaultToastQueue.add(tone, title, options);\n}\n\n/** Imperative convenience over `defaultToastQueue`. Bare `toast(...)` is `info`. */\nexport const toast: ToastFn = Object.assign(tonedAdd('info'), {\n info: tonedAdd('info'),\n success: tonedAdd('success'),\n warning: tonedAdd('warning'),\n error: tonedAdd('error'),\n});\n",".region {\n position: fixed;\n z-index: 1100;\n display: flex;\n /* RAC unshifts new toasts to the front of the DOM (newest-first), and the\n * ol/li wrappers are display:contents, so the flex direction alone\n * decides which end of the stack the newest toast lands on. Base\n * (bottom corners): column-reverse puts DOM-first (newest) at the\n * bottom, hugging the corner. Top corners override back to column so\n * DOM-first still hugs the top edge. */\n flex-direction: column-reverse;\n gap: 8px;\n max-width: 360px;\n outline: none;\n}\n\n.bottomRight { bottom: 16px; right: 16px; }\n.bottomLeft { bottom: 16px; left: 16px; }\n.topRight { top: 16px; right: 16px; flex-direction: column; }\n.topLeft { top: 16px; left: 16px; flex-direction: column; }\n\n.toast {\n --toast-accent: var(--wzl-accent-strong);\n /* Groups every toast under one ident so toastViewTransitions.css can\n * target `::view-transition-new/old(.wzl-toast)` without knowing the\n * per-toast view-transition-name (set inline in Toast.tsx). */\n view-transition-class: wzl-toast;\n display: flex;\n align-items: flex-start;\n gap: 8px;\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-left: 3px solid var(--toast-accent);\n border-radius: var(--wzl-radius-md);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n padding: 10px 12px;\n outline: none;\n}\n\n.region[data-focus-visible],\n.toast[data-focus-visible] {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 1px;\n}\n\n.toneInfo { /* default accent — uses --toast-accent fallback */ }\n.toneSuccess { --toast-accent: var(--wzl-success, #2ec27e); }\n.toneWarning { --toast-accent: var(--wzl-warning, #d99a3f); }\n.toneError { --toast-accent: var(--wzl-danger, #d94a3f); }\n\n.content {\n flex: 1;\n min-width: 0;\n}\n\n.title {\n display: block;\n font-size: 13px;\n font-weight: 600;\n}\n\n.description {\n display: block;\n margin-top: 2px;\n font-size: 12px;\n color: var(--wzl-fg-muted);\n}\n\n.close {\n flex: 0 0 auto;\n background: none;\n border: none;\n color: var(--wzl-fg-muted);\n font-size: 16px;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n border-radius: var(--wzl-radius-sm);\n}\n\n.close:hover {\n color: var(--wzl-fg);\n}\n","import {\n UNSTABLE_ToastRegion as RACToastRegion,\n UNSTABLE_Toast as RACToast,\n UNSTABLE_ToastContent as RACToastContent,\n Text,\n Button,\n} from 'react-aria-components';\nimport { defaultToastQueue, racQueueOf, type ToastQueue, type ToastTone } from './queue';\nimport s from './Toast.module.css';\nimport './toastViewTransitions.css';\n\n/** Screen corner a toast stack anchors to. */\nexport type ToastPlacement = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n\n/** Props for {@link ToastRegion}. */\nexport interface ToastRegionProps {\n /** Queue to render. Defaults to the module-level `defaultToastQueue`. */\n queue?: ToastQueue;\n /** Screen corner for the stack. Default `bottom-right`. */\n placement?: ToastPlacement;\n className?: string;\n}\n\nconst placementClass: Record<ToastPlacement, string> = {\n 'bottom-right': s.bottomRight,\n 'bottom-left': s.bottomLeft,\n 'top-right': s.topRight,\n 'top-left': s.topLeft,\n};\n\nconst toneClass: Record<ToastTone, string> = {\n info: s.toneInfo,\n success: s.toneSuccess,\n warning: s.toneWarning,\n error: s.toneError,\n};\n\n/**\n * Renders the toast stack for a queue. Mount once, near the app root.\n * From the underlying React Aria region: landmark semantics (keyboard /\n * F6 reachable), screen-reader announcements, and hover pausing the\n * auto-dismiss timers.\n */\nexport function ToastRegion(props: ToastRegionProps) {\n const { queue = defaultToastQueue, placement = 'bottom-right', className } = props;\n return (\n <RACToastRegion\n queue={racQueueOf(queue)}\n aria-label=\"Notifications\"\n className={[s.region, placementClass[placement], className].filter(Boolean).join(' ')}\n >\n {({ toast: t }) => (\n <RACToast\n toast={t}\n className={[s.toast, toneClass[t.content.tone]].filter(Boolean).join(' ')}\n // Inline style is the one sanctioned exception here: each toast\n // needs a *unique* view-transition-name (so enter/exit/reflow\n // track per-toast), and a class can't mint unique idents. The\n // prefix keeps the ident valid (RAC keys can start with digits).\n style={{ viewTransitionName: `wzl-toast-${t.key.replace(/[^a-zA-Z0-9_-]/g, '_')}` }}\n >\n <RACToastContent className={s.content}>\n <Text slot=\"title\" className={s.title}>{t.content.title}</Text>\n {t.content.description !== undefined && (\n <Text slot=\"description\" className={s.description}>{t.content.description}</Text>\n )}\n </RACToastContent>\n <Button slot=\"close\" className={s.close} aria-label=\"Dismiss notification\">\n ×\n </Button>\n </RACToast>\n )}\n </RACToastRegion>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ToggleBar-BP1zFcvg.js","names":[],"sources":["../../src/components/ToggleBar/ToggleBar.module.css","../../src/components/ToggleBar/ToggleBar.tsx"],"sourcesContent":["/* Aqua-glass segmented control. The track is a pill-shaped well; the\n selected segment becomes a tinted glass cylinder that reads as the same\n \"lozenge\" family as the standalone Button. Visual params come from the\n `--btn-*` custom properties (see Button.module.css) so the Button Lab\n can tune both surfaces with one set of sliders. */\n.root {\n display: inline-flex;\n height: var(--wzl-tb-height, 28px);\n padding: 2px;\n background: linear-gradient(\n 180deg,\n rgba(0, 0, 0, 0.18) 0%,\n rgba(0, 0, 0, 0.05) 100%\n );\n border-radius: 999px;\n /* Clip segments to the track outline so interior corners read crisp and\n the selected segment's glass treatment doesn't paint over the rounded\n outer pixels. */\n overflow: hidden;\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.4),\n 0 1px 0 rgba(255, 255, 255, 0.06);\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n}\n\n.segment {\n position: relative;\n flex: 1 1 0;\n min-width: 0;\n padding: 0 12px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n margin: 0;\n font-family: var(--wzl-font-ui);\n font-weight: 300;\n font-size: var(--btn-fontsize, 14px);\n line-height: 1;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n /* Only round the outer corners of the bar — middle segments stay square\n so the seams between them don't render as facing parentheses. */\n border-radius: 0;\n isolation: isolate;\n overflow: hidden;\n transition: color var(--wzl-motion-fast);\n}\n\n.segment:first-child {\n border-radius: 999px 0 0 999px;\n}\n\n.segment:last-child {\n border-radius: 0 999px 999px 0;\n}\n\n.segment:only-child {\n border-radius: 999px;\n}\n\n.segment:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -2px;\n}\n\n.segment:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.segment:hover:not(:disabled):not(.segmentSelected) {\n color: var(--wzl-fg);\n}\n\n/* Selected segment — full Aqua glass cylinder. Mirrors Button.variant_primary. */\n.segmentSelected {\n background: linear-gradient(\n 180deg,\n color-mix(in srgb, var(--wzl-accent) var(--btn-top-alpha, 45%), transparent) 0%,\n color-mix(in srgb, var(--wzl-accent) var(--btn-upper-alpha, 70%), transparent) calc(var(--btn-equator, 52%) - var(--btn-equator-spread, 12%)),\n color-mix(in srgb, var(--wzl-accent) var(--btn-equator-tint, 95%), white) var(--btn-equator, 52%),\n var(--wzl-accent) calc(var(--btn-equator, 52%) + var(--btn-equator-spread, 12%) * 1.6),\n color-mix(in srgb, var(--wzl-accent) var(--btn-base-tint, 70%), black) 100%\n );\n backdrop-filter: blur(var(--btn-blur, 6px)) saturate(var(--btn-saturate, 1.3));\n -webkit-backdrop-filter: blur(var(--btn-blur, 6px)) saturate(var(--btn-saturate, 1.3));\n color: var(--wzl-fg-on-accent);\n text-shadow:\n 0 1px 0 rgba(0, 0, 0, var(--btn-text-shadow, 0.35)),\n var(--btn-text-inset-x, 0px) var(--btn-text-inset-y, 1px) 0 rgba(0, 0, 0, var(--btn-text-inset, 0.55)),\n 0 0 6px rgba(0, 0, 0, calc(var(--btn-text-shadow, 0.35) * 0.5));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, var(--btn-inset-hi, 0.42)),\n inset 0 -1px 2px rgba(0, 0, 0, var(--btn-inset-lo, 0.3)),\n 0 0 0 1px rgba(0, 0, 0, var(--btn-rim, 0.22)),\n 0 1px 2px rgba(0, 0, 0, var(--btn-drop-near, 0.3)),\n 0 2px 4px rgba(0, 0, 0, calc(var(--btn-drop-far, 0.18) * 0.8));\n}\n\n/* Specular gloss on the selected segment. Top corners inherit the\n segment's radius so they hug the upper edge cleanly; bottom corners\n are flattened so the gloss ends in a straight line at the equator\n instead of two little curves that read as `)(` brackets. */\n.segmentSelected::before {\n content: '';\n position: absolute;\n inset: 1px 1px var(--btn-gloss-bottom, 50%) 1px;\n border-radius: inherit;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background: linear-gradient(\n 180deg,\n rgba(255, 255, 255, var(--btn-gloss-top, 0.42)) 0%,\n rgba(255, 255, 255, var(--btn-gloss-mid, 0.12)) 60%,\n rgba(255, 255, 255, 0) 100%\n );\n pointer-events: none;\n z-index: 1;\n}\n\n.segmentSelected > * {\n position: relative;\n z-index: 2;\n}\n\n/* Mixed segment (`aria-pressed=\"mixed\"`) — the sources disagree. Reads as a\n deliberate third state rather than a dimmed \"on\": a flat accent wash with\n none of the selected segment's gloss/cylinder treatment, so it can't be\n mistaken for the glass lozenge at a glance. */\n.segmentMixed {\n background: color-mix(in srgb, var(--wzl-accent) 22%, transparent);\n color: var(--wzl-fg);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wzl-accent) 45%, transparent);\n}\n\n.segmentMixed:hover:not(:disabled) {\n background: color-mix(in srgb, var(--wzl-accent) 32%, transparent);\n}\n\n.variant_minimal .segmentMixed {\n background: color-mix(in srgb, var(--wzl-accent) 18%, transparent);\n box-shadow: none;\n color: var(--wzl-fg);\n}\n\n/* Small variant — ~60% of the default height, with proportionally reduced\n padding and font. Sized for dense surfaces (lab control panels, inline\n filters, etc.). */\n.size_sm {\n height: 17px;\n padding: 1px;\n}\n\n.size_sm .segment {\n padding: 0 8px;\n font-size: 11px;\n}\n\n/* Minimal variant — strips the pill track, glass selected state, inset\n shadows, and outer padding. Used in dense diagnostic surfaces (dispatch\n trace, lab inspectors) where the bar must read as plain text controls. */\n.variant_minimal {\n height: auto;\n padding: 0;\n background: none;\n border: 1px solid var(--wzl-border, rgba(255, 255, 255, 0.18));\n border-radius: 3px;\n box-shadow: none;\n gap: 0;\n}\n\n.variant_minimal .segment {\n padding: 0 6px;\n font-size: 11px;\n font-weight: 400;\n line-height: 1.4;\n border-radius: 0;\n color: var(--wzl-fg-muted);\n transition: none;\n}\n\n/* Dividers between segments make the mutual-exclusion grouping visible\n without a heavy track. Only the inner edges get a rule — the outer\n border on `.variant_minimal` handles the perimeter. */\n.variant_minimal .segment + .segment {\n border-left: 1px solid var(--wzl-border, rgba(255, 255, 255, 0.18));\n}\n\n.variant_minimal .segment:first-child,\n.variant_minimal .segment:last-child,\n.variant_minimal .segment:only-child {\n border-radius: 0;\n}\n\n.variant_minimal .segment:hover:not(:disabled):not(.segmentSelected) {\n color: var(--wzl-fg);\n background: rgba(255, 255, 255, 0.06);\n}\n\n.variant_minimal .segmentSelected {\n background: none;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n color: var(--wzl-accent-fg, var(--wzl-accent));\n text-shadow: none;\n box-shadow: none;\n font-weight: 500;\n}\n\n.variant_minimal .segmentSelected::before {\n content: none;\n}\n","import { type CSSProperties, type ReactElement, type ReactNode } from 'react';\nimport s from './ToggleBar.module.css';\nimport { useRovingTabIndex } from '../../useRovingTabIndex';\n\nexport type ToggleBarItem<V extends string | number = string> = {\n value: V;\n label?: ReactNode;\n ariaLabel?: string;\n disabled?: boolean;\n};\n\nexport type ToggleBarSize = 'sm' | 'md';\nexport type ToggleBarVariant = 'default' | 'minimal';\n\ntype CommonProps = {\n ariaLabel?: string;\n className?: string;\n height?: number;\n /** Size variant. `sm` is ~60% of the default height with reduced padding\n * and font size — sized for dense surfaces like lab control panels. */\n size?: ToggleBarSize;\n /** Visual variant. `minimal` strips the pill track and glass treatment;\n * selection becomes a flat accent. For dense diagnostic surfaces. */\n variant?: ToggleBarVariant;\n};\n\nexport type ToggleBarProps<V extends string | number = string> =\n | (CommonProps & {\n mode?: 'single';\n items: readonly ToggleBarItem<V>[];\n value: V | null;\n onChange: (next: V | null) => void;\n allowDeselect?: boolean;\n })\n | (CommonProps & {\n mode: 'multiple';\n items: readonly ToggleBarItem<V>[];\n value: readonly V[];\n /**\n * Values that are neither on nor off — the sources this bar\n * aggregates disagree (a text range that is bold in part of it, a\n * multi-selection whose nodes differ). Rendered `aria-pressed=\"mixed\"`,\n * the ARIA tri-state a toggle button actually has, rather than\n * `SelectionPanel`'s reduced-opacity-plus-`title` workaround for\n * `Switch`, which has no indeterminate state to render.\n *\n * `value` wins where the two lists overlap, so a caller that can't\n * cheaply keep them disjoint doesn't get an ambiguous segment.\n *\n * Clicking a mixed segment turns it fully **on**, matching the\n * everywhere-else convention (and `toggleFlagInRange`'s rule for a\n * partially-styled text range) that a mixed toggle resolves toward\n * the affirmative rather than clearing.\n */\n mixedValues?: readonly V[];\n onChange: (next: V[]) => void;\n });\n\nexport function ToggleBar<V extends string | number = string>(props: ToggleBarProps<V>): ReactElement {\n const { items, ariaLabel, className, height, size, variant } = props;\n const mode = props.mode ?? 'single';\n\n const isSelected = (value: V): boolean => {\n if (mode === 'multiple') return (props.value as readonly V[]).includes(value);\n return (props.value as V | null) === value;\n };\n\n /** Mixed only in multiple mode, and only where `value` doesn't already\n * claim the segment. Single mode has nothing to be mixed about — its\n * aggregate is one value or none. */\n const isMixed = (value: V): boolean => {\n if (mode !== 'multiple') return false;\n const mixed = (props as { mixedValues?: readonly V[] }).mixedValues;\n return mixed !== undefined && mixed.includes(value) && !isSelected(value);\n };\n\n const handleClick = (index: number) => () => {\n const item = items[index];\n if (item.disabled) return;\n if (mode === 'multiple') {\n const current = props.value as readonly V[];\n const next = current.includes(item.value)\n ? current.filter(v => v !== item.value)\n : [...current, item.value];\n (props.onChange as (n: V[]) => void)(next);\n } else {\n const current = props.value as V | null;\n if (current === item.value) {\n if ((props as { allowDeselect?: boolean }).allowDeselect) {\n (props.onChange as (n: V | null) => void)(null);\n }\n return;\n }\n (props.onChange as (n: V | null) => void)(item.value);\n }\n };\n\n const selectedIndex = mode === 'single'\n ? items.findIndex(it => it.value === (props.value as V | null))\n : -1;\n\n const roving = useRovingTabIndex({\n items,\n itemClassName: s.segment,\n // Single mode is a radiogroup: the tab stop is the current value, and\n // arrow keys move the selection with the focus. Multiple mode is a set of\n // independent toggles — focus alone moves, and Space/Enter flips.\n tabStopIndex: selectedIndex >= 0 && !items[selectedIndex].disabled ? selectedIndex : undefined,\n onNavigate: mode === 'single'\n ? (index) => (props.onChange as (n: V | null) => void)(items[index].value)\n : undefined,\n onActivate: mode === 'multiple' ? (index) => handleClick(index)() : undefined,\n });\n\n const style: CSSProperties | undefined = height !== undefined\n ? ({ ['--wzl-tb-height' as string]: `${height}px` } as CSSProperties)\n : undefined;\n\n const rootCls = [\n s.root,\n size && s[`size_${size}`],\n variant && s[`variant_${variant}`],\n className,\n ].filter(Boolean).join(' ');\n\n return (\n <div\n ref={roving.rootRef}\n className={rootCls}\n role={mode === 'multiple' ? 'group' : 'radiogroup'}\n aria-label={ariaLabel}\n style={style}\n >\n {items.map((item, i) => {\n const selected = isSelected(item.value);\n const mixed = isMixed(item.value);\n const cls = [s.segment, selected && s.segmentSelected, mixed && s.segmentMixed]\n .filter(Boolean)\n .join(' ');\n return (\n <button\n key={item.value}\n type=\"button\"\n role={mode === 'multiple' ? undefined : 'radio'}\n aria-checked={mode === 'multiple' ? undefined : selected}\n aria-pressed={mode === 'multiple' ? (mixed ? 'mixed' : selected) : undefined}\n aria-label={item.ariaLabel}\n disabled={item.disabled}\n tabIndex={roving.tabIndexFor(i)}\n className={cls}\n onClick={handleClick(i)}\n onKeyDown={roving.onKeyDown(i)}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AC0DA,SAAgB,EAA8C,GAAwC;CACpG,IAAM,EAAE,UAAO,cAAW,cAAW,WAAQ,SAAM,eAAY,GACzD,IAAO,EAAM,QAAQ,UAErB,KAAc,MACd,MAAS,aAAoB,EAAM,MAAuB,SAAS,EAAM,GACrE,EAAM,UAAuB,GAMjC,KAAW,MAAsB;AACrC,MAAI,MAAS,WAAY,QAAO;EAChC,IAAM,IAAS,EAAyC;AACxD,SAAO,MAAU,KAAA,KAAa,EAAM,SAAS,EAAM,IAAI,CAAC,EAAW,EAAM;IAGrE,KAAe,YAAwB;EAC3C,IAAM,IAAO,EAAM;AACf,SAAK,SACT,KAAI,MAAS,YAAY;GACvB,IAAM,IAAU,EAAM,OAChB,IAAO,EAAQ,SAAS,EAAK,MAAM,GACrC,EAAQ,QAAO,MAAK,MAAM,EAAK,MAAM,GACrC,CAAC,GAAG,GAAS,EAAK,MAAM;AAC3B,KAAM,SAA8B,EAAK;SACrC;AAEL,OADgB,EAAM,UACN,EAAK,OAAO;AAC1B,IAAK,EAAsC,iBACxC,EAAM,SAAmC,KAAK;AAEjD;;AAED,KAAM,SAAmC,EAAK,MAAM;;IAInD,IAAgB,MAAS,WAC3B,EAAM,WAAU,MAAM,EAAG,UAAW,EAAM,MAAmB,GAC7D,IAEE,IAAS,EAAkB;EAC/B;EACA,eAAe,EAAE;EAIjB,cAAc,KAAiB,KAAK,CAAC,EAAM,GAAe,WAAW,IAAgB,KAAA;EACrF,YAAY,MAAS,YAChB,MAAW,EAAM,SAAmC,EAAM,GAAO,MAAM,GACxE,KAAA;EACJ,YAAY,MAAS,cAAc,MAAU,EAAY,EAAM,EAAE,GAAG,KAAA;EACrE,CAAC,EAEI,IAAmC,MAAW,KAAA,IAEhD,KAAA,IADC,EAAG,mBAA8B,GAAG,EAAO,KAAK,EAG/C,IAAU;EACd,EAAE;EACF,KAAQ,EAAE,QAAQ;EAClB,KAAW,EAAE,WAAW;EACxB;EACD,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAE3B,QACE,kBAAC,OAAD;EACE,KAAK,EAAO;EACZ,WAAW;EACX,MAAM,MAAS,aAAa,UAAU;EACtC,cAAY;EACL;YAEN,EAAM,KAAK,GAAM,MAAM;GACtB,IAAM,IAAW,EAAW,EAAK,MAAM,EACjC,IAAQ,EAAQ,EAAK,MAAM,EAC3B,IAAM;IAAC,EAAE;IAAS,KAAY,EAAE;IAAiB,KAAS,EAAE;IAAa,CAC5E,OAAO,QAAQ,CACf,KAAK,IAAI;AACZ,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,MAAM,MAAS,aAAa,KAAA,IAAY;IACxC,gBAAc,MAAS,aAAa,KAAA,IAAY;IAChD,gBAAc,MAAS,aAAc,IAAQ,UAAU,IAAY,KAAA;IACnE,cAAY,EAAK;IACjB,UAAU,EAAK;IACf,UAAU,EAAO,YAAY,EAAE;IAC/B,WAAW;IACX,SAAS,EAAY,EAAE;IACvB,WAAW,EAAO,UAAU,EAAE;cAE7B,EAAK;IACC,EAbF,EAAK,MAaH;IAEX;EACE,CAAA"}
1
+ {"version":3,"file":"ToggleBar-BP1zFcvg.js","names":[],"sources":["../../src/components/ToggleBar/ToggleBar.module.css","../../src/components/ToggleBar/ToggleBar.tsx"],"sourcesContent":["/* Aqua-glass segmented control. The track is a pill-shaped well; the\n selected segment becomes a tinted glass cylinder that reads as the same\n \"lozenge\" family as the standalone Button. Visual params come from the\n `--btn-*` custom properties (see Button.module.css) so the Button Lab\n can tune both surfaces with one set of sliders. */\n.root {\n display: inline-flex;\n height: var(--wzl-tb-height, 28px);\n padding: 2px;\n background: linear-gradient(\n 180deg,\n rgba(0, 0, 0, 0.18) 0%,\n rgba(0, 0, 0, 0.05) 100%\n );\n border-radius: 999px;\n /* Clip segments to the track outline so interior corners read crisp and\n the selected segment's glass treatment doesn't paint over the rounded\n outer pixels. */\n overflow: hidden;\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.4),\n 0 1px 0 rgba(255, 255, 255, 0.06);\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n}\n\n.segment {\n position: relative;\n flex: 1 1 0;\n min-width: 0;\n padding: 0 12px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n margin: 0;\n font-family: var(--wzl-font-ui);\n font-weight: 300;\n font-size: var(--btn-fontsize, 14px);\n line-height: 1;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n /* Only round the outer corners of the bar — middle segments stay square\n so the seams between them don't render as facing parentheses. */\n border-radius: 0;\n isolation: isolate;\n overflow: hidden;\n transition: color var(--wzl-motion-fast);\n}\n\n.segment:first-child {\n border-radius: 999px 0 0 999px;\n}\n\n.segment:last-child {\n border-radius: 0 999px 999px 0;\n}\n\n.segment:only-child {\n border-radius: 999px;\n}\n\n.segment:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -2px;\n}\n\n.segment:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.segment:hover:not(:disabled):not(.segmentSelected) {\n color: var(--wzl-fg);\n}\n\n/* Selected segment — full Aqua glass cylinder. Mirrors Button.variant_primary. */\n.segmentSelected {\n background: linear-gradient(\n 180deg,\n color-mix(in srgb, var(--wzl-accent) var(--btn-top-alpha, 45%), transparent) 0%,\n color-mix(in srgb, var(--wzl-accent) var(--btn-upper-alpha, 70%), transparent) calc(var(--btn-equator, 52%) - var(--btn-equator-spread, 12%)),\n color-mix(in srgb, var(--wzl-accent) var(--btn-equator-tint, 95%), white) var(--btn-equator, 52%),\n var(--wzl-accent) calc(var(--btn-equator, 52%) + var(--btn-equator-spread, 12%) * 1.6),\n color-mix(in srgb, var(--wzl-accent) var(--btn-base-tint, 70%), black) 100%\n );\n backdrop-filter: blur(var(--btn-blur, 6px)) saturate(var(--btn-saturate, 1.3));\n -webkit-backdrop-filter: blur(var(--btn-blur, 6px)) saturate(var(--btn-saturate, 1.3));\n color: var(--wzl-fg-on-accent);\n text-shadow:\n 0 1px 0 rgba(0, 0, 0, var(--btn-text-shadow, 0.35)),\n var(--btn-text-inset-x, 0px) var(--btn-text-inset-y, 1px) 0 rgba(0, 0, 0, var(--btn-text-inset, 0.55)),\n 0 0 6px rgba(0, 0, 0, calc(var(--btn-text-shadow, 0.35) * 0.5));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, var(--btn-inset-hi, 0.42)),\n inset 0 -1px 2px rgba(0, 0, 0, var(--btn-inset-lo, 0.3)),\n 0 0 0 1px rgba(0, 0, 0, var(--btn-rim, 0.22)),\n 0 1px 2px rgba(0, 0, 0, var(--btn-drop-near, 0.3)),\n 0 2px 4px rgba(0, 0, 0, calc(var(--btn-drop-far, 0.18) * 0.8));\n}\n\n/* Specular gloss on the selected segment. Top corners inherit the\n segment's radius so they hug the upper edge cleanly; bottom corners\n are flattened so the gloss ends in a straight line at the equator\n instead of two little curves that read as `)(` brackets. */\n.segmentSelected::before {\n content: '';\n position: absolute;\n inset: 1px 1px var(--btn-gloss-bottom, 50%) 1px;\n border-radius: inherit;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background: linear-gradient(\n 180deg,\n rgba(255, 255, 255, var(--btn-gloss-top, 0.42)) 0%,\n rgba(255, 255, 255, var(--btn-gloss-mid, 0.12)) 60%,\n rgba(255, 255, 255, 0) 100%\n );\n pointer-events: none;\n z-index: 1;\n}\n\n.segmentSelected > * {\n position: relative;\n z-index: 2;\n}\n\n/* Mixed segment (`aria-pressed=\"mixed\"`) — the sources disagree. Reads as a\n deliberate third state rather than a dimmed \"on\": a flat accent wash with\n none of the selected segment's gloss/cylinder treatment, so it can't be\n mistaken for the glass lozenge at a glance. */\n.segmentMixed {\n background: color-mix(in srgb, var(--wzl-accent) 22%, transparent);\n color: var(--wzl-fg);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--wzl-accent) 45%, transparent);\n}\n\n.segmentMixed:hover:not(:disabled) {\n background: color-mix(in srgb, var(--wzl-accent) 32%, transparent);\n}\n\n.variant_minimal .segmentMixed {\n background: color-mix(in srgb, var(--wzl-accent) 18%, transparent);\n box-shadow: none;\n color: var(--wzl-fg);\n}\n\n/* Small variant — ~60% of the default height, with proportionally reduced\n padding and font. Sized for dense surfaces (lab control panels, inline\n filters, etc.). */\n.size_sm {\n height: 17px;\n padding: 1px;\n}\n\n.size_sm .segment {\n padding: 0 8px;\n font-size: 11px;\n}\n\n/* Minimal variant — strips the pill track, glass selected state, inset\n shadows, and outer padding. Used in dense diagnostic surfaces (dispatch\n trace, lab inspectors) where the bar must read as plain text controls. */\n.variant_minimal {\n height: auto;\n padding: 0;\n background: none;\n border: 1px solid var(--wzl-border, rgba(255, 255, 255, 0.18));\n border-radius: 3px;\n box-shadow: none;\n gap: 0;\n}\n\n.variant_minimal .segment {\n padding: 0 6px;\n font-size: 11px;\n font-weight: 400;\n line-height: 1.4;\n border-radius: 0;\n color: var(--wzl-fg-muted);\n transition: none;\n}\n\n/* Dividers between segments make the mutual-exclusion grouping visible\n without a heavy track. Only the inner edges get a rule — the outer\n border on `.variant_minimal` handles the perimeter. */\n.variant_minimal .segment + .segment {\n border-left: 1px solid var(--wzl-border, rgba(255, 255, 255, 0.18));\n}\n\n.variant_minimal .segment:first-child,\n.variant_minimal .segment:last-child,\n.variant_minimal .segment:only-child {\n border-radius: 0;\n}\n\n.variant_minimal .segment:hover:not(:disabled):not(.segmentSelected) {\n color: var(--wzl-fg);\n background: rgba(255, 255, 255, 0.06);\n}\n\n.variant_minimal .segmentSelected {\n background: none;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n color: var(--wzl-accent-fg, var(--wzl-accent));\n text-shadow: none;\n box-shadow: none;\n font-weight: 500;\n}\n\n.variant_minimal .segmentSelected::before {\n content: none;\n}\n","import { type CSSProperties, type ReactElement, type ReactNode } from 'react';\nimport s from './ToggleBar.module.css';\nimport { useRovingTabIndex } from '../../useRovingTabIndex';\n\n/** One segment of a {@link ToggleBar}, identified by its `value`. */\nexport type ToggleBarItem<V extends string | number = string> = {\n value: V;\n label?: ReactNode;\n ariaLabel?: string;\n disabled?: boolean;\n};\n\n/** Segment height and type scale for a {@link ToggleBar}. */\nexport type ToggleBarSize = 'sm' | 'md';\n/** Visual treatment of a {@link ToggleBar}. */\nexport type ToggleBarVariant = 'default' | 'minimal';\n\ntype CommonProps = {\n ariaLabel?: string;\n className?: string;\n height?: number;\n /** Size variant. `sm` is ~60% of the default height with reduced padding\n * and font size — sized for dense surfaces like lab control panels. */\n size?: ToggleBarSize;\n /** Visual variant. `minimal` strips the pill track and glass treatment;\n * selection becomes a flat accent. For dense diagnostic surfaces. */\n variant?: ToggleBarVariant;\n};\n\n/**\n * Props for {@link ToggleBar}, discriminated on `mode`. Single mode is\n * controlled by one value or `null`; multiple mode by an array, and only it\n * accepts `mixedValues`.\n */\nexport type ToggleBarProps<V extends string | number = string> =\n | (CommonProps & {\n mode?: 'single';\n items: readonly ToggleBarItem<V>[];\n value: V | null;\n onChange: (next: V | null) => void;\n allowDeselect?: boolean;\n })\n | (CommonProps & {\n mode: 'multiple';\n items: readonly ToggleBarItem<V>[];\n value: readonly V[];\n /**\n * Values that are neither on nor off — the sources this bar\n * aggregates disagree (a text range that is bold in part of it, a\n * multi-selection whose nodes differ). Rendered `aria-pressed=\"mixed\"`,\n * the ARIA tri-state a toggle button actually has, rather than\n * `SelectionPanel`'s reduced-opacity-plus-`title` workaround for\n * `Switch`, which has no indeterminate state to render.\n *\n * `value` wins where the two lists overlap, so a caller that can't\n * cheaply keep them disjoint doesn't get an ambiguous segment.\n *\n * Clicking a mixed segment turns it fully **on**, matching the\n * everywhere-else convention (and `toggleFlagInRange`'s rule for a\n * partially-styled text range) that a mixed toggle resolves toward\n * the affirmative rather than clearing.\n */\n mixedValues?: readonly V[];\n onChange: (next: V[]) => void;\n });\n\n/**\n * Segmented control for choosing among a fixed set of values — one of them\n * (`mode: 'single'`, the default) or any number (`mode: 'multiple'`).\n *\n * Single mode ignores a click on the already-selected segment unless\n * `allowDeselect` is set. Arrow keys move focus without changing the value;\n * Space and Enter commit.\n */\nexport function ToggleBar<V extends string | number = string>(props: ToggleBarProps<V>): ReactElement {\n const { items, ariaLabel, className, height, size, variant } = props;\n const mode = props.mode ?? 'single';\n\n const isSelected = (value: V): boolean => {\n if (mode === 'multiple') return (props.value as readonly V[]).includes(value);\n return (props.value as V | null) === value;\n };\n\n /** Mixed only in multiple mode, and only where `value` doesn't already\n * claim the segment. Single mode has nothing to be mixed about — its\n * aggregate is one value or none. */\n const isMixed = (value: V): boolean => {\n if (mode !== 'multiple') return false;\n const mixed = (props as { mixedValues?: readonly V[] }).mixedValues;\n return mixed !== undefined && mixed.includes(value) && !isSelected(value);\n };\n\n const handleClick = (index: number) => () => {\n const item = items[index];\n if (item.disabled) return;\n if (mode === 'multiple') {\n const current = props.value as readonly V[];\n const next = current.includes(item.value)\n ? current.filter(v => v !== item.value)\n : [...current, item.value];\n (props.onChange as (n: V[]) => void)(next);\n } else {\n const current = props.value as V | null;\n if (current === item.value) {\n if ((props as { allowDeselect?: boolean }).allowDeselect) {\n (props.onChange as (n: V | null) => void)(null);\n }\n return;\n }\n (props.onChange as (n: V | null) => void)(item.value);\n }\n };\n\n const selectedIndex = mode === 'single'\n ? items.findIndex(it => it.value === (props.value as V | null))\n : -1;\n\n const roving = useRovingTabIndex({\n items,\n itemClassName: s.segment,\n // Single mode is a radiogroup: the tab stop is the current value, and\n // arrow keys move the selection with the focus. Multiple mode is a set of\n // independent toggles — focus alone moves, and Space/Enter flips.\n tabStopIndex: selectedIndex >= 0 && !items[selectedIndex].disabled ? selectedIndex : undefined,\n onNavigate: mode === 'single'\n ? (index) => (props.onChange as (n: V | null) => void)(items[index].value)\n : undefined,\n onActivate: mode === 'multiple' ? (index) => handleClick(index)() : undefined,\n });\n\n const style: CSSProperties | undefined = height !== undefined\n ? ({ ['--wzl-tb-height' as string]: `${height}px` } as CSSProperties)\n : undefined;\n\n const rootCls = [\n s.root,\n size && s[`size_${size}`],\n variant && s[`variant_${variant}`],\n className,\n ].filter(Boolean).join(' ');\n\n return (\n <div\n ref={roving.rootRef}\n className={rootCls}\n role={mode === 'multiple' ? 'group' : 'radiogroup'}\n aria-label={ariaLabel}\n style={style}\n >\n {items.map((item, i) => {\n const selected = isSelected(item.value);\n const mixed = isMixed(item.value);\n const cls = [s.segment, selected && s.segmentSelected, mixed && s.segmentMixed]\n .filter(Boolean)\n .join(' ');\n return (\n <button\n key={item.value}\n type=\"button\"\n role={mode === 'multiple' ? undefined : 'radio'}\n aria-checked={mode === 'multiple' ? undefined : selected}\n aria-pressed={mode === 'multiple' ? (mixed ? 'mixed' : selected) : undefined}\n aria-label={item.ariaLabel}\n disabled={item.disabled}\n tabIndex={roving.tabIndexFor(i)}\n className={cls}\n onClick={handleClick(i)}\n onKeyDown={roving.onKeyDown(i)}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AC0EA,SAAgB,EAA8C,GAAwC;CACpG,IAAM,EAAE,UAAO,cAAW,cAAW,WAAQ,SAAM,eAAY,GACzD,IAAO,EAAM,QAAQ,UAErB,KAAc,MACd,MAAS,aAAoB,EAAM,MAAuB,SAAS,EAAM,GACrE,EAAM,UAAuB,GAMjC,KAAW,MAAsB;AACrC,MAAI,MAAS,WAAY,QAAO;EAChC,IAAM,IAAS,EAAyC;AACxD,SAAO,MAAU,KAAA,KAAa,EAAM,SAAS,EAAM,IAAI,CAAC,EAAW,EAAM;IAGrE,KAAe,YAAwB;EAC3C,IAAM,IAAO,EAAM;AACf,SAAK,SACT,KAAI,MAAS,YAAY;GACvB,IAAM,IAAU,EAAM,OAChB,IAAO,EAAQ,SAAS,EAAK,MAAM,GACrC,EAAQ,QAAO,MAAK,MAAM,EAAK,MAAM,GACrC,CAAC,GAAG,GAAS,EAAK,MAAM;AAC3B,KAAM,SAA8B,EAAK;SACrC;AAEL,OADgB,EAAM,UACN,EAAK,OAAO;AAC1B,IAAK,EAAsC,iBACxC,EAAM,SAAmC,KAAK;AAEjD;;AAED,KAAM,SAAmC,EAAK,MAAM;;IAInD,IAAgB,MAAS,WAC3B,EAAM,WAAU,MAAM,EAAG,UAAW,EAAM,MAAmB,GAC7D,IAEE,IAAS,EAAkB;EAC/B;EACA,eAAe,EAAE;EAIjB,cAAc,KAAiB,KAAK,CAAC,EAAM,GAAe,WAAW,IAAgB,KAAA;EACrF,YAAY,MAAS,YAChB,MAAW,EAAM,SAAmC,EAAM,GAAO,MAAM,GACxE,KAAA;EACJ,YAAY,MAAS,cAAc,MAAU,EAAY,EAAM,EAAE,GAAG,KAAA;EACrE,CAAC,EAEI,IAAmC,MAAW,KAAA,IAEhD,KAAA,IADC,EAAG,mBAA8B,GAAG,EAAO,KAAK,EAG/C,IAAU;EACd,EAAE;EACF,KAAQ,EAAE,QAAQ;EAClB,KAAW,EAAE,WAAW;EACxB;EACD,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAE3B,QACE,kBAAC,OAAD;EACE,KAAK,EAAO;EACZ,WAAW;EACX,MAAM,MAAS,aAAa,UAAU;EACtC,cAAY;EACL;YAEN,EAAM,KAAK,GAAM,MAAM;GACtB,IAAM,IAAW,EAAW,EAAK,MAAM,EACjC,IAAQ,EAAQ,EAAK,MAAM,EAC3B,IAAM;IAAC,EAAE;IAAS,KAAY,EAAE;IAAiB,KAAS,EAAE;IAAa,CAC5E,OAAO,QAAQ,CACf,KAAK,IAAI;AACZ,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,MAAM,MAAS,aAAa,KAAA,IAAY;IACxC,gBAAc,MAAS,aAAa,KAAA,IAAY;IAChD,gBAAc,MAAS,aAAc,IAAQ,UAAU,IAAY,KAAA;IACnE,cAAY,EAAK;IACjB,UAAU,EAAK;IACf,UAAU,EAAO,YAAY,EAAE;IAC/B,WAAW;IACX,SAAS,EAAY,EAAE;IACvB,WAAW,EAAO,UAAU,EAAE;cAE7B,EAAK;IACC,EAbF,EAAK,MAaH;IAEX;EACE,CAAA"}
@@ -11,10 +11,10 @@ var a = {
11
11
  //#endregion
12
12
  //#region src/components/ToolButton/ToolButton.tsx
13
13
  function o(o) {
14
- let { icon: s, label: c, shortcut: l, active: u, disabled: d, ariaDisabled: f, tabbable: p, onClick: m, title: h, className: g } = o, _ = h ?? (l ? `${c} (${l})` : c), v = [
14
+ let { icon: s, label: c, shortcut: l, active: u, disabled: d, ariaDisabled: f, tabbable: p, onClick: m, onKeyDown: h, title: g, className: _ } = o, v = g ?? (l ? `${c} (${l})` : c), y = [
15
15
  a.button,
16
16
  u && a.active,
17
- g
17
+ _
18
18
  ].filter(Boolean).join(" ");
19
19
  return /* @__PURE__ */ i(e, {
20
20
  isDisabled: d,
@@ -23,11 +23,12 @@ function o(o) {
23
23
  children: /* @__PURE__ */ i("button", {
24
24
  type: "button",
25
25
  tabIndex: p ? 0 : -1,
26
- className: v,
26
+ className: y,
27
27
  "aria-current": u ? "true" : void 0,
28
28
  "aria-disabled": f ? "true" : void 0,
29
29
  disabled: d,
30
30
  onClick: m,
31
+ onKeyDown: h,
31
32
  children: [
32
33
  /* @__PURE__ */ r("span", {
33
34
  className: a.icon,
@@ -44,10 +45,10 @@ function o(o) {
44
45
  })
45
46
  ]
46
47
  })
47
- }), /* @__PURE__ */ r(t, { children: _ })]
48
+ }), /* @__PURE__ */ r(t, { children: v })]
48
49
  });
49
50
  }
50
51
  //#endregion
51
52
  export { o as t };
52
53
 
53
- //# sourceMappingURL=ToolButton-DUeg8fPN.js.map
54
+ //# sourceMappingURL=ToolButton-DpT-mukU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolButton-DpT-mukU.js","names":[],"sources":["../../src/components/ToolButton/ToolButton.module.css","../../src/components/ToolButton/ToolButton.tsx"],"sourcesContent":[".button {\n display: inline-flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 2px;\n min-width: 44px;\n padding: 6px 4px;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 3px;\n color: var(--wzl-fg);\n cursor: default;\n font-family: inherit;\n font-size: 10px;\n line-height: 1.1;\n}\n.button:hover:not([disabled]) {\n background: var(--wzl-surface-hover);\n color: var(--wzl-fg);\n}\n.button:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -2px;\n}\n.button.active {\n background: var(--wzl-surface-pressed);\n border-color: var(--wzl-accent);\n color: var(--wzl-fg);\n}\n.button[disabled] {\n opacity: 0.4;\n}\n\n.icon { display: block; }\n.icon > svg { display: block; }\n.label { font-size: 10px; }\n.shortcut {\n color: var(--wzl-fg-muted);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 9px;\n}\n","import type { KeyboardEvent, ReactNode } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { Tooltip, TooltipTrigger } from '../Tooltip';\nimport s from './ToolButton.module.css';\n\n/** Props for {@link ToolButton}. */\nexport interface ToolButtonProps {\n /** Icon node (typically an SVG component). */\n icon: ReactNode;\n /** Human-readable label shown under the icon. */\n label: string;\n /** Optional shortcut hint (e.g. \"V\" or \"⌘Z\"). */\n shortcut?: string;\n /** Selected/active state — toggles the active visual treatment. */\n active?: boolean;\n /** Disabled state — passes through to the underlying button. */\n disabled?: boolean;\n /**\n * When true, sets `aria-disabled=\"true\"` on the button without using the\n * native `disabled` attribute. This keeps the button focusable and reachable\n * by keyboard (roving-tabindex still applies) while marking it as ineligible\n * to screen readers. The caller is responsible for making `onClick` a no-op.\n */\n ariaDisabled?: boolean;\n /**\n * Whether this button is the currently tabbable member of its toolbar.\n * Toolbars use roving tabindex: exactly one button has `tabIndex=0` at\n * a time; the rest are `-1`. Caller manages which.\n */\n tabbable?: boolean;\n /** Click handler. */\n onClick(): void;\n /** Key handler for the underlying button — a toolbar's roving-tabindex\n * navigation attaches here. */\n onKeyDown?(e: KeyboardEvent<HTMLElement>): void;\n /**\n * Tooltip content. Defaults to `label` (plus `shortcut` if provided).\n */\n title?: string;\n /** Additional class for the root button. */\n className?: string;\n}\n\n/**\n * Generic icon-+-label toolbar button — building block for `ToolPalette`\n * and similar surfaces. Headless about layout direction (parent group\n * supplies flex direction via `ToolGroup`). Theme via `--wzl-*` tokens.\n */\nexport function ToolButton(props: ToolButtonProps) {\n const {\n icon, label, shortcut, active, disabled, ariaDisabled, tabbable,\n onClick, onKeyDown, title, className,\n } = props;\n const resolvedTitle = title ?? (shortcut ? `${label} (${shortcut})` : label);\n const cls = [s.button, active && s.active, className].filter(Boolean).join(' ');\n return (\n <TooltipTrigger isDisabled={disabled}>\n <Focusable isDisabled={disabled}>\n <button\n type=\"button\"\n tabIndex={tabbable ? 0 : -1}\n className={cls}\n aria-current={active ? 'true' : undefined}\n aria-disabled={ariaDisabled ? 'true' : undefined}\n disabled={disabled}\n onClick={onClick}\n onKeyDown={onKeyDown}\n >\n <span className={s.icon} aria-hidden=\"true\">{icon}</span>\n <span className={s.label}>{label}</span>\n {shortcut && <span className={s.shortcut}>{shortcut}</span>}\n </button>\n </Focusable>\n <Tooltip>{resolvedTitle}</Tooltip>\n </TooltipTrigger>\n );\n}\n"],"mappings":";;;;;;;;;;;;ACgDA,SAAgB,EAAW,GAAwB;CACjD,IAAM,EACJ,SAAM,UAAO,aAAU,WAAQ,aAAU,iBAAc,aACvD,YAAS,cAAW,UAAO,iBACzB,GACE,IAAgB,MAAU,IAAW,GAAG,EAAM,IAAI,EAAS,KAAK,IAChE,IAAM;EAAC,EAAE;EAAQ,KAAU,EAAE;EAAQ;EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAC/E,QACE,kBAAC,GAAD;EAAgB,YAAY;YAA5B,CACE,kBAAC,GAAD;GAAW,YAAY;aACrB,kBAAC,UAAD;IACE,MAAK;IACL,UAAU,IAAW,IAAI;IACzB,WAAW;IACX,gBAAc,IAAS,SAAS,KAAA;IAChC,iBAAe,IAAe,SAAS,KAAA;IAC7B;IACD;IACE;cARb;KAUE,kBAAC,QAAD;MAAM,WAAW,EAAE;MAAM,eAAY;gBAAQ;MAAY,CAAA;KACzD,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAQ;MAAa,CAAA;KACvC,KAAY,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAW;MAAgB,CAAA;KACpD;;GACC,CAAA,EACZ,kBAAC,GAAD,EAAA,UAAU,GAAwB,CAAA,CACnB"}
@@ -22,4 +22,4 @@ function n(n) {
22
22
  //#endregion
23
23
  export { n as t };
24
24
 
25
- //# sourceMappingURL=ToolGroup-D0CvyQhQ.js.map
25
+ //# sourceMappingURL=ToolGroup-BTIwd4U4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolGroup-BTIwd4U4.js","names":[],"sources":["../../src/components/ToolGroup/ToolGroup.module.css","../../src/components/ToolGroup/ToolGroup.tsx"],"sourcesContent":[".group {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.group.horizontal {\n flex-direction: row;\n}\n","import type { ReactNode } from 'react';\nimport s from './ToolGroup.module.css';\n\n/** Props for {@link ToolGroup}. */\nexport interface ToolGroupProps {\n children: ReactNode;\n /**\n * Lays children along an axis. `'vertical'` (default) stacks them in\n * a column; `'horizontal'` lays them in a row. Independent of the\n * parent toolbar's own orientation.\n */\n orientation?: 'vertical' | 'horizontal';\n /**\n * Optional `data-group` attribute — useful for parents that want to\n * select the group from CSS or tests by name.\n */\n groupKey?: string;\n /** Accessible name for the group (rendered as `aria-label`). */\n ariaLabel?: string;\n className?: string;\n}\n\n/**\n * Generic toolbar group — a `role=\"group\"` flex container that arranges\n * `<ToolButton>`s (or any children) along a chosen axis. No visual chrome\n * beyond layout; theming lives on the buttons.\n */\nexport function ToolGroup(props: ToolGroupProps) {\n const { children, orientation = 'vertical', groupKey, ariaLabel, className } = props;\n const cls = [s.group, orientation === 'horizontal' && s.horizontal, className]\n .filter(Boolean).join(' ');\n return (\n <div className={cls} role=\"group\" data-group={groupKey} aria-label={ariaLabel}>\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;;AC2BA,SAAgB,EAAU,GAAuB;CAC/C,IAAM,EAAE,aAAU,iBAAc,YAAY,aAAU,cAAW,iBAAc;AAG/E,QACE,kBAAC,OAAD;EAAK,WAHK;GAAC,EAAE;GAAO,MAAgB,gBAAgB,EAAE;GAAY;GAAU,CAC3E,OAAO,QAAQ,CAAC,KAAK,IAEN;EAAK,MAAK;EAAQ,cAAY;EAAU,cAAY;EACjE;EACG,CAAA"}
@@ -24,4 +24,4 @@ function r(r) {
24
24
  //#endregion
25
25
  export { r as t };
26
26
 
27
- //# sourceMappingURL=ToolOptionsBar-N_MQcSo4.js.map
27
+ //# sourceMappingURL=ToolOptionsBar-JvmY7iNe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolOptionsBar-JvmY7iNe.js","names":[],"sources":["../../src/components/ToolOptionsBar/ToolOptionsBar.module.css","../../src/components/ToolOptionsBar/ToolOptionsBar.tsx"],"sourcesContent":["/* Horizontal strip reserved above the workspace for contextual tool\n options. Fixed height so mounting/unmounting content never resizes\n the workspace beneath it — that's the whole reason this row exists\n as permanent chrome rather than an on-demand overlay.\n\n Overflow: when controls exceed the available width, `.controls`\n scrolls horizontally rather than wrapping (which would grow the row\n and defeat the fixed-height guarantee above) or clipping (which\n would hide controls with no way to reach them). A flyout/long-press\n affordance was considered and rejected — grouped, directly visible\n layouts are preferred in this repo. */\n\n.root {\n display: flex;\n align-items: center;\n gap: 8px;\n height: var(--wzl-tb-height, 28px);\n padding: 0 8px;\n background: var(--wzl-surface);\n border-bottom: 1px solid var(--wzl-border);\n color: var(--wzl-fg);\n overflow: hidden;\n}\n\n.label {\n flex: 0 0 auto;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--wzl-fg-muted);\n white-space: nowrap;\n}\n\n.controls {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1 1 auto;\n min-width: 0;\n height: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: thin;\n}\n\n/* Firefox gets a thin scrollbar for free via `scrollbar-width` above.\n * Chromium/WebKit need it spelled out — on a classic (non-overlay)\n * scrollbar theme (default on Windows Chrome/Edge), an unstyled\n * horizontal scrollbar is ~15-17px tall and renders *inside* this box,\n * clipping/off-centering the row's controls. That would reintroduce the\n * exact height instability the scroll-over-wrap overflow choice above\n * exists to avoid — just at the row's inner edge instead of its outer\n * one. Pin it thin and out of the way instead. */\n.controls::-webkit-scrollbar {\n height: 4px;\n}\n.controls::-webkit-scrollbar-track {\n background: transparent;\n}\n.controls::-webkit-scrollbar-thumb {\n background: var(--wzl-border);\n border-radius: 2px;\n}\n","import type { ReactNode } from 'react';\nimport s from './ToolOptionsBar.module.css';\n\n/** Props for {@link ToolOptionsBar}. */\nexport interface ToolOptionsBarProps {\n /**\n * Context label for the row — e.g. the active tool's name. Rendered\n * before the children and doubles as the toolbar's `aria-label`. Omit\n * it and the row still renders (it's a permanently reserved slot), just\n * without an accessible name.\n *\n * Using `label` for both the visible text and `aria-label` means a\n * screen reader announces it twice when landing on the toolbar (\"Text,\n * toolbar\", then \"Text\" again when browsing into the visible span).\n * `aria-labelledby` would avoid the echo at the cost of id plumbing and\n * an awkward no-`id`-to-point-at case when `label` is omitted. The\n * double-announcement is common enough for labelled landmark regions\n * that it's left as-is — considered, not missed.\n */\n label?: string;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * Horizontal strip reserved above the workspace for contextual tool\n * options — its first tenant is the character controls a text tool\n * exposes while a caret range is active. Pure chrome: it renders an\n * optional label and a children slot, nothing else. It knows nothing\n * about text, or about any specific tool — any tool can fill it.\n *\n * The row always renders, even with no `label` and no `children`,\n * because the app reserves its height permanently rather than mounting\n * it on demand — mounting mid-edit would resize the workspace under a\n * canvas that's sized to the page.\n *\n * `role=\"toolbar\"` without roving tabindex: the ARIA authoring practices\n * expect arrow-key roving focus across a toolbar's items, but this\n * component doesn't own or know the shape of its children — they're\n * arbitrary, possibly compound controls (`NumberField`, `Select`,\n * `ColorField`) that already use arrow keys for their own value editing.\n * Imposing roving focus here would fight those controls' own key\n * handling. Tab order falls back to normal DOM order for now; revisit\n * once the first tenant's control set is concrete and a roving-focus\n * contract can be designed in coordination with those controls.\n */\nexport function ToolOptionsBar(props: ToolOptionsBarProps) {\n const { label, children, className } = props;\n const cls = [s.root, className].filter(Boolean).join(' ');\n return (\n <div className={cls} role=\"toolbar\" aria-label={label}>\n {label !== undefined && <span className={s.label}>{label}</span>}\n <div className={s.controls}>{children}</div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;AC8CA,SAAgB,EAAe,GAA4B;CACzD,IAAM,EAAE,UAAO,aAAU,iBAAc;AAEvC,QACE,kBAAC,OAAD;EAAK,WAFK,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAEnC;EAAK,MAAK;EAAU,cAAY;YAAhD,CACG,MAAU,KAAA,KAAa,kBAAC,QAAD;GAAM,WAAW,EAAE;aAAQ;GAAa,CAAA,EAChE,kBAAC,OAAD;GAAK,WAAW,EAAE;GAAW;GAAe,CAAA,CACxC"}
@@ -1,15 +1,17 @@
1
1
  import { t as e } from "./keyGlyph-BDH5fqkO.js";
2
- import { t } from "./ToolButton-DUeg8fPN.js";
3
- import { t as n } from "./ToolGroup-D0CvyQhQ.js";
4
- import { Fragment as r, useRef as i } from "react";
2
+ import { t } from "./useRovingTabIndex-DBZtq4IH.js";
3
+ import { t as n } from "./ToolButton-DpT-mukU.js";
4
+ import { t as r } from "./ToolGroup-BTIwd4U4.js";
5
+ import { Fragment as i } from "react";
5
6
  import { UnknownIcon as a } from "@weasel-js/core";
6
7
  import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
8
  import { eligibleContribution as c } from "@weasel-js/modes";
8
9
  var l = {
9
- palette: "_palette_1ntz3_1",
10
- horizontal: "_horizontal_1ntz3_11",
11
- ineligibleBtn: "_ineligibleBtn_1ntz3_16",
12
- separator: "_separator_1ntz3_21"
10
+ palette: "_palette_1mz1c_1",
11
+ horizontal: "_horizontal_1mz1c_11",
12
+ paletteBtn: "_paletteBtn_1mz1c_17",
13
+ ineligibleBtn: "_ineligibleBtn_1mz1c_20",
14
+ separator: "_separator_1mz1c_25"
13
15
  };
14
16
  //#endregion
15
17
  //#region src/components/ToolPalette/formatShortcut.ts
@@ -54,58 +56,36 @@ function _(e) {
54
56
  l.palette,
55
57
  u === "horizontal" && l.horizontal,
56
58
  p
57
- ].filter(Boolean).join(" "), C = x.flatMap((e) => b.get(e).map((e) => e.id)), w = a.active && C.includes(a.active) ? a.active : C[0], T = i(null);
58
- function E(e) {
59
- let t = e.target;
60
- if (t.tagName !== "BUTTON") return;
61
- let n = T.current;
62
- if (!n) return;
63
- let r = Array.from(n.querySelectorAll("button")), i = r.indexOf(t);
64
- if (i < 0) return;
65
- let a = -1;
66
- switch (e.key) {
67
- case "ArrowDown":
68
- case "ArrowRight":
69
- a = (i + 1) % r.length;
70
- break;
71
- case "ArrowUp":
72
- case "ArrowLeft":
73
- a = (i - 1 + r.length) % r.length;
74
- break;
75
- case "Home":
76
- a = 0;
77
- break;
78
- case "End":
79
- a = r.length - 1;
80
- break;
81
- }
82
- a >= 0 && (e.preventDefault(), r[a].focus());
83
- }
59
+ ].filter(Boolean).join(" "), C = x.flatMap((e) => b.get(e)), w = C.map((e) => v ? c(v, e) : !0), T = C.findIndex((e) => e.id === a.active), E = t({
60
+ items: w.map((e) => ({ disabled: !e })),
61
+ itemClassName: l.paletteBtn,
62
+ ...T >= 0 && w[T] ? { tabStopIndex: T } : {}
63
+ }), D = -1;
84
64
  return /* @__PURE__ */ o("div", {
85
- ref: T,
65
+ ref: E.rootRef,
86
66
  className: S,
87
67
  role: "toolbar",
88
68
  "aria-label": "Tools",
89
- onKeyDown: E,
90
- children: x.map((e, i) => /* @__PURE__ */ s(r, { children: [i > 0 && /* @__PURE__ */ o("div", {
69
+ children: x.map((e, t) => /* @__PURE__ */ s(i, { children: [t > 0 && /* @__PURE__ */ o("div", {
91
70
  className: l.separator,
92
71
  role: "separator",
93
72
  "aria-orientation": u === "horizontal" ? "vertical" : "horizontal"
94
- }), /* @__PURE__ */ o(n, {
73
+ }), /* @__PURE__ */ o(r, {
95
74
  orientation: u,
96
75
  groupKey: e,
97
76
  children: b.get(e).map((e) => {
98
- let n = e.presentation?.label ?? e.id, r = e.presentation?.shortcut ?? d(_ ? _(e.id) : e.keybinding), i = v ? c(v, e) : !0, s = v ? v.current().id : void 0, u = !i && s ? `${n}${r ? ` (${r})` : ""} — disabled in ${s} mode` : void 0;
99
- return /* @__PURE__ */ o(t, {
77
+ let t = ++D, r = e.presentation?.label ?? e.id, i = e.presentation?.shortcut ?? d(_ ? _(e.id) : e.keybinding), s = w[t], c = v ? v.current().id : void 0, u = !s && c ? `${r}${i ? ` (${i})` : ""} — disabled in ${c} mode` : void 0;
78
+ return /* @__PURE__ */ o(n, {
100
79
  icon: g(e),
101
- label: n,
102
- shortcut: r,
80
+ label: r,
81
+ shortcut: i,
103
82
  active: a.active === e.id,
104
- tabbable: e.id === w,
105
- ariaDisabled: !i,
106
- className: i ? void 0 : l.ineligibleBtn,
83
+ tabbable: E.tabIndexFor(t) === 0,
84
+ ariaDisabled: !s,
85
+ className: [l.paletteBtn, !s && l.ineligibleBtn].filter(Boolean).join(" "),
107
86
  title: u,
108
- onClick: i ? () => a.setActive(e.id) : () => {}
87
+ onClick: s ? () => a.setActive(e.id) : () => {},
88
+ onKeyDown: E.onKeyDown(t)
109
89
  }, e.id);
110
90
  })
111
91
  })] }, e))
@@ -114,4 +94,4 @@ function _(e) {
114
94
  //#endregion
115
95
  export { d as n, u as r, _ as t };
116
96
 
117
- //# sourceMappingURL=ToolPalette-BipoMdZz.js.map
97
+ //# sourceMappingURL=ToolPalette-DPn0owno.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToolPalette-DPn0owno.js","names":[],"sources":["../../src/components/ToolPalette/ToolPalette.module.css","../../src/components/ToolPalette/formatShortcut.ts","../../src/components/ToolPalette/ToolPalette.tsx"],"sourcesContent":[".palette {\n display: flex;\n flex-direction: column;\n gap: 4px;\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-radius: 4px;\n padding: 4px;\n user-select: none;\n}\n.palette.horizontal {\n flex-direction: row;\n}\n\n/* Marker only — `useRovingTabIndex` finds the palette's buttons by this class.\n Carries no styles; deleting it breaks keyboard navigation. */\n.paletteBtn {}\n\n/* Applied to ineligible tool buttons in the active mode. */\n.ineligibleBtn {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.separator {\n background: var(--wzl-border);\n}\n.palette:not(.horizontal) .separator {\n height: 1px;\n margin: 2px 0;\n}\n.palette.horizontal .separator {\n width: 1px;\n margin: 0 2px;\n}\n","import { keyGlyph } from '../Keycaps/keyGlyph';\n\nexport interface ShortcutInput {\n key: string | readonly string[];\n mod?: boolean;\n alt?: boolean;\n shift?: boolean | 'optional';\n}\n\n/** Format a shortcut as an array of display chips, one per key — modifier\n * icons (⌘, ↑, ⌥) followed by the key glyph. UIs that want a boxed\n * per-key look render each chip in its own element. `shift: 'optional'` is\n * treated as falsy. */\nexport function formatShortcutParts(b: ShortcutInput | undefined): readonly string[] | undefined {\n if (!b) return undefined;\n const rawKey = Array.isArray(b.key) ? b.key[0] : (b.key as string);\n const parts: string[] = [];\n if (b.mod) parts.push('⌘');\n if (b.shift === true) parts.push('⇧');\n if (b.alt) parts.push('⌥');\n parts.push(keyGlyph(rawKey));\n return parts;\n}\n\n/** Format a shortcut as a single display string. Order: mod, shift, alt,\n * key. Returns `undefined` for `undefined` input so callers can `??` a\n * fallback. When `key` is an array, the first element is used. `shift: 'optional'`\n * is treated as falsy. */\nexport function formatShortcut(b: ShortcutInput | undefined): string | undefined {\n const parts = formatShortcutParts(b);\n return parts ? parts.join('') : undefined;\n}\n","import { Fragment, type ReactNode } from 'react';\nimport { UnknownIcon } from '@weasel-js/core';\nimport type { AnyTool, ToolsApi } from '@weasel-js/core';\nimport { eligibleContribution, type ModeRegistry } from '@weasel-js/modes';\nimport { ToolButton } from '../ToolButton';\nimport { ToolGroup } from '../ToolGroup';\nimport { useRovingTabIndex } from '../../useRovingTabIndex';\nimport s from './ToolPalette.module.css';\nimport { formatShortcut, type ShortcutInput } from './formatShortcut';\n\nconst DEFAULT_GROUP_ORDER = ['select', 'shape', 'draw', 'type', 'view'] as const;\nconst MISC = 'misc';\n\nfunction partitionByGroup(list: AnyTool[]): Map<string, AnyTool[]> {\n const groups = new Map<string, AnyTool[]>();\n for (const tool of list) {\n const g = tool.presentation?.group ?? MISC;\n const bucket = groups.get(g) ?? [];\n bucket.push(tool);\n groups.set(g, bucket);\n }\n return groups;\n}\n\nfunction orderedGroupKeys(\n groups: Map<string, AnyTool[]>,\n groupOrder: readonly string[],\n): string[] {\n const seen = new Set(groups.keys());\n const ordered: string[] = [];\n for (const known of groupOrder) {\n if (seen.has(known)) {\n ordered.push(known);\n seen.delete(known);\n }\n }\n // Unknown groups in insertion order (Map preserves it), misc last.\n for (const key of groups.keys()) {\n if (seen.has(key) && key !== MISC) {\n ordered.push(key);\n seen.delete(key);\n }\n }\n if (seen.has(MISC)) ordered.push(MISC);\n return ordered;\n}\n\nfunction resolveIcon(tool: AnyTool): ReactNode {\n const rawIcon = tool.presentation?.icon;\n if (rawIcon == null) return <UnknownIcon />;\n if (typeof rawIcon === 'function') return rawIcon(undefined);\n return rawIcon;\n}\n\n/** Props for {@link ToolPalette}. */\nexport interface ToolPaletteProps {\n tools: ToolsApi;\n orientation?: 'vertical' | 'horizontal';\n className?: string;\n /** Derive the keyboard shortcut for a tool button from the action registry.\n * Called with the tool's id; return `undefined` to suppress a chip. When\n * omitted, falls back to `tool.keybinding` (legacy path). */\n lookupShortcut?: (toolId: string) => ShortcutInput | undefined;\n /**\n * Optional mode registry. When provided, tools whose `capabilities` do not\n * match the current mode are rendered greyed-out, aria-disabled, and their\n * onClick is suppressed. When omitted, all tools are treated as eligible\n * (preserves existing behaviour in consumers that haven't wired a registry).\n */\n modeRegistry?: ModeRegistry;\n /**\n * Presentation-group display order. Groups not listed keep their registry\n * insertion order after the listed ones; `misc` always renders last.\n * Defaults to `['select', 'shape', 'draw', 'type', 'view']`.\n */\n groupOrder?: readonly string[];\n}\n\n/**\n * Toolbar of the registered tools, split into separator-divided groups by\n * each tool's presentation group and navigable by arrow keys. Clicking a\n * button makes that tool active.\n *\n * Given a `modeRegistry`, tools whose capabilities the current mode does not\n * allow render greyed out and inert rather than disappearing.\n */\nexport function ToolPalette(props: ToolPaletteProps) {\n const {\n tools, orientation = 'vertical', className, lookupShortcut, modeRegistry,\n groupOrder = DEFAULT_GROUP_ORDER,\n } = props;\n const list = Object.values(tools.registry);\n const groups = partitionByGroup(list);\n const groupKeys = orderedGroupKeys(groups, groupOrder);\n const cls = [s.palette, orientation === 'horizontal' && s.horizontal, className]\n .filter(Boolean).join(' ');\n\n // Flat, in DOM order — the roving-tabindex hook indexes by this order.\n const ordered = groupKeys.flatMap((k) => groups.get(k)!);\n const eligibility = ordered.map(\n (tool) => (modeRegistry ? eligibleContribution(modeRegistry, tool) : true),\n );\n const activeIndex = ordered.findIndex((t) => t.id === tools.active);\n const roving = useRovingTabIndex({\n items: eligibility.map((enabled) => ({ disabled: !enabled })),\n itemClassName: s.paletteBtn,\n // An ineligible active tool can't hold the tab stop; fall back to the\n // hook's first-enabled default.\n ...(activeIndex >= 0 && eligibility[activeIndex] ? { tabStopIndex: activeIndex } : {}),\n });\n let index = -1;\n\n return (\n <div\n ref={roving.rootRef}\n className={cls}\n role=\"toolbar\"\n aria-label=\"Tools\"\n >\n {groupKeys.map((key, i) => (\n <Fragment key={key}>\n {i > 0 && (\n <div\n className={s.separator}\n role=\"separator\"\n aria-orientation={orientation === 'horizontal' ? 'vertical' : 'horizontal'}\n />\n )}\n <ToolGroup orientation={orientation} groupKey={key}>\n {groups.get(key)!.map((tool) => {\n const i = ++index;\n const label = tool.presentation?.label ?? tool.id;\n const shortcut = tool.presentation?.shortcut\n ?? formatShortcut(lookupShortcut ? lookupShortcut(tool.id) : tool.keybinding);\n const enabled = eligibility[i];\n const modeName = modeRegistry ? modeRegistry.current().id : undefined;\n const resolvedTitle = !enabled && modeName\n ? `${label}${shortcut ? ` (${shortcut})` : ''} — disabled in ${modeName} mode`\n : undefined;\n return (\n <ToolButton\n key={tool.id}\n icon={resolveIcon(tool)}\n label={label}\n shortcut={shortcut}\n active={tools.active === tool.id}\n tabbable={roving.tabIndexFor(i) === 0}\n ariaDisabled={!enabled}\n className={[s.paletteBtn, !enabled && s.ineligibleBtn].filter(Boolean).join(' ')}\n title={resolvedTitle}\n onClick={enabled ? () => tools.setActive(tool.id) : () => {}}\n onKeyDown={roving.onKeyDown(i)}\n />\n );\n })}\n </ToolGroup>\n </Fragment>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;ACaA,SAAgB,EAAoB,GAA6D;AAC/F,KAAI,CAAC,EAAG;CACR,IAAM,IAAS,MAAM,QAAQ,EAAE,IAAI,GAAG,EAAE,IAAI,KAAM,EAAE,KAC9C,IAAkB,EAAE;AAK1B,QAJI,EAAE,OAAK,EAAM,KAAK,IAAI,EACtB,EAAE,UAAU,MAAM,EAAM,KAAK,IAAI,EACjC,EAAE,OAAK,EAAM,KAAK,IAAI,EAC1B,EAAM,KAAK,EAAS,EAAO,CAAC,EACrB;;AAOT,SAAgB,EAAe,GAAkD;CAC/E,IAAM,IAAQ,EAAoB,EAAE;AACpC,QAAO,IAAQ,EAAM,KAAK,GAAG,GAAG,KAAA;;;;ACpBlC,IAAM,IAAsB;CAAC;CAAU;CAAS;CAAQ;CAAQ;CAAO,EACjE,IAAO;AAEb,SAAS,EAAiB,GAAyC;CACjE,IAAM,oBAAS,IAAI,KAAwB;AAC3C,MAAK,IAAM,KAAQ,GAAM;EACvB,IAAM,IAAI,EAAK,cAAc,SAAS,GAChC,IAAS,EAAO,IAAI,EAAE,IAAI,EAAE;AAElC,EADA,EAAO,KAAK,EAAK,EACjB,EAAO,IAAI,GAAG,EAAO;;AAEvB,QAAO;;AAGT,SAAS,EACP,GACA,GACU;CACV,IAAM,IAAO,IAAI,IAAI,EAAO,MAAM,CAAC,EAC7B,IAAoB,EAAE;AAC5B,MAAK,IAAM,KAAS,EAClB,CAAI,EAAK,IAAI,EAAM,KACjB,EAAQ,KAAK,EAAM,EACnB,EAAK,OAAO,EAAM;AAItB,MAAK,IAAM,KAAO,EAAO,MAAM,CAC7B,CAAI,EAAK,IAAI,EAAI,IAAI,MAAQ,MAC3B,EAAQ,KAAK,EAAI,EACjB,EAAK,OAAO,EAAI;AAIpB,QADI,EAAK,IAAI,EAAK,IAAE,EAAQ,KAAK,EAAK,EAC/B;;AAGT,SAAS,EAAY,GAA0B;CAC7C,IAAM,IAAU,EAAK,cAAc;AAGnC,QAFI,KAAW,OAAa,kBAAC,GAAD,EAAe,CAAA,GACvC,OAAO,KAAY,aAAmB,EAAQ,KAAA,EAAU,GACrD;;AAmCT,SAAgB,EAAY,GAAyB;CACnD,IAAM,EACJ,UAAO,iBAAc,YAAY,cAAW,mBAAgB,iBAC5D,gBAAa,MACX,GAEE,IAAS,EADF,OAAO,OAAO,EAAM,SACD,CAAK,EAC/B,IAAY,EAAiB,GAAQ,EAAW,EAChD,IAAM;EAAC,EAAE;EAAS,MAAgB,gBAAgB,EAAE;EAAY;EAAU,CAC7E,OAAO,QAAQ,CAAC,KAAK,IAAI,EAGtB,IAAU,EAAU,SAAS,MAAM,EAAO,IAAI,EAAE,CAAE,EAClD,IAAc,EAAQ,KACzB,MAAU,IAAe,EAAqB,GAAc,EAAK,GAAG,GACtE,EACK,IAAc,EAAQ,WAAW,MAAM,EAAE,OAAO,EAAM,OAAO,EAC7D,IAAS,EAAkB;EAC/B,OAAO,EAAY,KAAK,OAAa,EAAE,UAAU,CAAC,GAAS,EAAE;EAC7D,eAAe,EAAE;EAGjB,GAAI,KAAe,KAAK,EAAY,KAAe,EAAE,cAAc,GAAa,GAAG,EAAE;EACtF,CAAC,EACE,IAAQ;AAEZ,QACE,kBAAC,OAAD;EACE,KAAK,EAAO;EACZ,WAAW;EACX,MAAK;EACL,cAAW;YAEV,EAAU,KAAK,GAAK,MACnB,kBAAC,GAAD,EAAA,UAAA,CACG,IAAI,KACH,kBAAC,OAAD;GACE,WAAW,EAAE;GACb,MAAK;GACL,oBAAkB,MAAgB,eAAe,aAAa;GAC9D,CAAA,EAEJ,kBAAC,GAAD;GAAwB;GAAa,UAAU;aAC5C,EAAO,IAAI,EAAI,CAAE,KAAK,MAAS;IAC9B,IAAM,IAAI,EAAE,GACN,IAAQ,EAAK,cAAc,SAAS,EAAK,IACzC,IAAW,EAAK,cAAc,YAC/B,EAAe,IAAiB,EAAe,EAAK,GAAG,GAAG,EAAK,WAAW,EACzE,IAAU,EAAY,IACtB,IAAW,IAAe,EAAa,SAAS,CAAC,KAAK,KAAA,GACtD,IAAgB,CAAC,KAAW,IAC9B,GAAG,IAAQ,IAAW,KAAK,EAAS,KAAK,GAAG,iBAAiB,EAAS,SACtE,KAAA;AACJ,WACE,kBAAC,GAAD;KAEE,MAAM,EAAY,EAAK;KAChB;KACG;KACV,QAAQ,EAAM,WAAW,EAAK;KAC9B,UAAU,EAAO,YAAY,EAAE,KAAK;KACpC,cAAc,CAAC;KACf,WAAW,CAAC,EAAE,YAAY,CAAC,KAAW,EAAE,cAAc,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;KAChF,OAAO;KACP,SAAS,UAAgB,EAAM,UAAU,EAAK,GAAG,SAAS;KAC1D,WAAW,EAAO,UAAU,EAAE;KAC9B,EAXK,EAAK,GAWV;KAEJ;GACQ,CAAA,CACH,EAAA,EApCI,EAoCJ,CACX;EACE,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip-Clgn4trm.js","names":[],"sources":["../../src/components/Tooltip/Tooltip.module.css","../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":[".tooltip {\n /* Above Dialog overlay (1000) and ToastRegion (1100) — RAC sets no z-index. */\n z-index: 1200;\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: 12px;\n line-height: 1.3;\n padding: 4px 8px;\n max-width: 260px;\n transition: opacity var(--wzl-motion-fast);\n}\n\n.tooltip[data-entering],\n.tooltip[data-exiting] {\n opacity: 0;\n}\n\n.arrow svg {\n display: block;\n fill: var(--wzl-surface);\n stroke: var(--wzl-border);\n stroke-width: 1;\n}\n\n.arrow[data-placement='bottom'] svg {\n transform: rotate(180deg);\n}\n\n.arrow[data-placement='left'] svg {\n transform: rotate(-90deg);\n}\n\n.arrow[data-placement='right'] svg {\n transform: rotate(90deg);\n}\n","import type { ReactNode } from 'react';\nimport {\n TooltipTrigger as RACTooltipTrigger,\n Tooltip as RACTooltip,\n OverlayArrow,\n type TooltipProps as RACTooltipProps,\n type TooltipTriggerComponentProps,\n} from 'react-aria-components';\nimport s from './Tooltip.module.css';\n\nexport type TooltipTriggerProps = TooltipTriggerComponentProps;\n\n/**\n * TooltipTrigger with kit defaults: ~600 ms open delay, instant close.\n * Wrap a focusable trigger plus a `<Tooltip>`. Non-RAC triggers (plain\n * `<button>` etc.) must be wrapped in `<Focusable>` from\n * react-aria-components so hover/focus props reach the DOM node.\n *\n * Caveat: while a tooltip is open, react-aria captures the first Escape\n * keydown (document-level, capture phase) to dismiss it — app-level\n * Escape handlers (deselect, cancel gesture) see only the second press.\n */\nexport function TooltipTrigger(props: TooltipTriggerProps) {\n const { delay = 600, closeDelay = 0, ...rest } = props;\n return <RACTooltipTrigger delay={delay} closeDelay={closeDelay} {...rest} />;\n}\n\nexport type TooltipProps = Omit<RACTooltipProps, 'children' | 'className'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * Tooltip bubble with an arrow pointing at the trigger. Non-interactive\n * content only (ARIA tooltip semantics). Use inside `<TooltipTrigger>`.\n */\nexport function Tooltip(props: TooltipProps) {\n const { children, className, placement = 'top', offset = 8, ...rest } = props;\n return (\n <RACTooltip\n {...rest}\n placement={placement}\n offset={offset}\n className={[s.tooltip, className].filter(Boolean).join(' ')}\n >\n <OverlayArrow className={s.arrow}>\n <svg width={8} height={8} viewBox=\"0 0 8 8\" aria-hidden=\"true\">\n <path d=\"M0 0 L4 4 L8 0\" />\n </svg>\n </OverlayArrow>\n {children}\n </RACTooltip>\n );\n}\n"],"mappings":";;;;;;;;ACsBA,SAAgB,EAAe,GAA4B;CACzD,IAAM,EAAE,WAAQ,KAAK,gBAAa,GAAG,GAAG,MAAS;AACjD,QAAO,kBAAC,GAAD;EAA0B;EAAmB;EAAY,GAAI;EAAQ,CAAA;;AAY9E,SAAgB,EAAQ,GAAqB;CAC3C,IAAM,EAAE,aAAU,cAAW,eAAY,OAAO,YAAS,GAAG,GAAG,MAAS;AACxE,QACE,kBAAC,GAAD;EACE,GAAI;EACO;EACH;EACR,WAAW,CAAC,EAAE,SAAS,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAJ7D,CAME,kBAAC,GAAD;GAAc,WAAW,EAAE;aACzB,kBAAC,OAAD;IAAK,OAAO;IAAG,QAAQ;IAAG,SAAQ;IAAU,eAAY;cACtD,kBAAC,QAAD,EAAM,GAAE,kBAAmB,CAAA;IACvB,CAAA;GACO,CAAA,EACd,EACU"}
1
+ {"version":3,"file":"Tooltip-Clgn4trm.js","names":[],"sources":["../../src/components/Tooltip/Tooltip.module.css","../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":[".tooltip {\n /* Above Dialog overlay (1000) and ToastRegion (1100) — RAC sets no z-index. */\n z-index: 1200;\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: 12px;\n line-height: 1.3;\n padding: 4px 8px;\n max-width: 260px;\n transition: opacity var(--wzl-motion-fast);\n}\n\n.tooltip[data-entering],\n.tooltip[data-exiting] {\n opacity: 0;\n}\n\n.arrow svg {\n display: block;\n fill: var(--wzl-surface);\n stroke: var(--wzl-border);\n stroke-width: 1;\n}\n\n.arrow[data-placement='bottom'] svg {\n transform: rotate(180deg);\n}\n\n.arrow[data-placement='left'] svg {\n transform: rotate(-90deg);\n}\n\n.arrow[data-placement='right'] svg {\n transform: rotate(90deg);\n}\n","import type { ReactNode } from 'react';\nimport {\n TooltipTrigger as RACTooltipTrigger,\n Tooltip as RACTooltip,\n OverlayArrow,\n type TooltipProps as RACTooltipProps,\n type TooltipTriggerComponentProps,\n} from 'react-aria-components';\nimport s from './Tooltip.module.css';\n\n/**\n * Props for {@link TooltipTrigger} — React Aria's `TooltipTrigger` props\n * unchanged.\n */\nexport type TooltipTriggerProps = TooltipTriggerComponentProps;\n\n/**\n * TooltipTrigger with kit defaults: ~600 ms open delay, instant close.\n * Wrap a focusable trigger plus a `<Tooltip>`. Non-RAC triggers (plain\n * `<button>` etc.) must be wrapped in `<Focusable>` from\n * react-aria-components so hover/focus props reach the DOM node.\n *\n * Caveat: while a tooltip is open, react-aria captures the first Escape\n * keydown (document-level, capture phase) to dismiss it — app-level\n * Escape handlers (deselect, cancel gesture) see only the second press.\n */\nexport function TooltipTrigger(props: TooltipTriggerProps) {\n const { delay = 600, closeDelay = 0, ...rest } = props;\n return <RACTooltipTrigger delay={delay} closeDelay={closeDelay} {...rest} />;\n}\n\n/** Props for {@link Tooltip}, on top of React Aria's `Tooltip` props. */\nexport type TooltipProps = Omit<RACTooltipProps, 'children' | 'className'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * Tooltip bubble with an arrow pointing at the trigger. Non-interactive\n * content only (ARIA tooltip semantics). Use inside `<TooltipTrigger>`.\n */\nexport function Tooltip(props: TooltipProps) {\n const { children, className, placement = 'top', offset = 8, ...rest } = props;\n return (\n <RACTooltip\n {...rest}\n placement={placement}\n offset={offset}\n className={[s.tooltip, className].filter(Boolean).join(' ')}\n >\n <OverlayArrow className={s.arrow}>\n <svg width={8} height={8} viewBox=\"0 0 8 8\" aria-hidden=\"true\">\n <path d=\"M0 0 L4 4 L8 0\" />\n </svg>\n </OverlayArrow>\n {children}\n </RACTooltip>\n );\n}\n"],"mappings":";;;;;;;;AC0BA,SAAgB,EAAe,GAA4B;CACzD,IAAM,EAAE,WAAQ,KAAK,gBAAa,GAAG,GAAG,MAAS;AACjD,QAAO,kBAAC,GAAD;EAA0B;EAAmB;EAAY,GAAI;EAAQ,CAAA;;AAa9E,SAAgB,EAAQ,GAAqB;CAC3C,IAAM,EAAE,aAAU,cAAW,eAAY,OAAO,YAAS,GAAG,GAAG,MAAS;AACxE,QACE,kBAAC,GAAD;EACE,GAAI;EACO;EACH;EACR,WAAW,CAAC,EAAE,SAAS,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAJ7D,CAME,kBAAC,GAAD;GAAc,WAAW,EAAE;aACzB,kBAAC,OAAD;IAAK,OAAO;IAAG,QAAQ;IAAG,SAAQ;IAAU,eAAY;cACtD,kBAAC,QAAD,EAAM,GAAE,kBAAmB,CAAA;IACvB,CAAA;GACO,CAAA,EACd,EACU"}
@@ -0,0 +1,11 @@
1
+ var e = {
2
+ root: "_root_1hmjs_10",
3
+ segment: "_segment_1hmjs_32",
4
+ segmentSelected: "_segmentSelected_1hmjs_79",
5
+ size_sm: "_size_sm_1hmjs_137",
6
+ variant_minimal: "_variant_minimal_1hmjs_150"
7
+ };
8
+ //#endregion
9
+ export { e as t };
10
+
11
+ //# sourceMappingURL=segmentedControl.module-DCyuDbj1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segmentedControl.module-DCyuDbj1.js","names":[],"sources":["../../src/components/segmentedControl.module.css"],"sourcesContent":["/* Shared by ActionsBar and OptionsBar, which look identical and differ only\n in what a segment does. Lives here rather than in either component so\n neither owns the other's styling.\n\n Aqua-glass segmented control. The track is a pill-shaped well; the\n selected segment becomes a tinted glass cylinder that reads as the same\n \"lozenge\" family as the standalone Button. Visual params come from the\n `--btn-*` custom properties (see Button.module.css) so the Button Lab\n can tune both surfaces with one set of sliders. */\n.root {\n display: inline-flex;\n height: var(--wzl-tb-height, 28px);\n padding: 2px;\n background: linear-gradient(\n 180deg,\n rgba(0, 0, 0, 0.18) 0%,\n rgba(0, 0, 0, 0.05) 100%\n );\n border-radius: 999px;\n /* Clip segments to the track outline so interior corners read crisp and\n the selected segment's glass treatment doesn't paint over the rounded\n outer pixels. */\n overflow: hidden;\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.4),\n 0 1px 0 rgba(255, 255, 255, 0.06);\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n}\n\n.segment {\n position: relative;\n flex: 1 1 0;\n min-width: 0;\n padding: 0 12px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n margin: 0;\n font-family: var(--wzl-font-ui);\n font-weight: 300;\n font-size: var(--btn-fontsize, 14px);\n line-height: 1;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n /* Only round the outer corners of the bar — middle segments stay square\n so the seams between them don't render as facing parentheses. */\n border-radius: 0;\n isolation: isolate;\n overflow: hidden;\n transition: color var(--wzl-motion-fast);\n}\n\n.segment:first-child {\n border-radius: 999px 0 0 999px;\n}\n\n.segment:last-child {\n border-radius: 0 999px 999px 0;\n}\n\n.segment:only-child {\n border-radius: 999px;\n}\n\n.segment:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -2px;\n}\n\n.segment:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.segment:hover:not(:disabled):not(.segmentSelected) {\n color: var(--wzl-fg);\n}\n\n/* Selected segment — full Aqua glass cylinder. Mirrors Button.variant_primary. */\n.segmentSelected {\n background: linear-gradient(\n 180deg,\n color-mix(in srgb, var(--wzl-accent) var(--btn-top-alpha, 45%), transparent) 0%,\n color-mix(in srgb, var(--wzl-accent) var(--btn-upper-alpha, 70%), transparent) calc(var(--btn-equator, 52%) - var(--btn-equator-spread, 12%)),\n color-mix(in srgb, var(--wzl-accent) var(--btn-equator-tint, 95%), white) var(--btn-equator, 52%),\n var(--wzl-accent) calc(var(--btn-equator, 52%) + var(--btn-equator-spread, 12%) * 1.6),\n color-mix(in srgb, var(--wzl-accent) var(--btn-base-tint, 70%), black) 100%\n );\n backdrop-filter: blur(var(--btn-blur, 6px)) saturate(var(--btn-saturate, 1.3));\n -webkit-backdrop-filter: blur(var(--btn-blur, 6px)) saturate(var(--btn-saturate, 1.3));\n color: var(--wzl-fg-on-accent);\n text-shadow:\n 0 1px 0 rgba(0, 0, 0, var(--btn-text-shadow, 0.35)),\n var(--btn-text-inset-x, 0px) var(--btn-text-inset-y, 1px) 0 rgba(0, 0, 0, var(--btn-text-inset, 0.55)),\n 0 0 6px rgba(0, 0, 0, calc(var(--btn-text-shadow, 0.35) * 0.5));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, var(--btn-inset-hi, 0.42)),\n inset 0 -1px 2px rgba(0, 0, 0, var(--btn-inset-lo, 0.3)),\n 0 0 0 1px rgba(0, 0, 0, var(--btn-rim, 0.22)),\n 0 1px 2px rgba(0, 0, 0, var(--btn-drop-near, 0.3)),\n 0 2px 4px rgba(0, 0, 0, calc(var(--btn-drop-far, 0.18) * 0.8));\n}\n\n/* Specular gloss on the selected segment. Top corners inherit the\n segment's radius so they hug the upper edge cleanly; bottom corners\n are flattened so the gloss ends in a straight line at the equator\n instead of two little curves that read as `)(` brackets. */\n.segmentSelected::before {\n content: '';\n position: absolute;\n inset: 1px 1px var(--btn-gloss-bottom, 50%) 1px;\n border-radius: inherit;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background: linear-gradient(\n 180deg,\n rgba(255, 255, 255, var(--btn-gloss-top, 0.42)) 0%,\n rgba(255, 255, 255, var(--btn-gloss-mid, 0.12)) 60%,\n rgba(255, 255, 255, 0) 100%\n );\n pointer-events: none;\n z-index: 1;\n}\n\n.segmentSelected > * {\n position: relative;\n z-index: 2;\n}\n\n/* Small variant — ~60% of the default height, with proportionally reduced\n padding and font. Sized for dense surfaces (lab control panels, inline\n filters, etc.). */\n.size_sm {\n height: 17px;\n padding: 1px;\n}\n\n.size_sm .segment {\n padding: 0 8px;\n font-size: 11px;\n}\n\n/* Minimal variant — strips the pill track, glass selected state, inset\n shadows, and outer padding. Used in dense diagnostic surfaces (dispatch\n trace, lab inspectors) where the bar must read as plain text controls. */\n.variant_minimal {\n height: auto;\n padding: 0;\n background: none;\n border-radius: 0;\n box-shadow: none;\n gap: 2px;\n}\n\n.variant_minimal .segment {\n padding: 0 4px;\n font-size: 11px;\n font-weight: 400;\n line-height: 1.4;\n border-radius: 2px;\n color: var(--wzl-fg-muted);\n transition: none;\n}\n\n.variant_minimal .segment:first-child,\n.variant_minimal .segment:last-child,\n.variant_minimal .segment:only-child {\n border-radius: 2px;\n}\n\n.variant_minimal .segment:hover:not(:disabled):not(.segmentSelected) {\n color: var(--wzl-fg);\n background: rgba(255, 255, 255, 0.06);\n}\n\n.variant_minimal .segmentSelected {\n background: none;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n color: var(--wzl-accent-fg, var(--wzl-accent));\n text-shadow: none;\n box-shadow: none;\n font-weight: 500;\n}\n\n.variant_minimal .segmentSelected::before {\n content: none;\n}\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"useRovingTabIndex-DBZtq4IH.js","names":[],"sources":["../../src/useRovingTabIndex.ts"],"sourcesContent":["import { useRef, type KeyboardEvent as ReactKeyboardEvent, type RefObject } from 'react';\n\n/** The only thing the hook needs to know about a bar's items: which are\n * skipped by arrow navigation. */\nexport type RovingItem = { disabled?: boolean };\n\nexport type UseRovingTabIndexOptions = {\n /** In the same order as the elements carrying `itemClassName`. */\n items: readonly RovingItem[];\n /** Class on the focusable element of each item, inside the container the\n * returned `rootRef` is attached to. Their DOM order must match `items`. */\n itemClassName: string;\n /** Index that carries `tabIndex=0`. Defaults to the first enabled item; a\n * radiogroup-style bar passes its selected index so Tab lands on the\n * current value. */\n tabStopIndex?: number;\n /** Called with the new index as arrow/Home/End focus moves, before focus\n * moves. This is where a selection-follows-focus bar commits the value. */\n onNavigate?: (index: number) => void;\n /** Space/Enter on the item at `index`. When omitted the keypress is left\n * alone, which on a native `<button>` produces an ordinary click. */\n onActivate?: (index: number) => void;\n};\n\nexport type RovingTabIndex<T extends HTMLElement = HTMLDivElement> = {\n /** Attach to the container element that holds the items. */\n rootRef: RefObject<T | null>;\n /** `tabIndex` value for the item at `index`. */\n tabIndexFor: (index: number) => 0 | -1;\n /** `onKeyDown` for the item at `index`. */\n onKeyDown: (index: number) => (e: ReactKeyboardEvent<HTMLElement>) => void;\n};\n\nfunction firstEnabledIndex(items: readonly RovingItem[]): number {\n for (let i = 0; i < items.length; i++) if (!items[i].disabled) return i;\n return -1;\n}\n\nfunction lastEnabledIndex(items: readonly RovingItem[]): number {\n for (let i = items.length - 1; i >= 0; i--) if (!items[i].disabled) return i;\n return -1;\n}\n\nfunction nextEnabledIndex(items: readonly RovingItem[], from: number): number {\n const n = items.length;\n for (let k = 1; k <= n; k++) {\n const i = (from + k + n) % n;\n if (!items[i].disabled) return i;\n }\n return from;\n}\n\nfunction prevEnabledIndex(items: readonly RovingItem[], from: number): number {\n const n = items.length;\n for (let k = 1; k <= n; k++) {\n const i = (from - k + n) % n;\n if (!items[i].disabled) return i;\n }\n return from;\n}\n\n/**\n * Roving tabindex for a bar of items: one item is in the tab order, and the\n * arrow keys move focus among the rest, skipping disabled ones and wrapping\n * at both ends. Home and End go to the first and last enabled item. Both axes\n * navigate, so the same bar works laid out either way.\n *\n * Backs `ActionsBar`, `OptionsBar`, and `ToggleBar`.\n *\n * **When a bar should not use this.** Only when the bar owns its items. A\n * container of arbitrary compound controls — a number field, a select, a\n * color field — must leave the arrow keys alone, because those controls edit\n * their own value with them; taking the arrows over would break the control\n * to navigate between controls. `ToolOptionsBar` is that case and keeps plain\n * DOM tab order. A bar whose items are all simple buttons is the case this\n * hook is for.\n *\n * With every item disabled there is no tab stop and the bar drops out of the\n * tab order entirely.\n */\nexport function useRovingTabIndex<T extends HTMLElement = HTMLDivElement>(\n options: UseRovingTabIndexOptions,\n): RovingTabIndex<T> {\n const { items, itemClassName, onNavigate, onActivate } = options;\n const rootRef = useRef<T | null>(null);\n\n const tabStopIndex = options.tabStopIndex ?? firstEnabledIndex(items);\n\n const focusItem = (index: number): void => {\n const root = rootRef.current;\n if (!root) return;\n const elements = root.querySelectorAll<HTMLElement>(`.${itemClassName}`);\n elements[index]?.focus();\n };\n\n const onKeyDown = (index: number) => (e: ReactKeyboardEvent<HTMLElement>): void => {\n let nextIndex = -1;\n switch (e.key) {\n case 'ArrowLeft':\n case 'ArrowUp':\n nextIndex = prevEnabledIndex(items, index);\n break;\n case 'ArrowRight':\n case 'ArrowDown':\n nextIndex = nextEnabledIndex(items, index);\n break;\n case 'Home':\n nextIndex = firstEnabledIndex(items);\n break;\n case 'End':\n nextIndex = lastEnabledIndex(items);\n break;\n case ' ':\n case 'Enter':\n if (onActivate) {\n e.preventDefault();\n onActivate(index);\n }\n return;\n default:\n return;\n }\n if (nextIndex < 0 || nextIndex === index) return;\n e.preventDefault();\n onNavigate?.(nextIndex);\n focusItem(nextIndex);\n };\n\n return {\n rootRef,\n tabIndexFor: (index: number) => (index === tabStopIndex ? 0 : -1),\n onKeyDown,\n };\n}\n"],"mappings":";;AAiCA,SAAS,EAAkB,GAAsC;AAC/D,MAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,IAAK,KAAI,CAAC,EAAM,GAAG,SAAU,QAAO;AACtE,QAAO;;AAGT,SAAS,EAAiB,GAAsC;AAC9D,MAAK,IAAI,IAAI,EAAM,SAAS,GAAG,KAAK,GAAG,IAAK,KAAI,CAAC,EAAM,GAAG,SAAU,QAAO;AAC3E,QAAO;;AAGT,SAAS,EAAiB,GAA8B,GAAsB;CAC5E,IAAM,IAAI,EAAM;AAChB,MAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;EAC3B,IAAM,KAAK,IAAO,IAAI,KAAK;AAC3B,MAAI,CAAC,EAAM,GAAG,SAAU,QAAO;;AAEjC,QAAO;;AAGT,SAAS,EAAiB,GAA8B,GAAsB;CAC5E,IAAM,IAAI,EAAM;AAChB,MAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;EAC3B,IAAM,KAAK,IAAO,IAAI,KAAK;AAC3B,MAAI,CAAC,EAAM,GAAG,SAAU,QAAO;;AAEjC,QAAO;;AAsBT,SAAgB,EACd,GACmB;CACnB,IAAM,EAAE,UAAO,kBAAe,eAAY,kBAAe,GACnD,IAAU,EAAiB,KAAK,EAEhC,IAAe,EAAQ,gBAAgB,EAAkB,EAAM,EAE/D,KAAa,MAAwB;EACzC,IAAM,IAAO,EAAQ;AAChB,OACY,EAAK,iBAA8B,IAAI,IACxD,CAAS,IAAQ,OAAO;;AAoC1B,QAAO;EACL;EACA,cAAc,MAAmB,MAAU,IAAe,IAAI;EAC9D,YApCiB,OAAmB,MAA6C;GACjF,IAAI,IAAY;AAChB,WAAQ,EAAE,KAAV;IACE,KAAK;IACL,KAAK;AACH,SAAY,EAAiB,GAAO,EAAM;AAC1C;IACF,KAAK;IACL,KAAK;AACH,SAAY,EAAiB,GAAO,EAAM;AAC1C;IACF,KAAK;AACH,SAAY,EAAkB,EAAM;AACpC;IACF,KAAK;AACH,SAAY,EAAiB,EAAM;AACnC;IACF,KAAK;IACL,KAAK;AACH,KAAI,MACF,EAAE,gBAAgB,EAClB,EAAW,EAAM;AAEnB;IACF,QACE;;AAEA,OAAY,KAAK,MAAc,MACnC,EAAE,gBAAgB,EAClB,IAAa,EAAU,EACvB,EAAU,EAAU;;EAOrB"}
1
+ {"version":3,"file":"useRovingTabIndex-DBZtq4IH.js","names":[],"sources":["../../src/useRovingTabIndex.ts"],"sourcesContent":["import { useRef, type KeyboardEvent as ReactKeyboardEvent, type RefObject } from 'react';\n\n/** The only thing the hook needs to know about a bar's items: which are\n * skipped by arrow navigation. */\nexport type RovingItem = { disabled?: boolean };\n\n/** Options for {@link useRovingTabIndex}. */\nexport type UseRovingTabIndexOptions = {\n /** In the same order as the elements carrying `itemClassName`. */\n items: readonly RovingItem[];\n /** Class on the focusable element of each item, inside the container the\n * returned `rootRef` is attached to. Their DOM order must match `items`. */\n itemClassName: string;\n /** Index that carries `tabIndex=0`. Defaults to the first enabled item; a\n * radiogroup-style bar passes its selected index so Tab lands on the\n * current value. */\n tabStopIndex?: number;\n /** Called with the new index as arrow/Home/End focus moves, before focus\n * moves. This is where a selection-follows-focus bar commits the value. */\n onNavigate?: (index: number) => void;\n /** Space/Enter on the item at `index`. When omitted the keypress is left\n * alone, which on a native `<button>` produces an ordinary click. */\n onActivate?: (index: number) => void;\n};\n\n/**\n * What {@link useRovingTabIndex} returns: a container ref plus the per-item\n * `tabIndex` and `onKeyDown` values to spread onto each item.\n */\nexport type RovingTabIndex<T extends HTMLElement = HTMLDivElement> = {\n /** Attach to the container element that holds the items. */\n rootRef: RefObject<T | null>;\n /** `tabIndex` value for the item at `index`. */\n tabIndexFor: (index: number) => 0 | -1;\n /** `onKeyDown` for the item at `index`. */\n onKeyDown: (index: number) => (e: ReactKeyboardEvent<HTMLElement>) => void;\n};\n\nfunction firstEnabledIndex(items: readonly RovingItem[]): number {\n for (let i = 0; i < items.length; i++) if (!items[i].disabled) return i;\n return -1;\n}\n\nfunction lastEnabledIndex(items: readonly RovingItem[]): number {\n for (let i = items.length - 1; i >= 0; i--) if (!items[i].disabled) return i;\n return -1;\n}\n\nfunction nextEnabledIndex(items: readonly RovingItem[], from: number): number {\n const n = items.length;\n for (let k = 1; k <= n; k++) {\n const i = (from + k + n) % n;\n if (!items[i].disabled) return i;\n }\n return from;\n}\n\nfunction prevEnabledIndex(items: readonly RovingItem[], from: number): number {\n const n = items.length;\n for (let k = 1; k <= n; k++) {\n const i = (from - k + n) % n;\n if (!items[i].disabled) return i;\n }\n return from;\n}\n\n/**\n * Roving tabindex for a bar of items: one item is in the tab order, and the\n * arrow keys move focus among the rest, skipping disabled ones and wrapping\n * at both ends. Home and End go to the first and last enabled item. Both axes\n * navigate, so the same bar works laid out either way.\n *\n * Backs `ActionsBar`, `OptionsBar`, and `ToggleBar`.\n *\n * **When a bar should not use this.** Only when the bar owns its items. A\n * container of arbitrary compound controls — a number field, a select, a\n * color field — must leave the arrow keys alone, because those controls edit\n * their own value with them; taking the arrows over would break the control\n * to navigate between controls. `ToolOptionsBar` is that case and keeps plain\n * DOM tab order. A bar whose items are all simple buttons is the case this\n * hook is for.\n *\n * With every item disabled there is no tab stop and the bar drops out of the\n * tab order entirely.\n */\nexport function useRovingTabIndex<T extends HTMLElement = HTMLDivElement>(\n options: UseRovingTabIndexOptions,\n): RovingTabIndex<T> {\n const { items, itemClassName, onNavigate, onActivate } = options;\n const rootRef = useRef<T | null>(null);\n\n const tabStopIndex = options.tabStopIndex ?? firstEnabledIndex(items);\n\n const focusItem = (index: number): void => {\n const root = rootRef.current;\n if (!root) return;\n const elements = root.querySelectorAll<HTMLElement>(`.${itemClassName}`);\n elements[index]?.focus();\n };\n\n const onKeyDown = (index: number) => (e: ReactKeyboardEvent<HTMLElement>): void => {\n let nextIndex = -1;\n switch (e.key) {\n case 'ArrowLeft':\n case 'ArrowUp':\n nextIndex = prevEnabledIndex(items, index);\n break;\n case 'ArrowRight':\n case 'ArrowDown':\n nextIndex = nextEnabledIndex(items, index);\n break;\n case 'Home':\n nextIndex = firstEnabledIndex(items);\n break;\n case 'End':\n nextIndex = lastEnabledIndex(items);\n break;\n case ' ':\n case 'Enter':\n if (onActivate) {\n e.preventDefault();\n onActivate(index);\n }\n return;\n default:\n return;\n }\n if (nextIndex < 0 || nextIndex === index) return;\n e.preventDefault();\n onNavigate?.(nextIndex);\n focusItem(nextIndex);\n };\n\n return {\n rootRef,\n tabIndexFor: (index: number) => (index === tabStopIndex ? 0 : -1),\n onKeyDown,\n };\n}\n"],"mappings":";;AAsCA,SAAS,EAAkB,GAAsC;AAC/D,MAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,IAAK,KAAI,CAAC,EAAM,GAAG,SAAU,QAAO;AACtE,QAAO;;AAGT,SAAS,EAAiB,GAAsC;AAC9D,MAAK,IAAI,IAAI,EAAM,SAAS,GAAG,KAAK,GAAG,IAAK,KAAI,CAAC,EAAM,GAAG,SAAU,QAAO;AAC3E,QAAO;;AAGT,SAAS,EAAiB,GAA8B,GAAsB;CAC5E,IAAM,IAAI,EAAM;AAChB,MAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;EAC3B,IAAM,KAAK,IAAO,IAAI,KAAK;AAC3B,MAAI,CAAC,EAAM,GAAG,SAAU,QAAO;;AAEjC,QAAO;;AAGT,SAAS,EAAiB,GAA8B,GAAsB;CAC5E,IAAM,IAAI,EAAM;AAChB,MAAK,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;EAC3B,IAAM,KAAK,IAAO,IAAI,KAAK;AAC3B,MAAI,CAAC,EAAM,GAAG,SAAU,QAAO;;AAEjC,QAAO;;AAsBT,SAAgB,EACd,GACmB;CACnB,IAAM,EAAE,UAAO,kBAAe,eAAY,kBAAe,GACnD,IAAU,EAAiB,KAAK,EAEhC,IAAe,EAAQ,gBAAgB,EAAkB,EAAM,EAE/D,KAAa,MAAwB;EACzC,IAAM,IAAO,EAAQ;AAChB,OACY,EAAK,iBAA8B,IAAI,IACxD,CAAS,IAAQ,OAAO;;AAoC1B,QAAO;EACL;EACA,cAAc,MAAmB,MAAU,IAAe,IAAI;EAC9D,YApCiB,OAAmB,MAA6C;GACjF,IAAI,IAAY;AAChB,WAAQ,EAAE,KAAV;IACE,KAAK;IACL,KAAK;AACH,SAAY,EAAiB,GAAO,EAAM;AAC1C;IACF,KAAK;IACL,KAAK;AACH,SAAY,EAAiB,GAAO,EAAM;AAC1C;IACF,KAAK;AACH,SAAY,EAAkB,EAAM;AACpC;IACF,KAAK;AACH,SAAY,EAAiB,EAAM;AACnC;IACF,KAAK;IACL,KAAK;AACH,KAAI,MACF,EAAE,gBAAgB,EAClB,EAAW,EAAM;AAEnB;IACF,QACE;;AAEA,OAAY,KAAK,MAAc,MACnC,EAAE,gBAAgB,EAClB,IAAa,EAAU,EACvB,EAAU,EAAU;;EAOrB"}
@@ -1,3 +1,9 @@
1
+ /**
2
+ * A chroma envelope over lightness, as three linear segments: flat at `cBot`
3
+ * below `lRange[0]`, rising to `cPeak` at `midL`, falling to `cTop` at
4
+ * `lRange[1]`, flat beyond. Keeps a color ramp inside gamut at the light and
5
+ * dark ends while staying saturated through the middle.
6
+ */
1
7
  export type ChromaCurve = {
2
8
  lRange: [number, number];
3
9
  midL: number;
@@ -5,10 +11,16 @@ export type ChromaCurve = {
5
11
  cPeak: number;
6
12
  cTop: number;
7
13
  };
14
+ /** A single lightness/chroma sample. */
8
15
  export type ChromaCurvePoint = {
9
16
  L: number;
10
17
  C: number;
11
18
  };
19
+ /**
20
+ * Converts an OKLCH color to a gamut-clipped `#rrggbb` string. Hue is in
21
+ * degrees, unlike the kit's OKLab helpers which take radians.
22
+ */
12
23
  export declare function oklchToHex(L: number, C: number, Hdeg: number): string;
24
+ /** Evaluates a {@link ChromaCurve} at a lightness, clamping outside its range. */
13
25
  export declare function chromaAt(L: number, curve: ChromaCurve): number;
14
26
  //# sourceMappingURL=oklch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"oklch.d.ts","sourceRoot":"","sources":["../../src/color/oklch.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAM9D"}
1
+ {"version":3,"file":"oklch.d.ts","sourceRoot":"","sources":["../../src/color/oklch.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wCAAwC;AACxC,MAAM,MAAM,gBAAgB,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAM9D"}