@weasel-js/ui 1.4.2 → 1.4.4

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 (218) hide show
  1. package/dist/chunks/{ActionBar-D4N1o4LB.js → ActionBar-DxGp3WuZ.js} +2 -2
  2. package/dist/chunks/{ActionBar-D4N1o4LB.js.map → ActionBar-DxGp3WuZ.js.map} +1 -1
  3. package/dist/chunks/{ActionsBar-C2oXIJk-.js → ActionsBar-D1VFreoj.js} +3 -3
  4. package/dist/chunks/{ActionsBar-C2oXIJk-.js.map → ActionsBar-D1VFreoj.js.map} +1 -1
  5. package/dist/chunks/{Badge-7yQseqZb.js → Badge-Bf8Rd1R6.js} +1 -1
  6. package/dist/chunks/{Badge-7yQseqZb.js.map → Badge-Bf8Rd1R6.js.map} +1 -1
  7. package/dist/chunks/{BandEditor-CqiMlSF9.js → BandEditor-CUlbOlJi.js} +1 -1
  8. package/dist/chunks/{BandEditor-CqiMlSF9.js.map → BandEditor-CUlbOlJi.js.map} +1 -1
  9. package/dist/chunks/Button-CExdkQcU.js +90 -0
  10. package/dist/chunks/{Button-0OdcoOMx.js.map → Button-CExdkQcU.js.map} +1 -1
  11. package/dist/chunks/Callout-Dvt6lqVn.js +122 -0
  12. package/dist/chunks/Callout-Dvt6lqVn.js.map +1 -0
  13. package/dist/chunks/{Checkbox-CkcngYWa.js → Checkbox-D-pVpobU.js} +1 -1
  14. package/dist/chunks/{Checkbox-CkcngYWa.js.map → Checkbox-D-pVpobU.js.map} +1 -1
  15. package/dist/chunks/{ColorField-CCBSv8dp.js → ColorField-C20aRvCg.js} +2 -2
  16. package/dist/chunks/{ColorField-CCBSv8dp.js.map → ColorField-C20aRvCg.js.map} +1 -1
  17. package/dist/chunks/ComboBox-BBRRWvdc.js +107 -0
  18. package/dist/chunks/ComboBox-BBRRWvdc.js.map +1 -0
  19. package/dist/chunks/{CurveEditor-BNbP2wgq.js → CurveEditor-DtjdpJVB.js} +2 -2
  20. package/dist/chunks/{CurveEditor-BNbP2wgq.js.map → CurveEditor-DtjdpJVB.js.map} +1 -1
  21. package/dist/chunks/{DataGrid-CiJGbgok.js → DataGrid-BVdDMZkQ.js} +2 -2
  22. package/dist/chunks/{DataGrid-CiJGbgok.js.map → DataGrid-BVdDMZkQ.js.map} +1 -1
  23. package/dist/chunks/{DetentSlider-C6RTsiPi.js → DetentSlider-BpD7bgjN.js} +2 -2
  24. package/dist/chunks/{DetentSlider-C6RTsiPi.js.map → DetentSlider-BpD7bgjN.js.map} +1 -1
  25. package/dist/chunks/Dialog-CYBLjwbd.js +61 -0
  26. package/dist/chunks/Dialog-CYBLjwbd.js.map +1 -0
  27. package/dist/chunks/{Disclosure-aSX2CZlw.js → Disclosure-DiA6UKdM.js} +1 -1
  28. package/dist/chunks/{Disclosure-aSX2CZlw.js.map → Disclosure-DiA6UKdM.js.map} +1 -1
  29. package/dist/chunks/{DragHandleGlyph-C6GJyfnm.js → DragHandleGlyph-CppAz4_8.js} +1 -1
  30. package/dist/chunks/{DragHandleGlyph-C6GJyfnm.js.map → DragHandleGlyph-CppAz4_8.js.map} +1 -1
  31. package/dist/chunks/{Field-BUroh92b.js → Field-dzlxOGxT.js} +1 -1
  32. package/dist/chunks/{Field-BUroh92b.js.map → Field-dzlxOGxT.js.map} +1 -1
  33. package/dist/chunks/{FontFamilySelect-xjOHQMsK.js → FontFamilySelect-DIJ4phXz.js} +2 -2
  34. package/dist/chunks/{FontFamilySelect-xjOHQMsK.js.map → FontFamilySelect-DIJ4phXz.js.map} +1 -1
  35. package/dist/chunks/{GradientEditor-D1IVEkHO.js → GradientEditor-BLpAtMDK.js} +4 -4
  36. package/dist/chunks/{GradientEditor-D1IVEkHO.js.map → GradientEditor-BLpAtMDK.js.map} +1 -1
  37. package/dist/chunks/{InlineRange-DG2dvtb4.js → InlineRange-CcfzY80y.js} +2 -2
  38. package/dist/chunks/{InlineRange-DG2dvtb4.js.map → InlineRange-CcfzY80y.js.map} +1 -1
  39. package/dist/chunks/{Input-CxMANia3.js → Input-CoKOtyD0.js} +2 -2
  40. package/dist/chunks/{Input-CxMANia3.js.map → Input-CoKOtyD0.js.map} +1 -1
  41. package/dist/chunks/{ItemList-BcdHAah3.js → ItemList-BZJQReXD.js} +1 -1
  42. package/dist/chunks/{ItemList-BcdHAah3.js.map → ItemList-BZJQReXD.js.map} +1 -1
  43. package/dist/chunks/{Keycaps-D-UGWDrt.js → Keycaps-DSeq6QWH.js} +1 -1
  44. package/dist/chunks/{Keycaps-D-UGWDrt.js.map → Keycaps-DSeq6QWH.js.map} +1 -1
  45. package/dist/chunks/{LayerStack-DQnWSOVC.js → LayerStack-DOylrQcm.js} +3 -3
  46. package/dist/chunks/{LayerStack-DQnWSOVC.js.map → LayerStack-DOylrQcm.js.map} +1 -1
  47. package/dist/chunks/{NumberField-DLw4UmF3.js → NumberField-DBowmOfe.js} +2 -2
  48. package/dist/chunks/{NumberField-DLw4UmF3.js.map → NumberField-DBowmOfe.js.map} +1 -1
  49. package/dist/chunks/{OptionsBar-BXyk_5gT.js → OptionsBar-D_Cd3ZYs.js} +3 -3
  50. package/dist/chunks/{OptionsBar-BXyk_5gT.js.map → OptionsBar-D_Cd3ZYs.js.map} +1 -1
  51. package/dist/chunks/{PaintInput-DCgWtKxw.js → PaintInput-B7AScmHN.js} +5 -5
  52. package/dist/chunks/{PaintInput-DCgWtKxw.js.map → PaintInput-B7AScmHN.js.map} +1 -1
  53. package/dist/chunks/{PatternPicker-CPFEhpXD.js → PatternPicker-Ba22Bf4L.js} +2 -2
  54. package/dist/chunks/{PatternPicker-CPFEhpXD.js.map → PatternPicker-Ba22Bf4L.js.map} +1 -1
  55. package/dist/chunks/{Plot2D-BtI_c25q.js → Plot2D-C97pO13t.js} +1 -1
  56. package/dist/chunks/{Plot2D-BtI_c25q.js.map → Plot2D-C97pO13t.js.map} +1 -1
  57. package/dist/chunks/{PointPlotter-B-DvXjkL.js → PointPlotter-CbDKVggs.js} +2 -2
  58. package/dist/chunks/{PointPlotter-B-DvXjkL.js.map → PointPlotter-CbDKVggs.js.map} +1 -1
  59. package/dist/chunks/{Powerline-CJYCbqSc.js → Powerline-BaHDJlcO.js} +2 -2
  60. package/dist/chunks/{Powerline-CJYCbqSc.js.map → Powerline-BaHDJlcO.js.map} +1 -1
  61. package/dist/chunks/Prefs-4JSEtuU-.js +294 -0
  62. package/dist/chunks/Prefs-4JSEtuU-.js.map +1 -0
  63. package/dist/chunks/{Properties-fLSEE65J.js → Properties-BpUYKi7-.js} +129 -117
  64. package/dist/chunks/Properties-BpUYKi7-.js.map +1 -0
  65. package/dist/chunks/{RadioGroup-VGAaLYaR.js → RadioGroup-B2_LUgDp.js} +2 -2
  66. package/dist/chunks/{RadioGroup-VGAaLYaR.js.map → RadioGroup-B2_LUgDp.js.map} +1 -1
  67. package/dist/chunks/{RangeSlider-DefoAOsh.js → RangeSlider-S2R3zA8N.js} +8 -8
  68. package/dist/chunks/RangeSlider-S2R3zA8N.js.map +1 -0
  69. package/dist/chunks/{ResizeHandle-DMJdqRud.js → ResizeHandle-BrCO0JrH.js} +1 -1
  70. package/dist/chunks/{ResizeHandle-DMJdqRud.js.map → ResizeHandle-BrCO0JrH.js.map} +1 -1
  71. package/dist/chunks/Select-CacMfs4v.js +124 -0
  72. package/dist/chunks/Select-CacMfs4v.js.map +1 -0
  73. package/dist/chunks/{SelectionPanel-BwBevj5F.js → SelectionPanel-C59wavdA.js} +203 -202
  74. package/dist/chunks/SelectionPanel-C59wavdA.js.map +1 -0
  75. package/dist/chunks/{Sidebar-CeSRkWKK.js → Sidebar-BvwjZiFm.js} +1 -1
  76. package/dist/chunks/{Sidebar-CeSRkWKK.js.map → Sidebar-BvwjZiFm.js.map} +1 -1
  77. package/dist/chunks/{SidebarPanel-BWIiJv7M.js → SidebarPanel-D5Ua2vyP.js} +2 -2
  78. package/dist/chunks/{SidebarPanel-BWIiJv7M.js.map → SidebarPanel-D5Ua2vyP.js.map} +1 -1
  79. package/dist/chunks/{Slider-X6WcO-6s.js → Slider-Bs-KSYgQ.js} +15 -15
  80. package/dist/chunks/Slider-Bs-KSYgQ.js.map +1 -0
  81. package/dist/chunks/{StatusBar-COyPUoEs.js → StatusBar-Bqv4D0dW.js} +1 -1
  82. package/dist/chunks/{StatusBar-COyPUoEs.js.map → StatusBar-Bqv4D0dW.js.map} +1 -1
  83. package/dist/chunks/{Switch-Y097LD1L.js → Switch-C4NU-me1.js} +1 -1
  84. package/dist/chunks/{Switch-Y097LD1L.js.map → Switch-C4NU-me1.js.map} +1 -1
  85. package/dist/chunks/{Tabs-BDzkqWDE.js → Tabs-BT3CDkoQ.js} +1 -1
  86. package/dist/chunks/{Tabs-BDzkqWDE.js.map → Tabs-BT3CDkoQ.js.map} +1 -1
  87. package/dist/chunks/{Timeline-D9xQBdKN.js → Timeline-Bz806JbE.js} +2 -2
  88. package/dist/chunks/{Timeline-D9xQBdKN.js.map → Timeline-Bz806JbE.js.map} +1 -1
  89. package/dist/chunks/{Toast-DX0iLdC3.js → Toast-B7ylq7fz.js} +1 -1
  90. package/dist/chunks/{Toast-DX0iLdC3.js.map → Toast-B7ylq7fz.js.map} +1 -1
  91. package/dist/chunks/{ToggleBar-fQGk_Wf_.js → ToggleBar-Bxwv6xvj.js} +2 -2
  92. package/dist/chunks/{ToggleBar-fQGk_Wf_.js.map → ToggleBar-Bxwv6xvj.js.map} +1 -1
  93. package/dist/chunks/{ToolButton-DpqTcgLa.js → ToolButton-CtDkqUIc.js} +2 -2
  94. package/dist/chunks/{ToolButton-DpqTcgLa.js.map → ToolButton-CtDkqUIc.js.map} +1 -1
  95. package/dist/chunks/{ToolGroup-DOrObiiA.js → ToolGroup-zDtqt0QJ.js} +1 -1
  96. package/dist/chunks/{ToolGroup-DOrObiiA.js.map → ToolGroup-zDtqt0QJ.js.map} +1 -1
  97. package/dist/chunks/{ToolOptionsBar-f6b5u99i.js → ToolOptionsBar-B2xPWzJf.js} +1 -1
  98. package/dist/chunks/{ToolOptionsBar-f6b5u99i.js.map → ToolOptionsBar-B2xPWzJf.js.map} +1 -1
  99. package/dist/chunks/{ToolPalette-B_Zjw6dC.js → ToolPalette-BbrMHfQ9.js} +5 -5
  100. package/dist/chunks/{ToolPalette-B_Zjw6dC.js.map → ToolPalette-BbrMHfQ9.js.map} +1 -1
  101. package/dist/chunks/Tooltip-BHre_vbI.js +42 -0
  102. package/dist/chunks/Tooltip-BHre_vbI.js.map +1 -0
  103. package/dist/chunks/{keyGlyph-L5bsDQe5.js → keyGlyph-Bp0wnLHn.js} +1 -1
  104. package/dist/chunks/{keyGlyph-L5bsDQe5.js.map → keyGlyph-Bp0wnLHn.js.map} +1 -1
  105. package/dist/chunks/{number-Dy633mrx.js → number-Bv1nbhOJ.js} +1 -1
  106. package/dist/chunks/{number-Dy633mrx.js.map → number-Bv1nbhOJ.js.map} +1 -1
  107. package/dist/chunks/portalHost-CjpCnuGU.js +28 -0
  108. package/dist/chunks/portalHost-CjpCnuGU.js.map +1 -0
  109. package/dist/chunks/range.module-CvSfABmu.js +8 -0
  110. package/dist/chunks/range.module-CvSfABmu.js.map +1 -0
  111. package/dist/chunks/schema-CtWwGbqJ.js +41 -0
  112. package/dist/chunks/schema-CtWwGbqJ.js.map +1 -0
  113. package/dist/chunks/{segmentedControl.module-pcPOsNey.js → segmentedControl.module-BgvAPkSZ.js} +1 -1
  114. package/dist/chunks/{segmentedControl.module-pcPOsNey.js.map → segmentedControl.module-BgvAPkSZ.js.map} +1 -1
  115. package/dist/chunks/{useReorderDragList-C2fhvbU1.js → useReorderDragList-CSQGd7zQ.js} +1 -1
  116. package/dist/chunks/{useReorderDragList-C2fhvbU1.js.map → useReorderDragList-CSQGd7zQ.js.map} +1 -1
  117. package/dist/chunks/{useRovingTabIndex-DqXFNDaD.js → useRovingTabIndex-Dkpf_KhN.js} +1 -1
  118. package/dist/chunks/{useRovingTabIndex-DqXFNDaD.js.map → useRovingTabIndex-Dkpf_KhN.js.map} +1 -1
  119. package/dist/components/ActionBar/index.js +1 -1
  120. package/dist/components/ActionsBar/index.js +1 -1
  121. package/dist/components/Badge/index.js +1 -1
  122. package/dist/components/BandEditor/index.js +1 -1
  123. package/dist/components/Button/Button.d.ts +5 -0
  124. package/dist/components/Button/Button.d.ts.map +1 -1
  125. package/dist/components/Button/index.js +1 -1
  126. package/dist/components/Callout/Callout.d.ts +2 -1
  127. package/dist/components/Callout/Callout.d.ts.map +1 -1
  128. package/dist/components/Callout/index.js +1 -1
  129. package/dist/components/Checkbox/index.js +1 -1
  130. package/dist/components/ColorField/index.js +1 -1
  131. package/dist/components/ComboBox/ComboBox.d.ts +9 -2
  132. package/dist/components/ComboBox/ComboBox.d.ts.map +1 -1
  133. package/dist/components/ComboBox/index.js +1 -1
  134. package/dist/components/CurveEditor/index.js +1 -1
  135. package/dist/components/DataGrid/index.js +1 -1
  136. package/dist/components/DetentSlider/index.js +1 -1
  137. package/dist/components/Dialog/Dialog.d.ts +2 -1
  138. package/dist/components/Dialog/Dialog.d.ts.map +1 -1
  139. package/dist/components/Dialog/index.js +1 -1
  140. package/dist/components/Disclosure/index.js +1 -1
  141. package/dist/components/DragHandleGlyph/index.js +1 -1
  142. package/dist/components/Field/index.js +1 -1
  143. package/dist/components/FontFamilySelect/index.js +1 -1
  144. package/dist/components/GradientEditor/index.js +1 -1
  145. package/dist/components/InlineRange/index.js +1 -1
  146. package/dist/components/Input/index.js +1 -1
  147. package/dist/components/ItemList/index.js +1 -1
  148. package/dist/components/Keycaps/index.js +2 -2
  149. package/dist/components/LayerStack/index.js +1 -1
  150. package/dist/components/NumberField/index.js +1 -1
  151. package/dist/components/OptionsBar/index.js +1 -1
  152. package/dist/components/PaintInput/index.js +1 -1
  153. package/dist/components/PatternPicker/index.js +1 -1
  154. package/dist/components/Plot2D/index.js +1 -1
  155. package/dist/components/PointPlotter/index.js +1 -1
  156. package/dist/components/Powerline/index.js +2 -2
  157. package/dist/components/Prefs/PrefsForm.d.ts.map +1 -1
  158. package/dist/components/Prefs/index.d.ts +1 -1
  159. package/dist/components/Prefs/index.d.ts.map +1 -1
  160. package/dist/components/Prefs/index.js +3 -2
  161. package/dist/components/Prefs/schema.d.ts +17 -1
  162. package/dist/components/Prefs/schema.d.ts.map +1 -1
  163. package/dist/components/Properties/PropertyPanel.d.ts +27 -2
  164. package/dist/components/Properties/PropertyPanel.d.ts.map +1 -1
  165. package/dist/components/Properties/index.js +1 -1
  166. package/dist/components/RadioGroup/index.js +1 -1
  167. package/dist/components/RangeSlider/RangeSlider.d.ts +5 -0
  168. package/dist/components/RangeSlider/RangeSlider.d.ts.map +1 -1
  169. package/dist/components/RangeSlider/index.js +1 -1
  170. package/dist/components/ResizeHandle/index.js +1 -1
  171. package/dist/components/Select/Select.d.ts +2 -1
  172. package/dist/components/Select/Select.d.ts.map +1 -1
  173. package/dist/components/Select/index.js +1 -1
  174. package/dist/components/SelectionPanel/SelectionPanel.d.ts.map +1 -1
  175. package/dist/components/SelectionPanel/index.js +1 -1
  176. package/dist/components/Sidebar/index.js +1 -1
  177. package/dist/components/SidebarPanel/index.js +1 -1
  178. package/dist/components/Slider/index.js +1 -1
  179. package/dist/components/StatusBar/index.js +1 -1
  180. package/dist/components/Switch/index.js +1 -1
  181. package/dist/components/Tabs/index.js +1 -1
  182. package/dist/components/Timeline/index.js +1 -1
  183. package/dist/components/Toast/index.js +1 -1
  184. package/dist/components/ToggleBar/index.js +1 -1
  185. package/dist/components/ToolButton/index.js +1 -1
  186. package/dist/components/ToolGroup/index.js +1 -1
  187. package/dist/components/ToolOptionsBar/index.js +1 -1
  188. package/dist/components/ToolPalette/index.js +1 -1
  189. package/dist/components/Tooltip/Tooltip.d.ts +2 -1
  190. package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
  191. package/dist/components/Tooltip/index.js +1 -1
  192. package/dist/index.d.ts +1 -0
  193. package/dist/index.d.ts.map +1 -1
  194. package/dist/index.js +63 -61
  195. package/dist/index.js.map +1 -1
  196. package/dist/overlays/portalHost.d.ts +55 -0
  197. package/dist/overlays/portalHost.d.ts.map +1 -0
  198. package/dist/style.css +1 -1
  199. package/package.json +6 -6
  200. package/dist/chunks/Button-0OdcoOMx.js +0 -87
  201. package/dist/chunks/Callout-63z0T-w3.js +0 -116
  202. package/dist/chunks/Callout-63z0T-w3.js.map +0 -1
  203. package/dist/chunks/ComboBox-TnIrRldB.js +0 -89
  204. package/dist/chunks/ComboBox-TnIrRldB.js.map +0 -1
  205. package/dist/chunks/Dialog-D8pVzsw8.js +0 -59
  206. package/dist/chunks/Dialog-D8pVzsw8.js.map +0 -1
  207. package/dist/chunks/Prefs-BqnwyjmH.js +0 -300
  208. package/dist/chunks/Prefs-BqnwyjmH.js.map +0 -1
  209. package/dist/chunks/Properties-fLSEE65J.js.map +0 -1
  210. package/dist/chunks/RangeSlider-DefoAOsh.js.map +0 -1
  211. package/dist/chunks/Select-DGafwTF0.js +0 -121
  212. package/dist/chunks/Select-DGafwTF0.js.map +0 -1
  213. package/dist/chunks/SelectionPanel-BwBevj5F.js.map +0 -1
  214. package/dist/chunks/Slider-X6WcO-6s.js.map +0 -1
  215. package/dist/chunks/Tooltip-Bq8Hvj4r.js +0 -40
  216. package/dist/chunks/Tooltip-Bq8Hvj4r.js.map +0 -1
  217. package/dist/chunks/range.module-CO59aXEH.js +0 -8
  218. package/dist/chunks/range.module-CO59aXEH.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"Keycaps-D-UGWDrt.js","names":[],"sources":["../../src/components/Keycaps/Keycaps.module.css","../../src/components/Keycaps/Keycap.tsx","../../src/components/Keycaps/Keycaps.tsx","../../src/components/Keycaps/keySpecsFromMods.ts"],"sourcesContent":["/* Keystroke \"key cap\" styling — each modifier and key in its own bordered chip. */\n.keys {\n display: inline-flex;\n gap: 3px;\n /* Bottom-align so chips with text labels and chips with icon glyphs\n (arrows, modifier symbols) line up at the same bottom edge. Baseline\n drifts because text baselines sit above icon centers. */\n align-items: flex-end;\n /* When this whole sequence sits inline in surrounding text (e.g.\n \"Press <KeySequence> to confirm.\"), the row anchors to the\n surrounding line's bottom edge, not its baseline. */\n vertical-align: bottom;\n flex-wrap: wrap;\n}\n\n.keysEmpty {\n color: var(--wzl-fg-muted);\n}\n\n.sep {\n color: var(--wzl-fg-muted);\n font-size: var(--wzl-font-size-sm);\n line-height: 1;\n}\n\n.key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n height: 18px;\n padding: 0 4px;\n /* Light face, slightly-darker-than-face border, dark glyph — reads as\n a real keycap rather than a recessed slot in the panel. */\n background: var(--wzl-fg, #e6e7e9);\n border: 1px solid #25272c;\n border-bottom-width: 2px;\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-surface);\n /* Keycaps are a deliberate exception to the site theme — the design-\n system UI font (often a display face like Oswald) is wrong for\n keyboard chips. Use a neutral sans stack so labels read like real\n key legends. Consumers wanting a specific face per chip pass the\n `font` prop on KeyCap. */\n font-family: ui-sans-serif, system-ui, sans-serif;\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-bold);\n line-height: 1;\n}\n\n/* Default chip is square — letters, digits, and single glyphs (⎋ ⌫ ↑ etc.)\n sit in a uniform key-cap box. */\n.key[data-kind='square'] {\n width: 18px;\n padding: 0;\n}\n\n/* Modifiers (⌘ ⇪ ⌥ ⌃) are wider to match the visual weight of how they\n appear on a real keyboard — and to differentiate them from the keys\n they modify in a stack. */\n.key[data-kind='modifier'] {\n min-width: 26px;\n padding: 0 6px;\n}\n\n/* Tab / Enter / Space — actual wide keys on a real keyboard. */\n.key[data-kind='wide'] {\n min-width: 32px;\n padding: 0 8px;\n}\n\n/* Optional/inverted (default variant): face flips to dark; legend drops\n to 60% opacity; dotted border drops to 40% opacity. Three soft cues\n stacked for \"may be held but isn't required.\" */\n.key[data-inverted] {\n background: var(--wzl-surface);\n color: color-mix(in srgb, var(--wzl-fg, #e6e7e9) 60%, transparent);\n border-style: dotted;\n border-color: color-mix(in srgb, var(--wzl-fg, #e6e7e9) 40%, transparent);\n border-width: 1.5px;\n border-bottom-width: 1.5px;\n}\n\n/* Minimal variant — no fill, borders and legend follow currentColor.\n Useful inline in colored prose / next to colored badges where the\n chip should take the surrounding text color. */\n.key[data-variant='minimal'] {\n background: transparent;\n border-color: currentColor;\n color: currentColor;\n}\n\n/* Optional + minimal: dotted border at full currentColor, legend at full\n currentColor. The dotted pattern alone carries the \"optional\" cue here\n — minimal's whole point is to defer color to the surrounding context,\n so dimming would fight that. (Default variant uses graded opacity as a\n stacked cue; minimal keeps it crisp.) */\n.key[data-variant='minimal'][data-inverted] {\n border-style: dotted;\n border-color: currentColor;\n color: currentColor;\n border-width: 1.5px;\n border-bottom-width: 1.5px;\n}\n","import type { CSSProperties } from 'react';\nimport s from './Keycaps.module.css';\n\nconst MODIFIER_GLYPHS = new Set(['⌘', '⇪', '⌥', '⌃', '⇧']);\nconst WIDE_GLYPHS = new Set(['⇥', '↵', '␣']);\n\n/**\n * Width class of a keycap: a modifier glyph, a multi-character legend that\n * needs a wider chip, or a single character in a square one.\n */\nexport type KeycapKind = 'modifier' | 'wide' | 'square';\n\n/**\n * Picks the chip width for a legend — modifier glyphs and anything longer\n * than one character get the wider forms.\n */\nexport function inferKeycapKind(label: string): KeycapKind {\n if (MODIFIER_GLYPHS.has(label)) return 'modifier';\n if (WIDE_GLYPHS.has(label)) return 'wide';\n if (label.length > 1) return 'wide';\n return 'square';\n}\n\n/** Visual style of a keycap chip. */\nexport type KeyCapVariant = 'default' | 'minimal';\n\n/** Props for {@link KeyCap}. */\nexport interface KeyCapProps {\n /** Glyph rendered in the chip (modifier or key). */\n label: string;\n /** Marks the chip as not required to trigger the action. In the default\n * variant the chip face inverts (dark face / light glyph) for\n * emphasis. In the `'minimal'` variant the chip's border becomes\n * dotted to read as \"may be held, but isn't required.\" */\n inverted?: boolean;\n /** Visual style. `'default'` (default) renders a filled chip; `'minimal'`\n * renders an unfilled chip whose border and legend are `currentColor`\n * — useful inline in colored prose / next to colored badges where the\n * chip should take the surrounding text color. */\n variant?: KeyCapVariant;\n className?: string;\n /** Optional inline style. Useful for one-off width overrides (e.g.\n * building a keyboard-layout where the space bar spans several\n * standard-key widths). The component sets no inline styles of its\n * own — everything else comes from the CSS module. */\n style?: CSSProperties;\n /** Override the chip's font-family. Accepts any CSS font-family\n * string. When omitted, the chip inherits from the design-system\n * UI font token (`--wzl-font-ui`). Useful for one-off cases where\n * a different face is desired — though the canonical move is to\n * override the CSS variable at the consumer's scope, not the prop. */\n font?: string;\n}\n\n/** Single bordered keycap chip for one glyph (modifier or key). Use\n * `KeySequence` to render a full shortcut. */\nexport function KeyCap({ label, inverted = false, variant = 'default', className, style, font }: KeyCapProps) {\n const resolvedStyle = font ? { ...style, fontFamily: font } : style;\n return (\n <kbd\n className={[s.key, className].filter(Boolean).join(' ')}\n data-kind={inferKeycapKind(label)}\n data-variant={variant === 'default' ? undefined : variant}\n data-inverted={inverted || undefined}\n style={resolvedStyle}\n >\n {label}\n </kbd>\n );\n}\n","import { Fragment } from 'react';\nimport s from './Keycaps.module.css';\nimport { KeyCap, inferKeycapKind, type KeyCapVariant } from './Keycap';\n\n/** One key in a shortcut. */\nexport interface KeySpec {\n /** Glyph rendered in the chip (modifier or key). */\n label: string;\n /** When true, the chip renders inverted to mark it as not required to\n * trigger the action (e.g. an optional modifier). Defaults to false. */\n optional?: boolean;\n}\n\n/** Props for {@link KeySequence}. */\nexport interface KeySequenceProps {\n /** Keys to render. `undefined` or empty renders a muted em-dash.\n * Modifiers are always rendered first regardless of input order; relative\n * order within each group is preserved. */\n keys: readonly KeySpec[] | undefined;\n /** Character inserted between the trailing modifier chip and the first\n * non-modifier chip (e.g. `'+'` renders `⌘ + K`). `null` or `''`\n * suppresses it. Defaults to `'+'`. */\n separator?: string | null;\n /** Forwarded to every `KeyCap` in the sequence. `'minimal'` renders\n * unfilled chips whose border + legend are `currentColor`. */\n variant?: KeyCapVariant;\n /** Forwarded to every `KeyCap` in the sequence. Overrides the design-\n * system UI font for that chip only. */\n font?: string;\n className?: string;\n}\n\n/** Renders a shortcut as a row of `KeyCap` chips, one per key. Optional\n * keys render inverted to distinguish them from required ones. */\nexport function KeySequence({ keys, separator = '+', variant = 'default', font, className }: KeySequenceProps) {\n if (!keys || keys.length === 0) {\n return <span className={[s.keysEmpty, className].filter(Boolean).join(' ')}>—</span>;\n }\n const ordered = keys\n .map((k, i) => ({ k, i }))\n .sort((a, b) => {\n const am = inferKeycapKind(a.k.label) === 'modifier' ? 0 : 1;\n const bm = inferKeycapKind(b.k.label) === 'modifier' ? 0 : 1;\n return am - bm || a.i - b.i;\n })\n .map(({ k }) => k);\n const sepIdx = ordered.findIndex((k) => inferKeycapKind(k.label) !== 'modifier');\n const showSep = !!separator && sepIdx > 0 && sepIdx < ordered.length;\n return (\n <span className={[s.keys, className].filter(Boolean).join(' ')}>\n {ordered.map((k, i) => (\n <Fragment key={i}>\n {showSep && i === sepIdx ? <span className={s.sep}>{separator}</span> : null}\n <KeyCap label={k.label} inverted={k.optional ?? false} variant={variant} font={font} />\n </Fragment>\n ))}\n </span>\n );\n}\n","import type { KeySpec } from './Keycaps';\n\n/** Logical modifier name. Matches the kit's `ModifierKey` in shape — kept as a\n * local type so `weasel-ui` stays independent of `@weasel-js/core`. */\nexport type LogicalMod = 'mod' | 'shift' | 'alt' | 'ctrl' | 'meta';\n\n/** Detected (or overridden) platform. `'mod'` resolves to ⌘ on macOS,\n * Ctrl on Windows / Linux. */\nexport type Platform = 'macos' | 'windows' | 'linux';\n\n/** Visual form of a modifier or named-key label.\n *\n * - `'auto'` (default): the per-entry, per-platform default — whatever\n * label is actually printed on the physical key. macOS keys show the\n * Apple glyphs (⌘ ⌥ ⌃ ⇧ ⎋ ↵ ⇥). Windows keys mostly show text\n * (Ctrl, Alt, Shift) BUT the Win key shows its logo (⊞) and the\n * context-menu key shows ▤. Linux mirrors Windows with Super (⊞).\n * - `'symbol'` forces the Apple-style glyph everywhere a symbolic form\n * exists. Entries without a widely-recognized symbol still fall\n * back to text.\n * - `'text'` always spells the label out (Cmd / Option / Esc / Enter). */\nexport type LegendStyle = 'auto' | 'symbol' | 'text';\n\n/** A modifier to render, named platform-independently. */\nexport interface LogicalModSpec {\n name: LogicalMod;\n /** Marks this modifier as optional (may be held but isn't required).\n * Forwarded as the resulting `KeySpec.optional`. */\n optional?: boolean;\n}\n\n/** Options for {@link keySpecsFromMods}. */\nexport interface KeySpecsFromModsOptions {\n /** Override OS detection. Defaults to the detected platform, or\n * `'macos'` when detection is unavailable (e.g. in tests, or when\n * `navigator` is missing). */\n platform?: Platform;\n /** Defaults to `'auto'` (symbol on macOS, text everywhere else). Pass\n * `'symbol'` or `'text'` to force a specific style regardless of\n * platform. */\n legend?: LegendStyle;\n}\n\nconst MOD_BY_PLATFORM: Record<LogicalMod, Record<Platform, Record<LegendStyle, string>>> = {\n mod: {\n macos: { auto: '⌘', symbol: '⌘', text: 'Cmd' },\n windows: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n linux: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n },\n shift: {\n macos: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n windows: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n linux: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n },\n alt: {\n macos: { auto: '⌥', symbol: '⌥', text: 'Option' },\n windows: { auto: 'Alt', symbol: 'Alt', text: 'Alt' },\n linux: { auto: 'Alt', symbol: 'Alt', text: 'Alt' },\n },\n ctrl: {\n macos: { auto: '⌃', symbol: '⌃', text: 'Control' },\n windows: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n linux: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n },\n // Windows/Linux Meta has a recognized symbol on the physical key (⊞ Win\n // logo / Super logo), so `auto` shows it. Use `text` for Win / Super.\n meta: {\n macos: { auto: '⌘', symbol: '⌘', text: 'Cmd' },\n windows: { auto: '⊞', symbol: '⊞', text: 'Win' },\n linux: { auto: '⊞', symbol: '⊞', text: 'Super' },\n },\n};\n\n/** Detect the user's OS from the browser environment. UA sniffing — not\n * 100% reliable, but adequate for picking a modifier-glyph convention.\n * Falls back to `'macos'` (matches the kit's docs convention) when\n * `navigator` is unavailable. */\nexport function detectPlatform(): Platform {\n if (typeof navigator === 'undefined') return 'macos';\n // Prefer the modern userAgentData when available (Chromium-only today).\n const uaData = (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData;\n const raw = (uaData?.platform ?? navigator.userAgent ?? '').toLowerCase();\n if (raw.includes('mac') || raw.includes('darwin') || raw.includes('iphone') || raw.includes('ipad')) return 'macos';\n if (raw.includes('win')) return 'windows';\n if (raw.includes('linux') || raw.includes('x11')) return 'linux';\n return 'macos';\n}\n\n/**\n * Map a list of **logical** modifiers (`'mod'`, `'shift'`, …) to **visual**\n * `KeySpec`s appropriate for the current platform.\n *\n * The kit-side gesture grammar uses logical names so the same route reads\n * correctly on every OS — `'mod'` means \"Cmd on Mac, Ctrl on Win/Linux\".\n * `KeySequence` is purely visual and renders the labels you give it; this\n * helper is the bridge.\n *\n * ```ts\n * <KeySequence\n * keys={keySpecsFromMods([{ name: 'mod' }, { name: 'shift', optional: true }])}\n * />\n * ```\n *\n * On a Mac: renders `⌘ ⇧`. On Windows: renders `Ctrl Shift`. Pass an\n * explicit `platform` to override detection (e.g. for docs that always\n * show the macOS form). Pass `legend: 'text'` to force \"Cmd\" over \"⌘\"\n * even on macOS.\n */\nexport function keySpecsFromMods(\n mods: readonly LogicalModSpec[],\n opts: KeySpecsFromModsOptions = {},\n): readonly KeySpec[] {\n const platform = opts.platform ?? detectPlatform();\n const legend = opts.legend ?? 'auto';\n return mods.map((m) => {\n const label = MOD_BY_PLATFORM[m.name][platform][legend];\n return m.optional ? { label, optional: true } : { label };\n });\n}\n\n/** Per-platform × per-legend table for named, non-modifier keys. `auto`\n * is whatever's printed on the physical key for that platform. Arrows\n * are universal symbols on every platform. */\nconst NAMED_KEY: Record<string, Record<Platform, Record<LegendStyle, string>>> = {\n Escape: { macos: { auto: '⎋', symbol: '⎋', text: 'Esc' }, windows: { auto: 'Esc', symbol: 'Esc', text: 'Esc' }, linux: { auto: 'Esc', symbol: 'Esc', text: 'Esc' } },\n Enter: { macos: { auto: '↵', symbol: '↵', text: 'Return' }, windows: { auto: 'Enter', symbol: 'Enter', text: 'Enter' }, linux: { auto: 'Enter', symbol: 'Enter', text: 'Enter' } },\n Return: { macos: { auto: '↵', symbol: '↵', text: 'Return' }, windows: { auto: 'Enter', symbol: 'Enter', text: 'Enter' }, linux: { auto: 'Enter', symbol: 'Enter', text: 'Enter' } },\n Tab: { macos: { auto: '⇥', symbol: '⇥', text: 'Tab' }, windows: { auto: 'Tab', symbol: 'Tab', text: 'Tab' }, linux: { auto: 'Tab', symbol: 'Tab', text: 'Tab' } },\n Space: { macos: { auto: '␣', symbol: '␣', text: 'Space' }, windows: { auto: 'Space', symbol: 'Space', text: 'Space' }, linux: { auto: 'Space', symbol: 'Space', text: 'Space' } },\n ' ': { macos: { auto: '␣', symbol: '␣', text: 'Space' }, windows: { auto: 'Space', symbol: 'Space', text: 'Space' }, linux: { auto: 'Space', symbol: 'Space', text: 'Space' } },\n Backspace: { macos: { auto: '⌫', symbol: '⌫', text: 'Backspace' }, windows: { auto: 'Backspace', symbol: 'Backspace', text: 'Backspace' }, linux: { auto: 'Backspace', symbol: 'Backspace', text: 'Backspace' } },\n Delete: { macos: { auto: '⌦', symbol: '⌦', text: 'Delete' }, windows: { auto: 'Delete', symbol: 'Delete', text: 'Delete' }, linux: { auto: 'Delete', symbol: 'Delete', text: 'Delete' } },\n // Context-menu key (the right-Ctrl-area key on Windows / Linux keyboards\n // with a ▤ icon). Mac has no equivalent; falls back to text 'Menu'.\n ContextMenu: { macos: { auto: 'Menu', symbol: 'Menu', text: 'Menu' }, windows: { auto: '▤', symbol: '▤', text: 'Menu' }, linux: { auto: '▤', symbol: '▤', text: 'Menu' } },\n ArrowUp: { macos: { auto: '↑', symbol: '↑', text: '↑' }, windows: { auto: '↑', symbol: '↑', text: '↑' }, linux: { auto: '↑', symbol: '↑', text: '↑' } },\n ArrowDown: { macos: { auto: '↓', symbol: '↓', text: '↓' }, windows: { auto: '↓', symbol: '↓', text: '↓' }, linux: { auto: '↓', symbol: '↓', text: '↓' } },\n ArrowLeft: { macos: { auto: '←', symbol: '←', text: '←' }, windows: { auto: '←', symbol: '←', text: '←' }, linux: { auto: '←', symbol: '←', text: '←' } },\n ArrowRight: { macos: { auto: '→', symbol: '→', text: '→' }, windows: { auto: '→', symbol: '→', text: '→' }, linux: { auto: '→', symbol: '→', text: '→' } },\n};\n\n/**\n * Render a raw `KeyboardEvent.key` value as a platform-appropriate\n * `KeySpec`. Named non-modifier keys (Esc, Enter, Tab, Space, Backspace,\n * Delete, arrows) get the platform's conventional form. Letter / digit /\n * other keys pass through upper-cased.\n *\n * ```ts\n * keySpecFromKey('Escape', { platform: 'macos' }) // { label: '⎋' }\n * keySpecFromKey('Escape', { platform: 'windows' }) // { label: 'Esc' }\n * keySpecFromKey('p') // { label: 'P' }\n * ```\n *\n * Symbol mode is only meaningful on macOS — Windows / Linux keyboards\n * print the text form on the physical key, so symbol mode there falls\n * back to text.\n */\nexport function keySpecFromKey(\n raw: string,\n opts: KeySpecsFromModsOptions & { optional?: boolean } = {},\n): KeySpec {\n const platform = opts.platform ?? detectPlatform();\n const legend = opts.legend ?? 'auto';\n const entry = NAMED_KEY[raw];\n const label = entry ? entry[platform][legend] : raw.length === 1 ? raw.toUpperCase() : raw;\n return opts.optional ? { label, optional: true } : { label };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Keycaps-DSeq6QWH.js","names":[],"sources":["../../src/components/Keycaps/Keycaps.module.css","../../src/components/Keycaps/Keycap.tsx","../../src/components/Keycaps/Keycaps.tsx","../../src/components/Keycaps/keySpecsFromMods.ts"],"sourcesContent":["/* Keystroke \"key cap\" styling — each modifier and key in its own bordered chip. */\n.keys {\n display: inline-flex;\n gap: 3px;\n /* Bottom-align so chips with text labels and chips with icon glyphs\n (arrows, modifier symbols) line up at the same bottom edge. Baseline\n drifts because text baselines sit above icon centers. */\n align-items: flex-end;\n /* When this whole sequence sits inline in surrounding text (e.g.\n \"Press <KeySequence> to confirm.\"), the row anchors to the\n surrounding line's bottom edge, not its baseline. */\n vertical-align: bottom;\n flex-wrap: wrap;\n}\n\n.keysEmpty {\n color: var(--wzl-fg-muted);\n}\n\n.sep {\n color: var(--wzl-fg-muted);\n font-size: var(--wzl-font-size-sm);\n line-height: 1;\n}\n\n.key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n height: 18px;\n padding: 0 4px;\n /* Light face, slightly-darker-than-face border, dark glyph — reads as\n a real keycap rather than a recessed slot in the panel. */\n background: var(--wzl-fg, #e6e7e9);\n border: 1px solid #25272c;\n border-bottom-width: 2px;\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-surface);\n /* Keycaps are a deliberate exception to the site theme — the design-\n system UI font (often a display face like Oswald) is wrong for\n keyboard chips. Use a neutral sans stack so labels read like real\n key legends. Consumers wanting a specific face per chip pass the\n `font` prop on KeyCap. */\n font-family: ui-sans-serif, system-ui, sans-serif;\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-bold);\n line-height: 1;\n}\n\n/* Default chip is square — letters, digits, and single glyphs (⎋ ⌫ ↑ etc.)\n sit in a uniform key-cap box. */\n.key[data-kind='square'] {\n width: 18px;\n padding: 0;\n}\n\n/* Modifiers (⌘ ⇪ ⌥ ⌃) are wider to match the visual weight of how they\n appear on a real keyboard — and to differentiate them from the keys\n they modify in a stack. */\n.key[data-kind='modifier'] {\n min-width: 26px;\n padding: 0 6px;\n}\n\n/* Tab / Enter / Space — actual wide keys on a real keyboard. */\n.key[data-kind='wide'] {\n min-width: 32px;\n padding: 0 8px;\n}\n\n/* Optional/inverted (default variant): face flips to dark; legend drops\n to 60% opacity; dotted border drops to 40% opacity. Three soft cues\n stacked for \"may be held but isn't required.\" */\n.key[data-inverted] {\n background: var(--wzl-surface);\n color: color-mix(in srgb, var(--wzl-fg, #e6e7e9) 60%, transparent);\n border-style: dotted;\n border-color: color-mix(in srgb, var(--wzl-fg, #e6e7e9) 40%, transparent);\n border-width: 1.5px;\n border-bottom-width: 1.5px;\n}\n\n/* Minimal variant — no fill, borders and legend follow currentColor.\n Useful inline in colored prose / next to colored badges where the\n chip should take the surrounding text color. */\n.key[data-variant='minimal'] {\n background: transparent;\n border-color: currentColor;\n color: currentColor;\n}\n\n/* Optional + minimal: dotted border at full currentColor, legend at full\n currentColor. The dotted pattern alone carries the \"optional\" cue here\n — minimal's whole point is to defer color to the surrounding context,\n so dimming would fight that. (Default variant uses graded opacity as a\n stacked cue; minimal keeps it crisp.) */\n.key[data-variant='minimal'][data-inverted] {\n border-style: dotted;\n border-color: currentColor;\n color: currentColor;\n border-width: 1.5px;\n border-bottom-width: 1.5px;\n}\n","import type { CSSProperties } from 'react';\nimport s from './Keycaps.module.css';\n\nconst MODIFIER_GLYPHS = new Set(['⌘', '⇪', '⌥', '⌃', '⇧']);\nconst WIDE_GLYPHS = new Set(['⇥', '↵', '␣']);\n\n/**\n * Width class of a keycap: a modifier glyph, a multi-character legend that\n * needs a wider chip, or a single character in a square one.\n */\nexport type KeycapKind = 'modifier' | 'wide' | 'square';\n\n/**\n * Picks the chip width for a legend — modifier glyphs and anything longer\n * than one character get the wider forms.\n */\nexport function inferKeycapKind(label: string): KeycapKind {\n if (MODIFIER_GLYPHS.has(label)) return 'modifier';\n if (WIDE_GLYPHS.has(label)) return 'wide';\n if (label.length > 1) return 'wide';\n return 'square';\n}\n\n/** Visual style of a keycap chip. */\nexport type KeyCapVariant = 'default' | 'minimal';\n\n/** Props for {@link KeyCap}. */\nexport interface KeyCapProps {\n /** Glyph rendered in the chip (modifier or key). */\n label: string;\n /** Marks the chip as not required to trigger the action. In the default\n * variant the chip face inverts (dark face / light glyph) for\n * emphasis. In the `'minimal'` variant the chip's border becomes\n * dotted to read as \"may be held, but isn't required.\" */\n inverted?: boolean;\n /** Visual style. `'default'` (default) renders a filled chip; `'minimal'`\n * renders an unfilled chip whose border and legend are `currentColor`\n * — useful inline in colored prose / next to colored badges where the\n * chip should take the surrounding text color. */\n variant?: KeyCapVariant;\n className?: string;\n /** Optional inline style. Useful for one-off width overrides (e.g.\n * building a keyboard-layout where the space bar spans several\n * standard-key widths). The component sets no inline styles of its\n * own — everything else comes from the CSS module. */\n style?: CSSProperties;\n /** Override the chip's font-family. Accepts any CSS font-family\n * string. When omitted, the chip inherits from the design-system\n * UI font token (`--wzl-font-ui`). Useful for one-off cases where\n * a different face is desired — though the canonical move is to\n * override the CSS variable at the consumer's scope, not the prop. */\n font?: string;\n}\n\n/** Single bordered keycap chip for one glyph (modifier or key). Use\n * `KeySequence` to render a full shortcut. */\nexport function KeyCap({ label, inverted = false, variant = 'default', className, style, font }: KeyCapProps) {\n const resolvedStyle = font ? { ...style, fontFamily: font } : style;\n return (\n <kbd\n className={[s.key, className].filter(Boolean).join(' ')}\n data-kind={inferKeycapKind(label)}\n data-variant={variant === 'default' ? undefined : variant}\n data-inverted={inverted || undefined}\n style={resolvedStyle}\n >\n {label}\n </kbd>\n );\n}\n","import { Fragment } from 'react';\nimport s from './Keycaps.module.css';\nimport { KeyCap, inferKeycapKind, type KeyCapVariant } from './Keycap';\n\n/** One key in a shortcut. */\nexport interface KeySpec {\n /** Glyph rendered in the chip (modifier or key). */\n label: string;\n /** When true, the chip renders inverted to mark it as not required to\n * trigger the action (e.g. an optional modifier). Defaults to false. */\n optional?: boolean;\n}\n\n/** Props for {@link KeySequence}. */\nexport interface KeySequenceProps {\n /** Keys to render. `undefined` or empty renders a muted em-dash.\n * Modifiers are always rendered first regardless of input order; relative\n * order within each group is preserved. */\n keys: readonly KeySpec[] | undefined;\n /** Character inserted between the trailing modifier chip and the first\n * non-modifier chip (e.g. `'+'` renders `⌘ + K`). `null` or `''`\n * suppresses it. Defaults to `'+'`. */\n separator?: string | null;\n /** Forwarded to every `KeyCap` in the sequence. `'minimal'` renders\n * unfilled chips whose border + legend are `currentColor`. */\n variant?: KeyCapVariant;\n /** Forwarded to every `KeyCap` in the sequence. Overrides the design-\n * system UI font for that chip only. */\n font?: string;\n className?: string;\n}\n\n/** Renders a shortcut as a row of `KeyCap` chips, one per key. Optional\n * keys render inverted to distinguish them from required ones. */\nexport function KeySequence({ keys, separator = '+', variant = 'default', font, className }: KeySequenceProps) {\n if (!keys || keys.length === 0) {\n return <span className={[s.keysEmpty, className].filter(Boolean).join(' ')}>—</span>;\n }\n const ordered = keys\n .map((k, i) => ({ k, i }))\n .sort((a, b) => {\n const am = inferKeycapKind(a.k.label) === 'modifier' ? 0 : 1;\n const bm = inferKeycapKind(b.k.label) === 'modifier' ? 0 : 1;\n return am - bm || a.i - b.i;\n })\n .map(({ k }) => k);\n const sepIdx = ordered.findIndex((k) => inferKeycapKind(k.label) !== 'modifier');\n const showSep = !!separator && sepIdx > 0 && sepIdx < ordered.length;\n return (\n <span className={[s.keys, className].filter(Boolean).join(' ')}>\n {ordered.map((k, i) => (\n <Fragment key={i}>\n {showSep && i === sepIdx ? <span className={s.sep}>{separator}</span> : null}\n <KeyCap label={k.label} inverted={k.optional ?? false} variant={variant} font={font} />\n </Fragment>\n ))}\n </span>\n );\n}\n","import type { KeySpec } from './Keycaps';\n\n/** Logical modifier name. Matches the kit's `ModifierKey` in shape — kept as a\n * local type so `weasel-ui` stays independent of `@weasel-js/core`. */\nexport type LogicalMod = 'mod' | 'shift' | 'alt' | 'ctrl' | 'meta';\n\n/** Detected (or overridden) platform. `'mod'` resolves to ⌘ on macOS,\n * Ctrl on Windows / Linux. */\nexport type Platform = 'macos' | 'windows' | 'linux';\n\n/** Visual form of a modifier or named-key label.\n *\n * - `'auto'` (default): the per-entry, per-platform default — whatever\n * label is actually printed on the physical key. macOS keys show the\n * Apple glyphs (⌘ ⌥ ⌃ ⇧ ⎋ ↵ ⇥). Windows keys mostly show text\n * (Ctrl, Alt, Shift) BUT the Win key shows its logo (⊞) and the\n * context-menu key shows ▤. Linux mirrors Windows with Super (⊞).\n * - `'symbol'` forces the Apple-style glyph everywhere a symbolic form\n * exists. Entries without a widely-recognized symbol still fall\n * back to text.\n * - `'text'` always spells the label out (Cmd / Option / Esc / Enter). */\nexport type LegendStyle = 'auto' | 'symbol' | 'text';\n\n/** A modifier to render, named platform-independently. */\nexport interface LogicalModSpec {\n name: LogicalMod;\n /** Marks this modifier as optional (may be held but isn't required).\n * Forwarded as the resulting `KeySpec.optional`. */\n optional?: boolean;\n}\n\n/** Options for {@link keySpecsFromMods}. */\nexport interface KeySpecsFromModsOptions {\n /** Override OS detection. Defaults to the detected platform, or\n * `'macos'` when detection is unavailable (e.g. in tests, or when\n * `navigator` is missing). */\n platform?: Platform;\n /** Defaults to `'auto'` (symbol on macOS, text everywhere else). Pass\n * `'symbol'` or `'text'` to force a specific style regardless of\n * platform. */\n legend?: LegendStyle;\n}\n\nconst MOD_BY_PLATFORM: Record<LogicalMod, Record<Platform, Record<LegendStyle, string>>> = {\n mod: {\n macos: { auto: '⌘', symbol: '⌘', text: 'Cmd' },\n windows: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n linux: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n },\n shift: {\n macos: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n windows: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n linux: { auto: '⇧', symbol: '⇧', text: 'Shift' },\n },\n alt: {\n macos: { auto: '⌥', symbol: '⌥', text: 'Option' },\n windows: { auto: 'Alt', symbol: 'Alt', text: 'Alt' },\n linux: { auto: 'Alt', symbol: 'Alt', text: 'Alt' },\n },\n ctrl: {\n macos: { auto: '⌃', symbol: '⌃', text: 'Control' },\n windows: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n linux: { auto: 'Ctrl', symbol: 'Ctrl', text: 'Ctrl' },\n },\n // Windows/Linux Meta has a recognized symbol on the physical key (⊞ Win\n // logo / Super logo), so `auto` shows it. Use `text` for Win / Super.\n meta: {\n macos: { auto: '⌘', symbol: '⌘', text: 'Cmd' },\n windows: { auto: '⊞', symbol: '⊞', text: 'Win' },\n linux: { auto: '⊞', symbol: '⊞', text: 'Super' },\n },\n};\n\n/** Detect the user's OS from the browser environment. UA sniffing — not\n * 100% reliable, but adequate for picking a modifier-glyph convention.\n * Falls back to `'macos'` (matches the kit's docs convention) when\n * `navigator` is unavailable. */\nexport function detectPlatform(): Platform {\n if (typeof navigator === 'undefined') return 'macos';\n // Prefer the modern userAgentData when available (Chromium-only today).\n const uaData = (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData;\n const raw = (uaData?.platform ?? navigator.userAgent ?? '').toLowerCase();\n if (raw.includes('mac') || raw.includes('darwin') || raw.includes('iphone') || raw.includes('ipad')) return 'macos';\n if (raw.includes('win')) return 'windows';\n if (raw.includes('linux') || raw.includes('x11')) return 'linux';\n return 'macos';\n}\n\n/**\n * Map a list of **logical** modifiers (`'mod'`, `'shift'`, …) to **visual**\n * `KeySpec`s appropriate for the current platform.\n *\n * The kit-side gesture grammar uses logical names so the same route reads\n * correctly on every OS — `'mod'` means \"Cmd on Mac, Ctrl on Win/Linux\".\n * `KeySequence` is purely visual and renders the labels you give it; this\n * helper is the bridge.\n *\n * ```ts\n * <KeySequence\n * keys={keySpecsFromMods([{ name: 'mod' }, { name: 'shift', optional: true }])}\n * />\n * ```\n *\n * On a Mac: renders `⌘ ⇧`. On Windows: renders `Ctrl Shift`. Pass an\n * explicit `platform` to override detection (e.g. for docs that always\n * show the macOS form). Pass `legend: 'text'` to force \"Cmd\" over \"⌘\"\n * even on macOS.\n */\nexport function keySpecsFromMods(\n mods: readonly LogicalModSpec[],\n opts: KeySpecsFromModsOptions = {},\n): readonly KeySpec[] {\n const platform = opts.platform ?? detectPlatform();\n const legend = opts.legend ?? 'auto';\n return mods.map((m) => {\n const label = MOD_BY_PLATFORM[m.name][platform][legend];\n return m.optional ? { label, optional: true } : { label };\n });\n}\n\n/** Per-platform × per-legend table for named, non-modifier keys. `auto`\n * is whatever's printed on the physical key for that platform. Arrows\n * are universal symbols on every platform. */\nconst NAMED_KEY: Record<string, Record<Platform, Record<LegendStyle, string>>> = {\n Escape: { macos: { auto: '⎋', symbol: '⎋', text: 'Esc' }, windows: { auto: 'Esc', symbol: 'Esc', text: 'Esc' }, linux: { auto: 'Esc', symbol: 'Esc', text: 'Esc' } },\n Enter: { macos: { auto: '↵', symbol: '↵', text: 'Return' }, windows: { auto: 'Enter', symbol: 'Enter', text: 'Enter' }, linux: { auto: 'Enter', symbol: 'Enter', text: 'Enter' } },\n Return: { macos: { auto: '↵', symbol: '↵', text: 'Return' }, windows: { auto: 'Enter', symbol: 'Enter', text: 'Enter' }, linux: { auto: 'Enter', symbol: 'Enter', text: 'Enter' } },\n Tab: { macos: { auto: '⇥', symbol: '⇥', text: 'Tab' }, windows: { auto: 'Tab', symbol: 'Tab', text: 'Tab' }, linux: { auto: 'Tab', symbol: 'Tab', text: 'Tab' } },\n Space: { macos: { auto: '␣', symbol: '␣', text: 'Space' }, windows: { auto: 'Space', symbol: 'Space', text: 'Space' }, linux: { auto: 'Space', symbol: 'Space', text: 'Space' } },\n ' ': { macos: { auto: '␣', symbol: '␣', text: 'Space' }, windows: { auto: 'Space', symbol: 'Space', text: 'Space' }, linux: { auto: 'Space', symbol: 'Space', text: 'Space' } },\n Backspace: { macos: { auto: '⌫', symbol: '⌫', text: 'Backspace' }, windows: { auto: 'Backspace', symbol: 'Backspace', text: 'Backspace' }, linux: { auto: 'Backspace', symbol: 'Backspace', text: 'Backspace' } },\n Delete: { macos: { auto: '⌦', symbol: '⌦', text: 'Delete' }, windows: { auto: 'Delete', symbol: 'Delete', text: 'Delete' }, linux: { auto: 'Delete', symbol: 'Delete', text: 'Delete' } },\n // Context-menu key (the right-Ctrl-area key on Windows / Linux keyboards\n // with a ▤ icon). Mac has no equivalent; falls back to text 'Menu'.\n ContextMenu: { macos: { auto: 'Menu', symbol: 'Menu', text: 'Menu' }, windows: { auto: '▤', symbol: '▤', text: 'Menu' }, linux: { auto: '▤', symbol: '▤', text: 'Menu' } },\n ArrowUp: { macos: { auto: '↑', symbol: '↑', text: '↑' }, windows: { auto: '↑', symbol: '↑', text: '↑' }, linux: { auto: '↑', symbol: '↑', text: '↑' } },\n ArrowDown: { macos: { auto: '↓', symbol: '↓', text: '↓' }, windows: { auto: '↓', symbol: '↓', text: '↓' }, linux: { auto: '↓', symbol: '↓', text: '↓' } },\n ArrowLeft: { macos: { auto: '←', symbol: '←', text: '←' }, windows: { auto: '←', symbol: '←', text: '←' }, linux: { auto: '←', symbol: '←', text: '←' } },\n ArrowRight: { macos: { auto: '→', symbol: '→', text: '→' }, windows: { auto: '→', symbol: '→', text: '→' }, linux: { auto: '→', symbol: '→', text: '→' } },\n};\n\n/**\n * Render a raw `KeyboardEvent.key` value as a platform-appropriate\n * `KeySpec`. Named non-modifier keys (Esc, Enter, Tab, Space, Backspace,\n * Delete, arrows) get the platform's conventional form. Letter / digit /\n * other keys pass through upper-cased.\n *\n * ```ts\n * keySpecFromKey('Escape', { platform: 'macos' }) // { label: '⎋' }\n * keySpecFromKey('Escape', { platform: 'windows' }) // { label: 'Esc' }\n * keySpecFromKey('p') // { label: 'P' }\n * ```\n *\n * Symbol mode is only meaningful on macOS — Windows / Linux keyboards\n * print the text form on the physical key, so symbol mode there falls\n * back to text.\n */\nexport function keySpecFromKey(\n raw: string,\n opts: KeySpecsFromModsOptions & { optional?: boolean } = {},\n): KeySpec {\n const platform = opts.platform ?? detectPlatform();\n const legend = opts.legend ?? 'auto';\n const entry = NAMED_KEY[raw];\n const label = entry ? entry[platform][legend] : raw.length === 1 ? raw.toUpperCase() : raw;\n return opts.optional ? { label, optional: true } : { label };\n}\n"],"mappings":";;;;;;;GCGM,IAAkB,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;CAAI,CAAC,EACpD,IAAc,IAAI,IAAI;CAAC;CAAK;CAAK;CAAI,CAAC;AAY5C,SAAgB,EAAgB,GAA2B;AAIzD,QAHI,EAAgB,IAAI,EAAM,GAAS,aACnC,EAAY,IAAI,EAAM,IACtB,EAAM,SAAS,IAAU,SACtB;;AAoCT,SAAgB,EAAO,EAAE,UAAO,cAAW,IAAO,aAAU,WAAW,cAAW,UAAO,WAAqB;CAC5G,IAAM,IAAgB,IAAO;EAAE,GAAG;EAAO,YAAY;EAAM,GAAG;AAC9D,QACE,kBAAC,OAAD;EACE,WAAW,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EACvD,aAAW,EAAgB,EAAM;EACjC,gBAAc,MAAY,YAAY,KAAA,IAAY;EAClD,iBAAe,KAAY,KAAA;EAC3B,OAAO;YAEN;EACG,CAAA;;;;ACjCV,SAAgB,EAAY,EAAE,SAAM,eAAY,KAAK,aAAU,WAAW,SAAM,gBAA+B;AAC7G,KAAI,CAAC,KAAQ,EAAK,WAAW,EAC3B,QAAO,kBAAC,QAAD;EAAM,WAAW,CAAC,EAAE,WAAW,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAAE;EAAQ,CAAA;CAEtF,IAAM,IAAU,EACb,KAAK,GAAG,OAAO;EAAE;EAAG;EAAG,EAAE,CACzB,MAAM,GAAG,OACG,EAAgB,EAAE,EAAE,MAAM,KAAK,aAAa,IAAI,MAChD,EAAgB,EAAE,EAAE,MAAM,KAAK,aAAa,IAAI,MACzC,EAAE,IAAI,EAAE,EAC1B,CACD,KAAK,EAAE,WAAQ,EAAE,EACd,IAAS,EAAQ,WAAW,MAAM,EAAgB,EAAE,MAAM,KAAK,WAAW,EAC1E,IAAU,CAAC,CAAC,KAAa,IAAS,KAAK,IAAS,EAAQ;AAC9D,QACE,kBAAC,QAAD;EAAM,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAC3D,EAAQ,KAAK,GAAG,MACf,kBAAC,GAAD,EAAA,UAAA,CACG,KAAW,MAAM,IAAS,kBAAC,QAAD;GAAM,WAAW,EAAE;aAAM;GAAiB,CAAA,GAAG,MACxE,kBAAC,GAAD;GAAQ,OAAO,EAAE;GAAO,UAAU,EAAE,YAAY;GAAgB;GAAe;GAAQ,CAAA,CAC9E,EAAA,EAHI,EAGJ,CACX;EACG,CAAA;;;;ACbX,IAAM,IAAqF;CACzF,KAAK;EACH,OAAS;GAAE,MAAM;GAAQ,QAAQ;GAAQ,MAAM;GAAO;EACtD,SAAS;GAAE,MAAM;GAAQ,QAAQ;GAAQ,MAAM;GAAQ;EACvD,OAAS;GAAE,MAAM;GAAQ,QAAQ;GAAQ,MAAM;GAAQ;EACxD;CACD,OAAO;EACL,OAAS;GAAE,MAAM;GAAK,QAAQ;GAAK,MAAM;GAAS;EAClD,SAAS;GAAE,MAAM;GAAK,QAAQ;GAAK,MAAM;GAAS;EAClD,OAAS;GAAE,MAAM;GAAK,QAAQ;GAAK,MAAM;GAAS;EACnD;CACD,KAAK;EACH,OAAS;GAAE,MAAM;GAAO,QAAQ;GAAO,MAAM;GAAU;EACvD,SAAS;GAAE,MAAM;GAAO,QAAQ;GAAO,MAAM;GAAO;EACpD,OAAS;GAAE,MAAM;GAAO,QAAQ;GAAO,MAAM;GAAO;EACrD;CACD,MAAM;EACJ,OAAS;GAAE,MAAM;GAAQ,QAAQ;GAAQ,MAAM;GAAW;EAC1D,SAAS;GAAE,MAAM;GAAQ,QAAQ;GAAQ,MAAM;GAAQ;EACvD,OAAS;GAAE,MAAM;GAAQ,QAAQ;GAAQ,MAAM;GAAQ;EACxD;CAGD,MAAM;EACJ,OAAS;GAAE,MAAM;GAAK,QAAQ;GAAK,MAAM;GAAO;EAChD,SAAS;GAAE,MAAM;GAAK,QAAQ;GAAK,MAAM;GAAO;EAChD,OAAS;GAAE,MAAM;GAAK,QAAQ;GAAK,MAAM;GAAS;EACnD;CACF;AAMD,SAAgB,IAA2B;AACzC,KAAI,OAAO,YAAc,IAAa,QAAO;CAG7C,IAAM,KADU,UAAoE,eAC/D,YAAY,UAAU,aAAa,IAAI,aAAa;AAIzE,QAHI,EAAI,SAAS,MAAM,IAAI,EAAI,SAAS,SAAS,IAAI,EAAI,SAAS,SAAS,IAAI,EAAI,SAAS,OAAO,GAAS,UACxG,EAAI,SAAS,MAAM,GAAS,YAC5B,EAAI,SAAS,QAAQ,IAAI,EAAI,SAAS,MAAM,GAAS,UAClD;;AAuBT,SAAgB,EACd,GACA,IAAgC,EAAE,EACd;CACpB,IAAM,IAAW,EAAK,YAAY,GAAgB,EAC5C,IAAS,EAAK,UAAU;AAC9B,QAAO,EAAK,KAAK,MAAM;EACrB,IAAM,IAAQ,EAAgB,EAAE,MAAM,GAAU;AAChD,SAAO,EAAE,WAAW;GAAE;GAAO,UAAU;GAAM,GAAG,EAAE,UAAO;GACzD;;AAMJ,IAAM,IAA2E;CAC/E,QAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAO;EAAQ,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAO;EAAQ,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAO;EAAE;CACvN,OAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAU;EAAK,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAS;EAAM,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAS;EAAE;CACzN,QAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAU;EAAK,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAS;EAAM,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAS;EAAE;CACzN,KAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAO;EAAQ,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAO;EAAQ,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAO;EAAE;CACvN,OAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAS;EAAM,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAS;EAAM,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAS;EAAE;CACzN,KAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAS;EAAM,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAS;EAAM,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAS;EAAE;CACzN,WAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAa;EAAE,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAa;EAAE,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAa;EAAE;CAC7N,QAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAU;EAAK,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAU;EAAK,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAU;EAAE;CAG1N,aAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAQ;EAAO,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAQ;EAAO,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAQ;EAAE;CACxN,SAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAK;EAAU,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAK;EAAU,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAK;EAAE;CACrN,WAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAK;EAAU,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAK;EAAU,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAK;EAAE;CACrN,WAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAK;EAAU,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAK;EAAU,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAK;EAAE;CACrN,YAAa;EAAE,OAAO;GAAE,MAAM;GAAQ,QAAQ;GAAY,MAAM;GAAK;EAAU,SAAS;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAK;EAAU,OAAO;GAAE,MAAM;GAAa,QAAQ;GAAa,MAAM;GAAK;EAAE;CACtN;AAkBD,SAAgB,EACd,GACA,IAAyD,EAAE,EAClD;CACT,IAAM,IAAW,EAAK,YAAY,GAAgB,EAC5C,IAAS,EAAK,UAAU,QACxB,IAAQ,EAAU,IAClB,IAAQ,IAAQ,EAAM,GAAU,KAAU,EAAI,WAAW,IAAI,EAAI,aAAa,GAAG;AACvF,QAAO,EAAK,WAAW;EAAE;EAAO,UAAU;EAAM,GAAG,EAAE,UAAO"}
@@ -1,6 +1,6 @@
1
1
  import { t as e } from "./dlog-CMYiRF3Q.js";
2
- import { t } from "./useReorderDragList-C2fhvbU1.js";
3
- import { t as n } from "./DragHandleGlyph-C6GJyfnm.js";
2
+ import { t } from "./useReorderDragList-CSQGd7zQ.js";
3
+ import { t as n } from "./DragHandleGlyph-CppAz4_8.js";
4
4
  import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
5
  import { useEffect as a, useState as o } from "react";
6
6
  var s = {
@@ -139,4 +139,4 @@ function c({ className: c, title: l, items: u, paletteKinds: d, onAdd: f, onRemo
139
139
  //#endregion
140
140
  export { c as t };
141
141
 
142
- //# sourceMappingURL=LayerStack-DQnWSOVC.js.map
142
+ //# sourceMappingURL=LayerStack-DOylrQcm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LayerStack-DQnWSOVC.js","names":[],"sources":["../../src/components/LayerStack/LayerStack.module.css","../../src/components/LayerStack/LayerStack.tsx"],"sourcesContent":["/* Authored against labkit's `.lk-root`, which supplied a border-box reset and a\n set of bare-button defaults. weasel-ui has neither, so the stack restates\n both over its own subtree rather than depending on an ancestor class. */\n.stack,\n.stack * {\n box-sizing: border-box;\n}\n\n.stack {\n display: grid;\n gap: 8px;\n}\n\n.head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.title {\n margin: 0;\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-lg);\n color: var(--wzl-fg);\n}\n\n.palette {\n display: flex;\n gap: 4px;\n}\n\n.add {\n font: inherit;\n height: var(--wzl-control-h);\n background: transparent;\n border: 1px dashed var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n padding: 2px 8px;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n cursor: pointer;\n}\n\n.add:hover {\n background: var(--wzl-surface-raised);\n color: var(--wzl-fg);\n}\n\n.list {\n display: grid;\n gap: 6px;\n}\n\n.dropHint {\n height: 2px;\n background: var(--wzl-accent);\n border-radius: var(--wzl-radius-sm);\n}\n\n.empty {\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size);\n padding: 8px;\n text-align: center;\n}\n\n.card {\n background: var(--wzl-surface-raised);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n overflow: hidden;\n}\n\n.cardAccented {\n /* Re-bind --wzl-accent inside the card so descendant controls (slider tracks\n and thumbs, readouts, etc.) tint to the per-card accent instead of the\n global theme accent. Mirrors EffectCard's behavior. */\n --wzl-accent: var(--wzl-layer-stack-accent);\n border-left: 3px solid var(--wzl-layer-stack-accent);\n}\n\n.cardDragging {\n opacity: 0.5;\n}\n\n.cardHead {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n}\n\n.handle {\n font: inherit;\n background: transparent;\n border: 0;\n color: var(--wzl-fg-subtle);\n padding: 2px 4px;\n cursor: grab;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n height: 22px;\n border-radius: var(--wzl-radius-sm);\n font-weight: var(--wzl-font-weight-bold);\n font-size: var(--wzl-font-size);\n}\n\n.cardAccented .handle {\n background: var(--wzl-layer-stack-accent);\n color: white;\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.kind,\n.primary {\n flex: 1;\n font-size: var(--wzl-font-size);\n}\n\n.primary {\n background: transparent;\n border: 0;\n color: var(--wzl-fg);\n font: inherit;\n text-transform: uppercase;\n letter-spacing: var(--wzl-tracking-wide);\n}\n\n.remove {\n font: inherit;\n height: var(--wzl-control-h);\n background: transparent;\n border: 0;\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size-lg);\n cursor: pointer;\n padding: 2px 6px;\n}\n\n.remove:hover {\n color: var(--wzl-fg);\n}\n\n.cardBody {\n padding: 8px;\n border-top: 1px solid var(--wzl-border);\n}\n","import { type CSSProperties, type ReactNode, type RefCallback, useEffect, useState } from 'react';\nimport { dlog } from '../../dlog';\nimport { useReorderDragList } from '../../useReorderDragList';\nimport { DragHandleGlyph } from '../DragHandleGlyph';\nimport s from './LayerStack.module.css';\n\n/** One card in a layer stack: its identity, the label shown when collapsed,\n * and the optional select hoisted into its header. */\nexport interface LayerStackItem {\n /** Stable id used for keys, onRemove, onReorder. Numeric to match\n * common id-from-nextId conventions; string ids also work. */\n id: number | string;\n /** Short kind label rendered in the header when no primary select\n * is hoisted (e.g. \"shadow\", \"stroke\"). Omit it for items that have no\n * kind; `label` then names the card. */\n kind?: string;\n /** Header text, when the item's name is not its kind. Falls back to\n * `kind`. */\n label?: string;\n /** When present, hoist this select into the card header so the user\n * can switch mode/shape without expanding. */\n primaryValue?: string;\n primaryOptions?: string[];\n /** Accent CSS color used as the left border / index-badge fill. Sets\n * --wzl-layer-stack-accent on the card, which re-binds --wzl-accent\n * for everything inside it. */\n accent?: string;\n /** Optional badge text rendered before the primary control\n * (e.g. tail index \"1\", \"2\", \"3\"). When omitted a drag handle\n * glyph renders in its place. */\n badge?: string;\n /** Initial expanded state. Defaults to true for newly-added items. */\n defaultExpanded?: boolean;\n}\n\n/** Props for `<LayerStack>`. */\nexport interface LayerStackProps {\n /** Heading above the list. Omit it, along with the palette, for a stack\n * whose surroundings already name it. */\n title?: string;\n items: LayerStackItem[];\n /** Kinds the user can add via the header palette. Omit both this and\n * `onAdd` for a stack the user cannot add to. */\n paletteKinds?: string[];\n onAdd?: (kind: string) => void;\n /** Omit for items the user cannot remove; the card then has no ✕. */\n onRemove?: (id: number | string) => void;\n onReorder: (orderedIds: Array<number | string>) => void;\n /** Required for `primaryValue` to render as a select — without a handler\n * the control could not be changed. */\n onPrimaryChange?: (id: number | string, nextValue: string) => void;\n /** Render the body controls for each item. */\n renderBody: (item: LayerStackItem) => ReactNode;\n /** Shown in place of the list when `items` is empty. Defaults to a line\n * that points at the palette, or a bare one when there is no palette. */\n emptyLabel?: ReactNode;\n /** Hide the title + palette row (used when an outer wrap renders its\n * own head — see speech-balloons Tails panel). */\n hideHead?: boolean;\n /** Appended to the root element's class list. The module's own class names\n * are hashed, so this is the supported way to reach the stack from a\n * consumer stylesheet. */\n className?: string;\n}\n\n/** A drag-reorderable stack of expandable cards, with a palette in the header\n * for adding more. The body of each card is the caller's to render. */\nexport function LayerStack({\n className,\n title,\n items,\n paletteKinds,\n onAdd,\n onRemove,\n onReorder,\n onPrimaryChange,\n renderBody,\n emptyLabel,\n hideHead,\n}: LayerStackProps) {\n const [expandedIds, setExpandedIds] = useState<Set<number | string>>(\n () => new Set(items.filter((i) => i.defaultExpanded !== false).map((i) => i.id)),\n );\n\n useEffect(() => {\n setExpandedIds((prev) => {\n let next: Set<number | string> | null = null;\n for (const item of items) {\n if (item.defaultExpanded === false) continue;\n if (prev.has(item.id)) continue;\n if (next === null) next = new Set(prev);\n next.add(item.id);\n }\n return next ?? prev;\n });\n }, [items]);\n\n const dragItems = items.map((it) => ({\n id: String(it.id),\n label: it.label ?? it.kind ?? String(it.id),\n }));\n const drag = useReorderDragList({\n items: dragItems,\n selectedIds: [],\n onReorder: (ids, targetIndex) => {\n dlog('layer-stack', 'onReorder', { ids, targetIndex });\n const orig = items.map((i) => i.id);\n const moving = new Set(ids);\n const remaining = orig.filter((id) => !moving.has(String(id)));\n const movedIds = items.map((i) => i.id).filter((id) => moving.has(String(id)));\n // weasel reports targetIndex against the original list (which includes\n // the dragged row). Each moved item that originally sat before\n // targetIndex needs to shift the insertion point left by one.\n const shift = orig.filter((id, idx) => moving.has(String(id)) && idx < targetIndex).length;\n const adjusted = targetIndex - shift;\n const out = [...remaining];\n out.splice(adjusted, 0, ...movedIds);\n onReorder(out);\n },\n });\n\n const toggleExpanded = (id: number | string) => {\n setExpandedIds((current) => {\n const n = new Set(current);\n if (n.has(id)) n.delete(id);\n else n.add(id);\n return n;\n });\n };\n\n const palette = onAdd ? (paletteKinds ?? []) : [];\n const showHead = !hideHead && (title !== undefined || palette.length > 0);\n\n return (\n <div className={className ? `${s.stack} ${className}` : s.stack}>\n {showHead && (\n <div className={s.head}>\n {title !== undefined && <h2 className={s.title}>{title}</h2>}\n <div className={s.palette}>\n {palette.map((k) => (\n <button\n key={k}\n type=\"button\"\n className={s.add}\n onClick={() => {\n dlog('layer-stack', 'onAdd', { kind: k });\n onAdd?.(k);\n }}\n aria-label={`Add ${k}`}\n >\n {k}\n </button>\n ))}\n </div>\n </div>\n )}\n <div\n className={s.list}\n ref={drag.containerProps.ref as RefCallback<HTMLDivElement>}\n >\n {items.map((item, i) => {\n const expanded = expandedIds.has(item.id);\n const draggedId = drag.state.draggedIds?.[0];\n const isDragging = draggedId === String(item.id);\n const showHintBefore = drag.state.targetIndex === i && draggedId !== String(item.id);\n const showHintAfter = drag.state.targetIndex === items.length && i === items.length - 1;\n const cardCls = [\n s.card,\n isDragging ? s.cardDragging : '',\n item.accent ? s.cardAccented : '',\n ]\n .filter(Boolean)\n .join(' ');\n const cardStyle = item.accent\n ? ({ '--wzl-layer-stack-accent': item.accent } as CSSProperties)\n : undefined;\n const { onPointerDown } = drag.rowProps(String(item.id), i);\n return (\n <div key={item.id}>\n {showHintBefore && <div className={s.dropHint} />}\n <div className={cardCls} data-testid={`layer-card-${item.id}`} style={cardStyle}>\n <div className={s.cardHead}>\n <button\n type=\"button\"\n className={s.handle}\n aria-label={`Drag to reorder layer ${item.id}`}\n onPointerDown={onPointerDown}\n onClick={() => toggleExpanded(item.id)}\n >\n {item.badge ?? <DragHandleGlyph />}\n </button>\n {item.primaryValue !== undefined && item.primaryOptions && onPrimaryChange ? (\n <select\n className={s.primary}\n value={item.primaryValue}\n aria-label={`Primary select for layer ${item.id}`}\n onChange={(e) => onPrimaryChange(item.id, e.target.value)}\n onClick={(e) => e.stopPropagation()}\n >\n {item.primaryOptions.map((o) => (\n <option key={o} value={o}>\n {o}\n </option>\n ))}\n </select>\n ) : (\n <span className={s.kind}>{item.label ?? item.kind}</span>\n )}\n {onRemove && (\n <button\n type=\"button\"\n className={s.remove}\n aria-label=\"Remove layer\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove(item.id);\n }}\n >\n ✕\n </button>\n )}\n </div>\n {expanded && <div className={s.cardBody}>{renderBody(item)}</div>}\n </div>\n {showHintAfter && <div className={s.dropHint} />}\n </div>\n );\n })}\n {items.length === 0 && (\n <div className={s.empty}>\n {emptyLabel ?? (palette.length > 0 ? 'No layers — add one above.' : 'No layers.')}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LayerStack-DOylrQcm.js","names":[],"sources":["../../src/components/LayerStack/LayerStack.module.css","../../src/components/LayerStack/LayerStack.tsx"],"sourcesContent":["/* Authored against labkit's `.lk-root`, which supplied a border-box reset and a\n set of bare-button defaults. weasel-ui has neither, so the stack restates\n both over its own subtree rather than depending on an ancestor class. */\n.stack,\n.stack * {\n box-sizing: border-box;\n}\n\n.stack {\n display: grid;\n gap: 8px;\n}\n\n.head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.title {\n margin: 0;\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-lg);\n color: var(--wzl-fg);\n}\n\n.palette {\n display: flex;\n gap: 4px;\n}\n\n.add {\n font: inherit;\n height: var(--wzl-control-h);\n background: transparent;\n border: 1px dashed var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n padding: 2px 8px;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n cursor: pointer;\n}\n\n.add:hover {\n background: var(--wzl-surface-raised);\n color: var(--wzl-fg);\n}\n\n.list {\n display: grid;\n gap: 6px;\n}\n\n.dropHint {\n height: 2px;\n background: var(--wzl-accent);\n border-radius: var(--wzl-radius-sm);\n}\n\n.empty {\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size);\n padding: 8px;\n text-align: center;\n}\n\n.card {\n background: var(--wzl-surface-raised);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n overflow: hidden;\n}\n\n.cardAccented {\n /* Re-bind --wzl-accent inside the card so descendant controls (slider tracks\n and thumbs, readouts, etc.) tint to the per-card accent instead of the\n global theme accent. Mirrors EffectCard's behavior. */\n --wzl-accent: var(--wzl-layer-stack-accent);\n border-left: 3px solid var(--wzl-layer-stack-accent);\n}\n\n.cardDragging {\n opacity: 0.5;\n}\n\n.cardHead {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n}\n\n.handle {\n font: inherit;\n background: transparent;\n border: 0;\n color: var(--wzl-fg-subtle);\n padding: 2px 4px;\n cursor: grab;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n height: 22px;\n border-radius: var(--wzl-radius-sm);\n font-weight: var(--wzl-font-weight-bold);\n font-size: var(--wzl-font-size);\n}\n\n.cardAccented .handle {\n background: var(--wzl-layer-stack-accent);\n color: white;\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.kind,\n.primary {\n flex: 1;\n font-size: var(--wzl-font-size);\n}\n\n.primary {\n background: transparent;\n border: 0;\n color: var(--wzl-fg);\n font: inherit;\n text-transform: uppercase;\n letter-spacing: var(--wzl-tracking-wide);\n}\n\n.remove {\n font: inherit;\n height: var(--wzl-control-h);\n background: transparent;\n border: 0;\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size-lg);\n cursor: pointer;\n padding: 2px 6px;\n}\n\n.remove:hover {\n color: var(--wzl-fg);\n}\n\n.cardBody {\n padding: 8px;\n border-top: 1px solid var(--wzl-border);\n}\n","import { type CSSProperties, type ReactNode, type RefCallback, useEffect, useState } from 'react';\nimport { dlog } from '../../dlog';\nimport { useReorderDragList } from '../../useReorderDragList';\nimport { DragHandleGlyph } from '../DragHandleGlyph';\nimport s from './LayerStack.module.css';\n\n/** One card in a layer stack: its identity, the label shown when collapsed,\n * and the optional select hoisted into its header. */\nexport interface LayerStackItem {\n /** Stable id used for keys, onRemove, onReorder. Numeric to match\n * common id-from-nextId conventions; string ids also work. */\n id: number | string;\n /** Short kind label rendered in the header when no primary select\n * is hoisted (e.g. \"shadow\", \"stroke\"). Omit it for items that have no\n * kind; `label` then names the card. */\n kind?: string;\n /** Header text, when the item's name is not its kind. Falls back to\n * `kind`. */\n label?: string;\n /** When present, hoist this select into the card header so the user\n * can switch mode/shape without expanding. */\n primaryValue?: string;\n primaryOptions?: string[];\n /** Accent CSS color used as the left border / index-badge fill. Sets\n * --wzl-layer-stack-accent on the card, which re-binds --wzl-accent\n * for everything inside it. */\n accent?: string;\n /** Optional badge text rendered before the primary control\n * (e.g. tail index \"1\", \"2\", \"3\"). When omitted a drag handle\n * glyph renders in its place. */\n badge?: string;\n /** Initial expanded state. Defaults to true for newly-added items. */\n defaultExpanded?: boolean;\n}\n\n/** Props for `<LayerStack>`. */\nexport interface LayerStackProps {\n /** Heading above the list. Omit it, along with the palette, for a stack\n * whose surroundings already name it. */\n title?: string;\n items: LayerStackItem[];\n /** Kinds the user can add via the header palette. Omit both this and\n * `onAdd` for a stack the user cannot add to. */\n paletteKinds?: string[];\n onAdd?: (kind: string) => void;\n /** Omit for items the user cannot remove; the card then has no ✕. */\n onRemove?: (id: number | string) => void;\n onReorder: (orderedIds: Array<number | string>) => void;\n /** Required for `primaryValue` to render as a select — without a handler\n * the control could not be changed. */\n onPrimaryChange?: (id: number | string, nextValue: string) => void;\n /** Render the body controls for each item. */\n renderBody: (item: LayerStackItem) => ReactNode;\n /** Shown in place of the list when `items` is empty. Defaults to a line\n * that points at the palette, or a bare one when there is no palette. */\n emptyLabel?: ReactNode;\n /** Hide the title + palette row (used when an outer wrap renders its\n * own head — see speech-balloons Tails panel). */\n hideHead?: boolean;\n /** Appended to the root element's class list. The module's own class names\n * are hashed, so this is the supported way to reach the stack from a\n * consumer stylesheet. */\n className?: string;\n}\n\n/** A drag-reorderable stack of expandable cards, with a palette in the header\n * for adding more. The body of each card is the caller's to render. */\nexport function LayerStack({\n className,\n title,\n items,\n paletteKinds,\n onAdd,\n onRemove,\n onReorder,\n onPrimaryChange,\n renderBody,\n emptyLabel,\n hideHead,\n}: LayerStackProps) {\n const [expandedIds, setExpandedIds] = useState<Set<number | string>>(\n () => new Set(items.filter((i) => i.defaultExpanded !== false).map((i) => i.id)),\n );\n\n useEffect(() => {\n setExpandedIds((prev) => {\n let next: Set<number | string> | null = null;\n for (const item of items) {\n if (item.defaultExpanded === false) continue;\n if (prev.has(item.id)) continue;\n if (next === null) next = new Set(prev);\n next.add(item.id);\n }\n return next ?? prev;\n });\n }, [items]);\n\n const dragItems = items.map((it) => ({\n id: String(it.id),\n label: it.label ?? it.kind ?? String(it.id),\n }));\n const drag = useReorderDragList({\n items: dragItems,\n selectedIds: [],\n onReorder: (ids, targetIndex) => {\n dlog('layer-stack', 'onReorder', { ids, targetIndex });\n const orig = items.map((i) => i.id);\n const moving = new Set(ids);\n const remaining = orig.filter((id) => !moving.has(String(id)));\n const movedIds = items.map((i) => i.id).filter((id) => moving.has(String(id)));\n // weasel reports targetIndex against the original list (which includes\n // the dragged row). Each moved item that originally sat before\n // targetIndex needs to shift the insertion point left by one.\n const shift = orig.filter((id, idx) => moving.has(String(id)) && idx < targetIndex).length;\n const adjusted = targetIndex - shift;\n const out = [...remaining];\n out.splice(adjusted, 0, ...movedIds);\n onReorder(out);\n },\n });\n\n const toggleExpanded = (id: number | string) => {\n setExpandedIds((current) => {\n const n = new Set(current);\n if (n.has(id)) n.delete(id);\n else n.add(id);\n return n;\n });\n };\n\n const palette = onAdd ? (paletteKinds ?? []) : [];\n const showHead = !hideHead && (title !== undefined || palette.length > 0);\n\n return (\n <div className={className ? `${s.stack} ${className}` : s.stack}>\n {showHead && (\n <div className={s.head}>\n {title !== undefined && <h2 className={s.title}>{title}</h2>}\n <div className={s.palette}>\n {palette.map((k) => (\n <button\n key={k}\n type=\"button\"\n className={s.add}\n onClick={() => {\n dlog('layer-stack', 'onAdd', { kind: k });\n onAdd?.(k);\n }}\n aria-label={`Add ${k}`}\n >\n {k}\n </button>\n ))}\n </div>\n </div>\n )}\n <div\n className={s.list}\n ref={drag.containerProps.ref as RefCallback<HTMLDivElement>}\n >\n {items.map((item, i) => {\n const expanded = expandedIds.has(item.id);\n const draggedId = drag.state.draggedIds?.[0];\n const isDragging = draggedId === String(item.id);\n const showHintBefore = drag.state.targetIndex === i && draggedId !== String(item.id);\n const showHintAfter = drag.state.targetIndex === items.length && i === items.length - 1;\n const cardCls = [\n s.card,\n isDragging ? s.cardDragging : '',\n item.accent ? s.cardAccented : '',\n ]\n .filter(Boolean)\n .join(' ');\n const cardStyle = item.accent\n ? ({ '--wzl-layer-stack-accent': item.accent } as CSSProperties)\n : undefined;\n const { onPointerDown } = drag.rowProps(String(item.id), i);\n return (\n <div key={item.id}>\n {showHintBefore && <div className={s.dropHint} />}\n <div className={cardCls} data-testid={`layer-card-${item.id}`} style={cardStyle}>\n <div className={s.cardHead}>\n <button\n type=\"button\"\n className={s.handle}\n aria-label={`Drag to reorder layer ${item.id}`}\n onPointerDown={onPointerDown}\n onClick={() => toggleExpanded(item.id)}\n >\n {item.badge ?? <DragHandleGlyph />}\n </button>\n {item.primaryValue !== undefined && item.primaryOptions && onPrimaryChange ? (\n <select\n className={s.primary}\n value={item.primaryValue}\n aria-label={`Primary select for layer ${item.id}`}\n onChange={(e) => onPrimaryChange(item.id, e.target.value)}\n onClick={(e) => e.stopPropagation()}\n >\n {item.primaryOptions.map((o) => (\n <option key={o} value={o}>\n {o}\n </option>\n ))}\n </select>\n ) : (\n <span className={s.kind}>{item.label ?? item.kind}</span>\n )}\n {onRemove && (\n <button\n type=\"button\"\n className={s.remove}\n aria-label=\"Remove layer\"\n onClick={(e) => {\n e.stopPropagation();\n onRemove(item.id);\n }}\n >\n ✕\n </button>\n )}\n </div>\n {expanded && <div className={s.cardBody}>{renderBody(item)}</div>}\n </div>\n {showHintAfter && <div className={s.dropHint} />}\n </div>\n );\n })}\n {items.length === 0 && (\n <div className={s.empty}>\n {emptyLabel ?? (palette.length > 0 ? 'No layers — add one above.' : 'No layers.')}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { n as e } from "./Field-BUroh92b.js";
1
+ import { n as e } from "./Field-dzlxOGxT.js";
2
2
  import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
3
  import { forwardRef as r } from "react";
4
4
  import { Button as i, FieldError as a, Group as o, Input as s, Label as c, NumberField as l, Text as u } from "react-aria-components";
@@ -59,4 +59,4 @@ var d = {
59
59
  //#endregion
60
60
  export { f as t };
61
61
 
62
- //# sourceMappingURL=NumberField-DLw4UmF3.js.map
62
+ //# sourceMappingURL=NumberField-DBowmOfe.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"NumberField-DLw4UmF3.js","names":[],"sources":["../../src/components/NumberField/NumberField.module.css","../../src/components/NumberField/NumberField.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: 4px;\n}\n\n.frame {\n display: flex;\n align-items: stretch;\n height: var(--wzl-field-h, var(--wzl-control-h));\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n line-height: 1;\n overflow: hidden;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.frame:focus-within {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .frame {\n border-color: var(--wzl-danger);\n}\n\n.field[data-disabled] .frame {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.frame input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font: inherit;\n padding: 0 var(--wzl-field-pad-x);\n text-align: right;\n -moz-appearance: textfield;\n}\n\n.frame input::-webkit-outer-spin-button,\n.frame input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.steppers {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--wzl-border);\n}\n\n.stepper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n flex: 1;\n background: transparent;\n border: none;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n font-size: var(--wzl-font-size-2xs);\n line-height: 1;\n}\n\n.stepper:not(:last-child) {\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.stepper[data-hovered]:not([data-disabled]) {\n background: var(--wzl-surface-hover);\n color: var(--wzl-fg);\n}\n\n.stepper[data-pressed]:not([data-disabled]) {\n background: var(--wzl-surface-pressed);\n}\n\n.stepper[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Mirrors the property rows' editable readout (`.readoutInput`), so a number\n sitting inside other chrome does not draw a second box around itself. */\n.ghost {\n height: auto;\n background: transparent;\n border-color: transparent;\n color: var(--wzl-accent);\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.ghost input {\n padding: 1px 0;\n}\n\n.ghost:hover {\n background: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.ghost:focus-within {\n background: var(--wzl-surface-sunken);\n border-color: var(--wzl-focus-ring);\n box-shadow: none;\n}\n\n/* `width='fit'` states the field's width in characters: left alone an `<input>`\n asks for its 20-character intrinsic width, wider than most rows it sits in. */\n.fit {\n width: auto;\n max-width: 100%;\n align-items: flex-start;\n}\n\n.fit .frame {\n width: fit-content;\n max-width: 100%;\n}\n\n.fit .frame input {\n flex: 0 0 auto;\n width: var(--wzl-number-field-width, 9ch);\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n NumberField as RACNumberField,\n Label,\n Input as RACInput,\n Group,\n Button as RACButton,\n Text,\n FieldError,\n type NumberFieldProps as RACNumberFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './NumberField.module.css';\n\n/** Props for {@link NumberField}, on top of React Aria's `NumberField` props. */\nexport type NumberFieldProps = Omit<RACNumberFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n /** Hide the up/down stepper buttons. Defaults to false. */\n hideSteppers?: boolean;\n /** Render with no box until focused — the readout treatment the property\n * rows use, for a value that sits inside other chrome rather than in a form. */\n ghost?: boolean;\n /** Native input placeholder — e.g. `'Mixed'` for a multi-selection\n * editor with no shared value. */\n placeholder?: string;\n /**\n * `'fill'` (the default) takes the width of whatever row the field sits in.\n * `'fit'` sizes it to `--wzl-number-field-width` (`9ch` by default) plus its\n * own chrome, rather than to the input's 20-character intrinsic width.\n */\n width?: 'fill' | 'fit';\n className?: string;\n};\n\n/**\n * Numeric input with stepper buttons, wrapping React Aria's NumberField and\n * supplying the same label / description / errorMessage slots as `Input`.\n * Arrow keys and scroll step the value; parsing and formatting are React\n * Aria's.\n *\n * `ref` forwards to the underlying `<input>`.\n */\nexport const NumberField = forwardRef(function NumberField(\n props: NumberFieldProps,\n ref: Ref<HTMLInputElement>,\n) {\n const {\n label,\n description,\n errorMessage,\n hideSteppers,\n ghost,\n placeholder,\n width = 'fill',\n className,\n ...rest\n } = props;\n return (\n <RACNumberField\n {...rest}\n className={[s.field, width === 'fit' && s.fit, fieldClasses.root, className]\n .filter(Boolean)\n .join(' ')}\n >\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <Group className={ghost ? `${s.frame} ${s.ghost}` : s.frame}>\n <RACInput ref={ref} placeholder={placeholder} />\n {!hideSteppers && (\n <div className={s.steppers}>\n <RACButton slot=\"increment\" className={s.stepper} aria-label=\"Increment\">▲</RACButton>\n <RACButton slot=\"decrement\" className={s.stepper} aria-label=\"Decrement\">▼</RACButton>\n </div>\n )}\n </Group>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </RACNumberField>\n );\n});\n"],"mappings":";;;;;;;;;;;GC6Ca,IAAc,EAAW,SACpC,GACA,GACA;CACA,IAAM,EACJ,UACA,gBACA,iBACA,iBACA,UACA,gBACA,WAAQ,QACR,cACA,GAAG,MACD;AACJ,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,WAAW;GAAC,EAAE;GAAO,MAAU,SAAS,EAAE;GAAK,EAAa;GAAM;GAAU,CACzE,OAAO,QAAQ,CACf,KAAK,IAAI;YAJd;GAMG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,GAAD;IAAO,WAAW,IAAQ,GAAG,EAAE,MAAM,GAAG,EAAE,UAAU,EAAE;cAAtD,CACE,kBAAC,GAAD;KAAe;KAAkB;KAAe,CAAA,EAC/C,CAAC,KACA,kBAAC,OAAD;KAAK,WAAW,EAAE;eAAlB,CACE,kBAAC,GAAD;MAAW,MAAK;MAAY,WAAW,EAAE;MAAS,cAAW;gBAAY;MAAa,CAAA,EACtF,kBAAC,GAAD;MAAW,MAAK;MAAY,WAAW,EAAE;MAAS,cAAW;gBAAY;MAAa,CAAA,CAClF;OAEF;;GACP,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GACvD;;EAEnB"}
1
+ {"version":3,"file":"NumberField-DBowmOfe.js","names":[],"sources":["../../src/components/NumberField/NumberField.module.css","../../src/components/NumberField/NumberField.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: 4px;\n}\n\n.frame {\n display: flex;\n align-items: stretch;\n height: var(--wzl-field-h, var(--wzl-control-h));\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n line-height: 1;\n overflow: hidden;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.frame:focus-within {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .frame {\n border-color: var(--wzl-danger);\n}\n\n.field[data-disabled] .frame {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.frame input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font: inherit;\n padding: 0 var(--wzl-field-pad-x);\n text-align: right;\n -moz-appearance: textfield;\n}\n\n.frame input::-webkit-outer-spin-button,\n.frame input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.steppers {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--wzl-border);\n}\n\n.stepper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n flex: 1;\n background: transparent;\n border: none;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n font-size: var(--wzl-font-size-2xs);\n line-height: 1;\n}\n\n.stepper:not(:last-child) {\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.stepper[data-hovered]:not([data-disabled]) {\n background: var(--wzl-surface-hover);\n color: var(--wzl-fg);\n}\n\n.stepper[data-pressed]:not([data-disabled]) {\n background: var(--wzl-surface-pressed);\n}\n\n.stepper[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Mirrors the property rows' editable readout (`.readoutInput`), so a number\n sitting inside other chrome does not draw a second box around itself. */\n.ghost {\n height: auto;\n background: transparent;\n border-color: transparent;\n color: var(--wzl-accent);\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.ghost input {\n padding: 1px 0;\n}\n\n.ghost:hover {\n background: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.ghost:focus-within {\n background: var(--wzl-surface-sunken);\n border-color: var(--wzl-focus-ring);\n box-shadow: none;\n}\n\n/* `width='fit'` states the field's width in characters: left alone an `<input>`\n asks for its 20-character intrinsic width, wider than most rows it sits in. */\n.fit {\n width: auto;\n max-width: 100%;\n align-items: flex-start;\n}\n\n.fit .frame {\n width: fit-content;\n max-width: 100%;\n}\n\n.fit .frame input {\n flex: 0 0 auto;\n width: var(--wzl-number-field-width, 9ch);\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n NumberField as RACNumberField,\n Label,\n Input as RACInput,\n Group,\n Button as RACButton,\n Text,\n FieldError,\n type NumberFieldProps as RACNumberFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './NumberField.module.css';\n\n/** Props for {@link NumberField}, on top of React Aria's `NumberField` props. */\nexport type NumberFieldProps = Omit<RACNumberFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n /** Hide the up/down stepper buttons. Defaults to false. */\n hideSteppers?: boolean;\n /** Render with no box until focused — the readout treatment the property\n * rows use, for a value that sits inside other chrome rather than in a form. */\n ghost?: boolean;\n /** Native input placeholder — e.g. `'Mixed'` for a multi-selection\n * editor with no shared value. */\n placeholder?: string;\n /**\n * `'fill'` (the default) takes the width of whatever row the field sits in.\n * `'fit'` sizes it to `--wzl-number-field-width` (`9ch` by default) plus its\n * own chrome, rather than to the input's 20-character intrinsic width.\n */\n width?: 'fill' | 'fit';\n className?: string;\n};\n\n/**\n * Numeric input with stepper buttons, wrapping React Aria's NumberField and\n * supplying the same label / description / errorMessage slots as `Input`.\n * Arrow keys and scroll step the value; parsing and formatting are React\n * Aria's.\n *\n * `ref` forwards to the underlying `<input>`.\n */\nexport const NumberField = forwardRef(function NumberField(\n props: NumberFieldProps,\n ref: Ref<HTMLInputElement>,\n) {\n const {\n label,\n description,\n errorMessage,\n hideSteppers,\n ghost,\n placeholder,\n width = 'fill',\n className,\n ...rest\n } = props;\n return (\n <RACNumberField\n {...rest}\n className={[s.field, width === 'fit' && s.fit, fieldClasses.root, className]\n .filter(Boolean)\n .join(' ')}\n >\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <Group className={ghost ? `${s.frame} ${s.ghost}` : s.frame}>\n <RACInput ref={ref} placeholder={placeholder} />\n {!hideSteppers && (\n <div className={s.steppers}>\n <RACButton slot=\"increment\" className={s.stepper} aria-label=\"Increment\">▲</RACButton>\n <RACButton slot=\"decrement\" className={s.stepper} aria-label=\"Decrement\">▼</RACButton>\n </div>\n )}\n </Group>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </RACNumberField>\n );\n});\n"],"mappings":";;;;;;;;;;;GC6Ca,IAAc,EAAW,SACpC,GACA,GACA;CACA,IAAM,EACJ,UACA,gBACA,iBACA,iBACA,UACA,gBACA,WAAQ,QACR,cACA,GAAG,MACD;AACJ,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,WAAW;GAAC,EAAE;GAAO,MAAU,SAAS,EAAE;GAAK,EAAa;GAAM;GAAU,CACzE,OAAO,QAAQ,CACf,KAAK,IAAI;YAJd;GAMG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,GAAD;IAAO,WAAW,IAAQ,GAAG,EAAE,MAAM,GAAG,EAAE,UAAU,EAAE;cAAtD,CACE,kBAAC,GAAD;KAAe;KAAkB;KAAe,CAAA,EAC/C,CAAC,KACA,kBAAC,OAAD;KAAK,WAAW,EAAE;eAAlB,CACE,kBAAC,GAAD;MAAW,MAAK;MAAY,WAAW,EAAE;MAAS,cAAW;gBAAY;MAAa,CAAA,EACtF,kBAAC,GAAD;MAAW,MAAK;MAAY,WAAW,EAAE;MAAS,cAAW;gBAAY;MAAa,CAAA,CAClF;OAEF;;GACP,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GACvD;;EAEnB"}
@@ -1,5 +1,5 @@
1
- import { t as e } from "./useRovingTabIndex-DqXFNDaD.js";
2
- import { t } from "./segmentedControl.module-pcPOsNey.js";
1
+ import { t as e } from "./useRovingTabIndex-Dkpf_KhN.js";
2
+ import { t } from "./segmentedControl.module-BgvAPkSZ.js";
3
3
  import { jsx as n } from "react/jsx-runtime";
4
4
  import "react";
5
5
  //#region src/components/OptionsBar/OptionsBar.tsx
@@ -42,4 +42,4 @@ function r(r) {
42
42
  //#endregion
43
43
  export { r as t };
44
44
 
45
- //# sourceMappingURL=OptionsBar-BXyk_5gT.js.map
45
+ //# sourceMappingURL=OptionsBar-D_Cd3ZYs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"OptionsBar-BXyk_5gT.js","names":[],"sources":["../../src/components/OptionsBar/OptionsBar.tsx"],"sourcesContent":["import { type CSSProperties, type ReactElement, type ReactNode } from 'react';\nimport s from '../segmentedControl.module.css';\nimport { useRovingTabIndex } from '../../useRovingTabIndex';\n\n/**\n * One segment of an {@link OptionsBar}. Unlike `ToggleBar`, each item carries\n * its own selected state and its own `onChange`.\n */\nexport type OptionsBarItem<V extends string | number = string> = {\n value: V;\n label?: ReactNode;\n ariaLabel?: string;\n disabled?: boolean;\n selected: boolean;\n onChange: (next: boolean) => void;\n};\n\n/** Segment height and type scale for an {@link OptionsBar}. */\nexport type OptionsBarSize = 'sm' | 'md';\n/** Visual treatment of an {@link OptionsBar}. */\nexport type OptionsBarVariant = 'default' | 'minimal';\n\n/** Props for {@link OptionsBar}. */\nexport type OptionsBarProps<V extends string | number = string> = {\n items: readonly OptionsBarItem<V>[];\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?: OptionsBarSize;\n /** Visual variant. `minimal` strips the pill track and glass treatment;\n * selection becomes a flat accent. For dense diagnostic surfaces. */\n variant?: OptionsBarVariant;\n};\n\n/**\n * Segmented strip of independent on/off options, each owning its own state.\n * Use it when the segments are unrelated booleans; use `ToggleBar` when they\n * are values of one setting.\n */\nexport function OptionsBar<V extends string | number = string>(props: OptionsBarProps<V>): ReactElement {\n const { items, ariaLabel, className, height, size, variant } = props;\n\n const toggle = (index: number) => {\n const item = items[index];\n if (item.disabled) return;\n item.onChange(!item.selected);\n };\n\n const roving = useRovingTabIndex({ items, itemClassName: s.segment, onActivate: toggle });\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=\"group\"\n aria-label={ariaLabel}\n style={style}\n >\n {items.map((item, i) => {\n const cls = `${s.segment}${item.selected ? ` ${s.segmentSelected}` : ''}`;\n return (\n <button\n key={item.value}\n type=\"button\"\n aria-pressed={item.selected}\n aria-label={item.ariaLabel}\n disabled={item.disabled}\n tabIndex={roving.tabIndexFor(i)}\n className={cls}\n onClick={() => toggle(i)}\n onKeyDown={roving.onKeyDown(i)}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;AAyCA,SAAgB,EAA+C,GAAyC;CACtG,IAAM,EAAE,UAAO,cAAW,cAAW,WAAQ,SAAM,eAAY,GAEzD,KAAU,MAAkB;EAChC,IAAM,IAAO,EAAM;AACf,IAAK,YACT,EAAK,SAAS,CAAC,EAAK,SAAS;IAGzB,IAAS,EAAkB;EAAE;EAAO,eAAe,EAAE;EAAS,YAAY;EAAQ,CAAC,EAEnF,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,MAAK;EACL,cAAY;EACL;YAEN,EAAM,KAAK,GAAM,MAAM;GACtB,IAAM,IAAM,GAAG,EAAE,UAAU,EAAK,WAAW,IAAI,EAAE,oBAAoB;AACrE,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,gBAAc,EAAK;IACnB,cAAY,EAAK;IACjB,UAAU,EAAK;IACf,UAAU,EAAO,YAAY,EAAE;IAC/B,WAAW;IACX,eAAe,EAAO,EAAE;IACxB,WAAW,EAAO,UAAU,EAAE;cAE7B,EAAK;IACC,EAXF,EAAK,MAWH;IAEX;EACE,CAAA"}
1
+ {"version":3,"file":"OptionsBar-D_Cd3ZYs.js","names":[],"sources":["../../src/components/OptionsBar/OptionsBar.tsx"],"sourcesContent":["import { type CSSProperties, type ReactElement, type ReactNode } from 'react';\nimport s from '../segmentedControl.module.css';\nimport { useRovingTabIndex } from '../../useRovingTabIndex';\n\n/**\n * One segment of an {@link OptionsBar}. Unlike `ToggleBar`, each item carries\n * its own selected state and its own `onChange`.\n */\nexport type OptionsBarItem<V extends string | number = string> = {\n value: V;\n label?: ReactNode;\n ariaLabel?: string;\n disabled?: boolean;\n selected: boolean;\n onChange: (next: boolean) => void;\n};\n\n/** Segment height and type scale for an {@link OptionsBar}. */\nexport type OptionsBarSize = 'sm' | 'md';\n/** Visual treatment of an {@link OptionsBar}. */\nexport type OptionsBarVariant = 'default' | 'minimal';\n\n/** Props for {@link OptionsBar}. */\nexport type OptionsBarProps<V extends string | number = string> = {\n items: readonly OptionsBarItem<V>[];\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?: OptionsBarSize;\n /** Visual variant. `minimal` strips the pill track and glass treatment;\n * selection becomes a flat accent. For dense diagnostic surfaces. */\n variant?: OptionsBarVariant;\n};\n\n/**\n * Segmented strip of independent on/off options, each owning its own state.\n * Use it when the segments are unrelated booleans; use `ToggleBar` when they\n * are values of one setting.\n */\nexport function OptionsBar<V extends string | number = string>(props: OptionsBarProps<V>): ReactElement {\n const { items, ariaLabel, className, height, size, variant } = props;\n\n const toggle = (index: number) => {\n const item = items[index];\n if (item.disabled) return;\n item.onChange(!item.selected);\n };\n\n const roving = useRovingTabIndex({ items, itemClassName: s.segment, onActivate: toggle });\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=\"group\"\n aria-label={ariaLabel}\n style={style}\n >\n {items.map((item, i) => {\n const cls = `${s.segment}${item.selected ? ` ${s.segmentSelected}` : ''}`;\n return (\n <button\n key={item.value}\n type=\"button\"\n aria-pressed={item.selected}\n aria-label={item.ariaLabel}\n disabled={item.disabled}\n tabIndex={roving.tabIndexFor(i)}\n className={cls}\n onClick={() => toggle(i)}\n onKeyDown={roving.onKeyDown(i)}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;AAyCA,SAAgB,EAA+C,GAAyC;CACtG,IAAM,EAAE,UAAO,cAAW,cAAW,WAAQ,SAAM,eAAY,GAEzD,KAAU,MAAkB;EAChC,IAAM,IAAO,EAAM;AACf,IAAK,YACT,EAAK,SAAS,CAAC,EAAK,SAAS;IAGzB,IAAS,EAAkB;EAAE;EAAO,eAAe,EAAE;EAAS,YAAY;EAAQ,CAAC,EAEnF,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,MAAK;EACL,cAAY;EACL;YAEN,EAAM,KAAK,GAAM,MAAM;GACtB,IAAM,IAAM,GAAG,EAAE,UAAU,EAAK,WAAW,IAAI,EAAE,oBAAoB;AACrE,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,gBAAc,EAAK;IACnB,cAAY,EAAK;IACjB,UAAU,EAAK;IACf,UAAU,EAAO,YAAY,EAAE;IAC/B,WAAW;IACX,eAAe,EAAO,EAAE;IACxB,WAAW,EAAO,UAAU,EAAE;cAE7B,EAAK;IACC,EAXF,EAAK,MAWH;IAEX;EACE,CAAA"}
@@ -1,8 +1,8 @@
1
1
  import { n as e, t } from "./Icon-DBXtBB1f.js";
2
- import { t as n } from "./ToggleBar-fQGk_Wf_.js";
3
- import { t as r } from "./ColorField-CCBSv8dp.js";
4
- import { r as i } from "./GradientEditor-D1IVEkHO.js";
5
- import { t as a } from "./PatternPicker-CPFEhpXD.js";
2
+ import { t as n } from "./ToggleBar-Bxwv6xvj.js";
3
+ import { t as r } from "./ColorField-C20aRvCg.js";
4
+ import { r as i } from "./GradientEditor-BLpAtMDK.js";
5
+ import { t as a } from "./PatternPicker-Ba22Bf4L.js";
6
6
  import { jsx as o, jsxs as s } from "react/jsx-runtime";
7
7
  import { getPaintKind as c, listPaintKinds as l, withGradientKind as u } from "@weasel-js/core";
8
8
  import { useCallback as d, useRef as f } from "react";
@@ -115,4 +115,4 @@ function b(b) {
115
115
  //#endregion
116
116
  export { b as t };
117
117
 
118
- //# sourceMappingURL=PaintInput-DCgWtKxw.js.map
118
+ //# sourceMappingURL=PaintInput-B7AScmHN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PaintInput-DCgWtKxw.js","names":[],"sources":["../../src/components/PaintInput/PaintInput.module.css","../../src/components/PaintInput/PaintInput.tsx"],"sourcesContent":["/* Kind switch stacked over whichever paint body it selects. */\n.root {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-inline-size: 0;\n}\n\n/* A value is in effect but nobody chose it. Never louder than mixed. */\n.unset {\n opacity: 0.55;\n}\n","import { useCallback, useRef, type ReactElement } from 'react';\nimport {\n getPaintKind,\n listPaintKinds,\n withGradientKind,\n type FillStyle,\n type GradientFill,\n type GradientKind,\n type PaintKind,\n} from '@weasel-js/core';\nimport { Icon } from '../../icons/Icon';\nimport { ICON_PATHS, type IconName } from '../../icons/paths';\nimport { ColorField } from '../ColorField';\nimport { GradientEditor } from '../GradientEditor';\nimport { PatternPicker, type PatternFill } from '../PatternPicker';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport s from './PaintInput.module.css';\n\n/** What a kind with no color of its own seeds from. */\nconst FALLBACK_COLOR = '#000000ff';\n\n/** The bar's answer for \"no paint at all\". Not a registered kind — absence\n * has no seed, no color and nothing to render — so it is this control's own\n * segment rather than a registry entry. */\nconst NONE = 'none';\n\nconst GRADIENT_KINDS: readonly string[] = [\n 'linear-gradient',\n 'radial-gradient',\n 'conic-gradient',\n];\n\nfunction isGradient(paint: FillStyle | undefined): paint is GradientFill {\n return paint !== undefined && GRADIENT_KINDS.includes(paint.fill ?? '');\n}\n\nfunction isPattern(paint: FillStyle | undefined): paint is PatternFill {\n return paint?.fill === 'pattern';\n}\n\n/** A paint's own discriminant. The solid member leaves `fill` off, and an\n * explicit `null` is the absence of paint rather than an unknown one. */\nfunction kindOf(paint: FillStyle | null | undefined): PaintKind | null {\n if (paint === null) return NONE;\n if (paint === undefined) return null;\n return paint.fill ?? 'solid';\n}\n\n/** Props for {@link PaintInput}. `onInput` fires throughout a gesture and\n * `onChange` once at its end. */\nexport interface PaintInputProps {\n /** The paint being edited. `null` is an explicit \"no paint\"; `undefined`\n * is no value to show. */\n value: FillStyle | null | undefined;\n /** Indeterminate presentation: no kind lit, and the body withheld because\n * there is no single paint to show. */\n mixed?: boolean;\n /** Dim the control — a value is in effect but was never chosen. */\n unset?: boolean;\n /** Restrict the kind bar. Default: every registered kind. */\n kinds?: readonly PaintKind[];\n /** Offer \"None\". Default `true` — the bar answers \"what kind of paint is\n * this?\" and no-paint is one of the answers. Turn it off where the slot\n * cannot express absence. */\n allowNone?: boolean;\n onInput?: (next: FillStyle | null) => void;\n onChange: (next: FillStyle | null) => void;\n /** Names the paint this control edits — `Fill`, `Color`. Carried by the\n * solid body's swatch, which is the part with a value to announce. */\n 'aria-label'?: string;\n className?: string;\n}\n\n/**\n * Editor for a whole `FillStyle` — a kind bar over whichever body that kind\n * wants.\n *\n * The bar is driven by the paint-kind registry rather than a fixed list, so a\n * consumer's registered kind appears in it and renders that entry's `Editor`.\n * The five built-in kinds keep their branch here: their editors live in this\n * package and `@weasel-js/core`, which owns the registry, cannot import it.\n *\n * Geometry is deliberately absent — on a canvas it belongs on the artwork,\n * via `<SceneGradientHandles>`.\n */\nexport function PaintInput(props: PaintInputProps): ReactElement {\n const {\n value, mixed = false, unset = false, kinds, allowNone = true,\n onInput, onChange, className,\n } = props;\n const ariaLabel = props['aria-label'];\n\n // Per-kind memory, so linear → solid → linear comes back with its stops.\n // A ref, not state: it must not survive the control being pointed at a\n // different selection, and nothing renders off it directly.\n const remembered = useRef(new Map<PaintKind, FillStyle>());\n\n const active = mixed ? null : kindOf(value);\n if (value != null && active !== null) remembered.current.set(active, value);\n\n const entries = listPaintKinds().filter(\n (entry) => kinds === undefined || kinds.includes(entry.id),\n );\n const segment = (id: string, label: string, icon: string | undefined): ToggleBarItem<PaintKind> =>\n icon && icon in ICON_PATHS\n // Icon-only: six labelled segments do not fit a property row, and the\n // full label stays the accessible name.\n // 14 to match the panel's other glyph segments. A `sm` bar is 17px tall\n // over 1px of padding, so 15 fills the segment box edge to edge.\n ? { value: id, label: <Icon name={icon as IconName} size={14} />, ariaLabel: label }\n : { value: id, label };\n\n const items: readonly ToggleBarItem<PaintKind>[] = [\n ...(allowNone ? [segment(NONE, 'None', 'paintNone')] : []),\n ...entries.map((entry) => segment(entry.id, entry.label, entry.icon)),\n ];\n\n /** The color a switch seeds from: whatever the current paint shows. */\n const currentColor = useCallback((): string => {\n if (value == null) return FALLBACK_COLOR;\n return getPaintKind(kindOf(value) ?? 'solid')?.colorOf(value) ?? FALLBACK_COLOR;\n }, [value]);\n\n const switchKind = (kind: PaintKind): void => {\n if (kind === active) return;\n if (kind === NONE) {\n onChange(null);\n return;\n }\n const seen = remembered.current.get(kind);\n if (seen !== undefined) {\n onChange(seen);\n return;\n }\n if (value != null && isGradient(value) && GRADIENT_KINDS.includes(kind)) {\n onChange(withGradientKind(value, kind as GradientKind));\n return;\n }\n const entry = getPaintKind(kind);\n if (entry) onChange(entry.seed(currentColor()));\n };\n\n return (\n <div\n className={[s.root, unset && s.unset, className].filter(Boolean).join(' ')}\n title={unset ? 'Not set' : undefined}\n >\n <ToggleBar<PaintKind>\n items={items}\n value={active}\n size=\"sm\"\n ariaLabel=\"Paint kind\"\n onChange={(kind) => kind && switchKind(kind)}\n />\n {renderBody()}\n </div>\n );\n\n function renderBody(): ReactElement | null {\n if (mixed || value === undefined) {\n return (\n <ColorField\n mixed\n aria-label={ariaLabel}\n onChange={(color) => onChange({ fill: 'solid', color })}\n />\n );\n }\n // Absence has nothing to edit. The lit segment is the whole statement.\n if (value === null) return null;\n // A registered `Editor` wins even for a built-in id: re-registering one is\n // how a consumer replaces a kit control, and the built-in branches below\n // are the fallback for the ids that ship without an entry editor.\n const entry = getPaintKind(kindOf(value) ?? 'solid');\n if (entry?.Editor) {\n const Editor = entry.Editor;\n return <Editor value={value} onInput={onInput} onChange={onChange} />;\n }\n if (isGradient(value)) {\n return (\n <GradientEditor\n value={value}\n kindSwitch={false}\n onInput={onInput}\n onChange={onChange}\n />\n );\n }\n if (isPattern(value)) {\n return <PatternPicker value={value} color={currentColor()} onChange={onChange} />;\n }\n return (\n <ColorField\n value={currentColor()}\n alpha\n aria-label={ariaLabel}\n onInput={(color) => onInput?.({ fill: 'solid', color })}\n onChange={(color) => onChange({ fill: 'solid', color })}\n />\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PaintInput-B7AScmHN.js","names":[],"sources":["../../src/components/PaintInput/PaintInput.module.css","../../src/components/PaintInput/PaintInput.tsx"],"sourcesContent":["/* Kind switch stacked over whichever paint body it selects. */\n.root {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-inline-size: 0;\n}\n\n/* A value is in effect but nobody chose it. Never louder than mixed. */\n.unset {\n opacity: 0.55;\n}\n","import { useCallback, useRef, type ReactElement } from 'react';\nimport {\n getPaintKind,\n listPaintKinds,\n withGradientKind,\n type FillStyle,\n type GradientFill,\n type GradientKind,\n type PaintKind,\n} from '@weasel-js/core';\nimport { Icon } from '../../icons/Icon';\nimport { ICON_PATHS, type IconName } from '../../icons/paths';\nimport { ColorField } from '../ColorField';\nimport { GradientEditor } from '../GradientEditor';\nimport { PatternPicker, type PatternFill } from '../PatternPicker';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport s from './PaintInput.module.css';\n\n/** What a kind with no color of its own seeds from. */\nconst FALLBACK_COLOR = '#000000ff';\n\n/** The bar's answer for \"no paint at all\". Not a registered kind — absence\n * has no seed, no color and nothing to render — so it is this control's own\n * segment rather than a registry entry. */\nconst NONE = 'none';\n\nconst GRADIENT_KINDS: readonly string[] = [\n 'linear-gradient',\n 'radial-gradient',\n 'conic-gradient',\n];\n\nfunction isGradient(paint: FillStyle | undefined): paint is GradientFill {\n return paint !== undefined && GRADIENT_KINDS.includes(paint.fill ?? '');\n}\n\nfunction isPattern(paint: FillStyle | undefined): paint is PatternFill {\n return paint?.fill === 'pattern';\n}\n\n/** A paint's own discriminant. The solid member leaves `fill` off, and an\n * explicit `null` is the absence of paint rather than an unknown one. */\nfunction kindOf(paint: FillStyle | null | undefined): PaintKind | null {\n if (paint === null) return NONE;\n if (paint === undefined) return null;\n return paint.fill ?? 'solid';\n}\n\n/** Props for {@link PaintInput}. `onInput` fires throughout a gesture and\n * `onChange` once at its end. */\nexport interface PaintInputProps {\n /** The paint being edited. `null` is an explicit \"no paint\"; `undefined`\n * is no value to show. */\n value: FillStyle | null | undefined;\n /** Indeterminate presentation: no kind lit, and the body withheld because\n * there is no single paint to show. */\n mixed?: boolean;\n /** Dim the control — a value is in effect but was never chosen. */\n unset?: boolean;\n /** Restrict the kind bar. Default: every registered kind. */\n kinds?: readonly PaintKind[];\n /** Offer \"None\". Default `true` — the bar answers \"what kind of paint is\n * this?\" and no-paint is one of the answers. Turn it off where the slot\n * cannot express absence. */\n allowNone?: boolean;\n onInput?: (next: FillStyle | null) => void;\n onChange: (next: FillStyle | null) => void;\n /** Names the paint this control edits — `Fill`, `Color`. Carried by the\n * solid body's swatch, which is the part with a value to announce. */\n 'aria-label'?: string;\n className?: string;\n}\n\n/**\n * Editor for a whole `FillStyle` — a kind bar over whichever body that kind\n * wants.\n *\n * The bar is driven by the paint-kind registry rather than a fixed list, so a\n * consumer's registered kind appears in it and renders that entry's `Editor`.\n * The five built-in kinds keep their branch here: their editors live in this\n * package and `@weasel-js/core`, which owns the registry, cannot import it.\n *\n * Geometry is deliberately absent — on a canvas it belongs on the artwork,\n * via `<SceneGradientHandles>`.\n */\nexport function PaintInput(props: PaintInputProps): ReactElement {\n const {\n value, mixed = false, unset = false, kinds, allowNone = true,\n onInput, onChange, className,\n } = props;\n const ariaLabel = props['aria-label'];\n\n // Per-kind memory, so linear → solid → linear comes back with its stops.\n // A ref, not state: it must not survive the control being pointed at a\n // different selection, and nothing renders off it directly.\n const remembered = useRef(new Map<PaintKind, FillStyle>());\n\n const active = mixed ? null : kindOf(value);\n if (value != null && active !== null) remembered.current.set(active, value);\n\n const entries = listPaintKinds().filter(\n (entry) => kinds === undefined || kinds.includes(entry.id),\n );\n const segment = (id: string, label: string, icon: string | undefined): ToggleBarItem<PaintKind> =>\n icon && icon in ICON_PATHS\n // Icon-only: six labelled segments do not fit a property row, and the\n // full label stays the accessible name.\n // 14 to match the panel's other glyph segments. A `sm` bar is 17px tall\n // over 1px of padding, so 15 fills the segment box edge to edge.\n ? { value: id, label: <Icon name={icon as IconName} size={14} />, ariaLabel: label }\n : { value: id, label };\n\n const items: readonly ToggleBarItem<PaintKind>[] = [\n ...(allowNone ? [segment(NONE, 'None', 'paintNone')] : []),\n ...entries.map((entry) => segment(entry.id, entry.label, entry.icon)),\n ];\n\n /** The color a switch seeds from: whatever the current paint shows. */\n const currentColor = useCallback((): string => {\n if (value == null) return FALLBACK_COLOR;\n return getPaintKind(kindOf(value) ?? 'solid')?.colorOf(value) ?? FALLBACK_COLOR;\n }, [value]);\n\n const switchKind = (kind: PaintKind): void => {\n if (kind === active) return;\n if (kind === NONE) {\n onChange(null);\n return;\n }\n const seen = remembered.current.get(kind);\n if (seen !== undefined) {\n onChange(seen);\n return;\n }\n if (value != null && isGradient(value) && GRADIENT_KINDS.includes(kind)) {\n onChange(withGradientKind(value, kind as GradientKind));\n return;\n }\n const entry = getPaintKind(kind);\n if (entry) onChange(entry.seed(currentColor()));\n };\n\n return (\n <div\n className={[s.root, unset && s.unset, className].filter(Boolean).join(' ')}\n title={unset ? 'Not set' : undefined}\n >\n <ToggleBar<PaintKind>\n items={items}\n value={active}\n size=\"sm\"\n ariaLabel=\"Paint kind\"\n onChange={(kind) => kind && switchKind(kind)}\n />\n {renderBody()}\n </div>\n );\n\n function renderBody(): ReactElement | null {\n if (mixed || value === undefined) {\n return (\n <ColorField\n mixed\n aria-label={ariaLabel}\n onChange={(color) => onChange({ fill: 'solid', color })}\n />\n );\n }\n // Absence has nothing to edit. The lit segment is the whole statement.\n if (value === null) return null;\n // A registered `Editor` wins even for a built-in id: re-registering one is\n // how a consumer replaces a kit control, and the built-in branches below\n // are the fallback for the ids that ship without an entry editor.\n const entry = getPaintKind(kindOf(value) ?? 'solid');\n if (entry?.Editor) {\n const Editor = entry.Editor;\n return <Editor value={value} onInput={onInput} onChange={onChange} />;\n }\n if (isGradient(value)) {\n return (\n <GradientEditor\n value={value}\n kindSwitch={false}\n onInput={onInput}\n onChange={onChange}\n />\n );\n }\n if (isPattern(value)) {\n return <PatternPicker value={value} color={currentColor()} onChange={onChange} />;\n }\n return (\n <ColorField\n value={currentColor()}\n alpha\n aria-label={ariaLabel}\n onInput={(color) => onInput?.({ fill: 'solid', color })}\n onChange={(color) => onChange({ fill: 'solid', color })}\n />\n );\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./ToggleBar-fQGk_Wf_.js";
1
+ import { t as e } from "./ToggleBar-Bxwv6xvj.js";
2
2
  import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
3
  import { tilePreviewCssUrl as r } from "@weasel-js/svg";
4
4
  var i = {
@@ -90,4 +90,4 @@ function d(d) {
90
90
  //#endregion
91
91
  export { u as n, d as t };
92
92
 
93
- //# sourceMappingURL=PatternPicker-CPFEhpXD.js.map
93
+ //# sourceMappingURL=PatternPicker-Ba22Bf4L.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PatternPicker-CPFEhpXD.js","names":[],"sources":["../../src/components/PatternPicker/PatternPicker.module.css","../../src/components/PatternPicker/PatternPicker.tsx"],"sourcesContent":["/* Tile grid over the size switch. */\n.root {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-inline-size: 0;\n}\n\n.grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--wzl-swatch-size, 28px), 1fr));\n gap: 0;\n}\n\n/* The swatch's whole face is its own tile, so it must not carry a flat\n background — and the tile repeats at its own scale, not the swatch's. */\n.swatch {\n width: 100%;\n aspect-ratio: 1 / 1;\n min-width: 0;\n padding: 0;\n border: 0.5px solid var(--wzl-border);\n border-radius: 0;\n cursor: pointer;\n background-color: var(--wzl-surface-sunken);\n background-image: var(--tile-preview);\n background-repeat: repeat;\n background-size: 16px 16px;\n background-clip: padding-box;\n}\n\n.swatch:hover {\n border-color: var(--wzl-fg-muted);\n}\n\n.swatchActive {\n border-color: var(--wzl-accent);\n box-shadow: 0 0 0 1px var(--wzl-accent);\n}\n","import type { ReactElement } from 'react';\nimport type { CSSProperties } from 'react';\nimport type { FillStyle, TilePatternSpec } from '@weasel-js/core';\nimport { tilePreviewCssUrl } from '@weasel-js/svg';\nimport { ToggleBar } from '../ToggleBar';\nimport s from './PatternPicker.module.css';\n\n/** The `pattern` member of the paint union. */\nexport type PatternFill = Extract<FillStyle, { fill: 'pattern' }>;\n\nconst TILES: readonly TilePatternSpec['tile'][] = ['hatch', 'crosshatch', 'dots', 'chunks'];\n\n/** Tile sizes the picker offers, coarse enough that each reads as a\n * distinct texture rather than a nudge. */\nconst TILE_SIZES: readonly { value: number; label: string }[] = [\n { value: 4, label: 'S' },\n { value: 8, label: 'M' },\n { value: 16, label: 'L' },\n { value: 32, label: 'XL' },\n];\n\nconst DEFAULT_TILE_SIZE = 8;\n\n/** Every swatch previews at one size so the grid compares tiles, not scales. */\nconst PREVIEW_SIZE = 16;\n\n/** The spec a pattern paint carries, if it carries one. A consumer-built\n * `TextureHandle` has no spec, so the picker shows no tile selected and\n * replaces it wholesale on the next click. */\nfunction specOf(fill: PatternFill): TilePatternSpec | null {\n return 'tile' in fill.pattern ? fill.pattern : null;\n}\n\n/**\n * A pattern seeded from a color. `units: 'bounds'` anchors the tile to the\n * node's box, so the texture travels with the shape.\n */\nexport function seedPattern(\n tile: TilePatternSpec['tile'],\n from: string,\n size: number = DEFAULT_TILE_SIZE,\n): PatternFill {\n const spec: TilePatternSpec = { tile, color: from, size };\n // `chunks` is a scatter over a ground rather than a line texture; without a\n // background it reads as loose specks on whatever sits beneath.\n if (tile === 'chunks') spec.bg = '#00000000';\n return { fill: 'pattern', pattern: spec, units: 'bounds' };\n}\n\n/** Props for {@link PatternPicker}. */\nexport interface PatternPickerProps {\n /** The pattern paint being edited. */\n value: PatternFill;\n /** The color new tiles are seeded with — the paint's own, normally. */\n color: string;\n /** Committed value: one call per click. A tile pick and a size change are\n * each a complete gesture, so there is no `onInput`. */\n onChange: (next: PatternFill) => void;\n className?: string;\n}\n\n/**\n * Grid of tile swatches over a size switch, each swatch previewing the real\n * tile at the current color.\n *\n * The preview shares its shape mapper with what the renderer paints, so a\n * swatch cannot drift from the texture it selects.\n */\nexport function PatternPicker(props: PatternPickerProps): ReactElement {\n const { value, color, onChange, className } = props;\n const spec = specOf(value);\n const size = spec?.size ?? DEFAULT_TILE_SIZE;\n\n const pick = (tile: TilePatternSpec['tile']): void => {\n onChange(seedPattern(tile, color, size));\n };\n\n const resize = (nextSize: number): void => {\n if (!spec) return;\n onChange({ ...value, pattern: { ...spec, size: nextSize } });\n };\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n <div className={s.grid}>\n {TILES.map((tile) => {\n const preview: TilePatternSpec = { tile, color, size: PREVIEW_SIZE };\n if (tile === 'chunks') preview.bg = '#00000000';\n const selected = spec?.tile === tile;\n return (\n <button\n key={tile}\n type=\"button\"\n className={[s.swatch, selected && s.swatchActive].filter(Boolean).join(' ')}\n style={{ ['--tile-preview' as string]: tilePreviewCssUrl(preview) } as CSSProperties}\n title={tile}\n aria-label={tile}\n aria-pressed={selected}\n onClick={() => pick(tile)}\n />\n );\n })}\n </div>\n <ToggleBar<number>\n items={TILE_SIZES}\n value={size}\n size=\"sm\"\n ariaLabel=\"Tile size\"\n onChange={(v) => v != null && resize(v)}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;GCUM,IAA4C;CAAC;CAAS;CAAc;CAAQ;CAAS,EAIrF,IAA0D;CAC9D;EAAE,OAAO;EAAG,OAAO;EAAK;CACxB;EAAE,OAAO;EAAG,OAAO;EAAK;CACxB;EAAE,OAAO;EAAI,OAAO;EAAK;CACzB;EAAE,OAAO;EAAI,OAAO;EAAM;CAC3B,EAEK,IAAoB,GAGpB,IAAe;AAKrB,SAAS,EAAO,GAA2C;AACzD,QAAO,UAAU,EAAK,UAAU,EAAK,UAAU;;AAOjD,SAAgB,EACd,GACA,GACA,IAAe,GACF;CACb,IAAM,IAAwB;EAAE;EAAM,OAAO;EAAM;EAAM;AAIzD,QADI,MAAS,aAAU,EAAK,KAAK,cAC1B;EAAE,MAAM;EAAW,SAAS;EAAM,OAAO;EAAU;;AAsB5D,SAAgB,EAAc,GAAyC;CACrE,IAAM,EAAE,UAAO,UAAO,aAAU,iBAAc,GACxC,IAAO,EAAO,EAAM,EACpB,IAAO,GAAM,QAAQ,GAErB,KAAQ,MAAwC;AACpD,IAAS,EAAY,GAAM,GAAO,EAAK,CAAC;IAGpC,KAAU,MAA2B;AACpC,OACL,EAAS;GAAE,GAAG;GAAO,SAAS;IAAE,GAAG;IAAM,MAAM;IAAU;GAAE,CAAC;;AAG9D,QACE,kBAAC,OAAD;EAAK,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA7D,CACE,kBAAC,OAAD;GAAK,WAAW,EAAE;aACf,EAAM,KAAK,MAAS;IACnB,IAAM,IAA2B;KAAE;KAAM;KAAO,MAAM;KAAc;AACpE,IAAI,MAAS,aAAU,EAAQ,KAAK;IACpC,IAAM,IAAW,GAAM,SAAS;AAChC,WACE,kBAAC,UAAD;KAEE,MAAK;KACL,WAAW,CAAC,EAAE,QAAQ,KAAY,EAAE,aAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;KAC3E,OAAO,EAAG,kBAA6B,EAAkB,EAAQ,EAAE;KACnE,OAAO;KACP,cAAY;KACZ,gBAAc;KACd,eAAe,EAAK,EAAK;KACzB,EARK,EAQL;KAEJ;GACE,CAAA,EACN,kBAAC,GAAD;GACE,OAAO;GACP,OAAO;GACP,MAAK;GACL,WAAU;GACV,WAAW,MAAM,KAAK,QAAQ,EAAO,EAAE;GACvC,CAAA,CACE"}
1
+ {"version":3,"file":"PatternPicker-Ba22Bf4L.js","names":[],"sources":["../../src/components/PatternPicker/PatternPicker.module.css","../../src/components/PatternPicker/PatternPicker.tsx"],"sourcesContent":["/* Tile grid over the size switch. */\n.root {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-inline-size: 0;\n}\n\n.grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--wzl-swatch-size, 28px), 1fr));\n gap: 0;\n}\n\n/* The swatch's whole face is its own tile, so it must not carry a flat\n background — and the tile repeats at its own scale, not the swatch's. */\n.swatch {\n width: 100%;\n aspect-ratio: 1 / 1;\n min-width: 0;\n padding: 0;\n border: 0.5px solid var(--wzl-border);\n border-radius: 0;\n cursor: pointer;\n background-color: var(--wzl-surface-sunken);\n background-image: var(--tile-preview);\n background-repeat: repeat;\n background-size: 16px 16px;\n background-clip: padding-box;\n}\n\n.swatch:hover {\n border-color: var(--wzl-fg-muted);\n}\n\n.swatchActive {\n border-color: var(--wzl-accent);\n box-shadow: 0 0 0 1px var(--wzl-accent);\n}\n","import type { ReactElement } from 'react';\nimport type { CSSProperties } from 'react';\nimport type { FillStyle, TilePatternSpec } from '@weasel-js/core';\nimport { tilePreviewCssUrl } from '@weasel-js/svg';\nimport { ToggleBar } from '../ToggleBar';\nimport s from './PatternPicker.module.css';\n\n/** The `pattern` member of the paint union. */\nexport type PatternFill = Extract<FillStyle, { fill: 'pattern' }>;\n\nconst TILES: readonly TilePatternSpec['tile'][] = ['hatch', 'crosshatch', 'dots', 'chunks'];\n\n/** Tile sizes the picker offers, coarse enough that each reads as a\n * distinct texture rather than a nudge. */\nconst TILE_SIZES: readonly { value: number; label: string }[] = [\n { value: 4, label: 'S' },\n { value: 8, label: 'M' },\n { value: 16, label: 'L' },\n { value: 32, label: 'XL' },\n];\n\nconst DEFAULT_TILE_SIZE = 8;\n\n/** Every swatch previews at one size so the grid compares tiles, not scales. */\nconst PREVIEW_SIZE = 16;\n\n/** The spec a pattern paint carries, if it carries one. A consumer-built\n * `TextureHandle` has no spec, so the picker shows no tile selected and\n * replaces it wholesale on the next click. */\nfunction specOf(fill: PatternFill): TilePatternSpec | null {\n return 'tile' in fill.pattern ? fill.pattern : null;\n}\n\n/**\n * A pattern seeded from a color. `units: 'bounds'` anchors the tile to the\n * node's box, so the texture travels with the shape.\n */\nexport function seedPattern(\n tile: TilePatternSpec['tile'],\n from: string,\n size: number = DEFAULT_TILE_SIZE,\n): PatternFill {\n const spec: TilePatternSpec = { tile, color: from, size };\n // `chunks` is a scatter over a ground rather than a line texture; without a\n // background it reads as loose specks on whatever sits beneath.\n if (tile === 'chunks') spec.bg = '#00000000';\n return { fill: 'pattern', pattern: spec, units: 'bounds' };\n}\n\n/** Props for {@link PatternPicker}. */\nexport interface PatternPickerProps {\n /** The pattern paint being edited. */\n value: PatternFill;\n /** The color new tiles are seeded with — the paint's own, normally. */\n color: string;\n /** Committed value: one call per click. A tile pick and a size change are\n * each a complete gesture, so there is no `onInput`. */\n onChange: (next: PatternFill) => void;\n className?: string;\n}\n\n/**\n * Grid of tile swatches over a size switch, each swatch previewing the real\n * tile at the current color.\n *\n * The preview shares its shape mapper with what the renderer paints, so a\n * swatch cannot drift from the texture it selects.\n */\nexport function PatternPicker(props: PatternPickerProps): ReactElement {\n const { value, color, onChange, className } = props;\n const spec = specOf(value);\n const size = spec?.size ?? DEFAULT_TILE_SIZE;\n\n const pick = (tile: TilePatternSpec['tile']): void => {\n onChange(seedPattern(tile, color, size));\n };\n\n const resize = (nextSize: number): void => {\n if (!spec) return;\n onChange({ ...value, pattern: { ...spec, size: nextSize } });\n };\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n <div className={s.grid}>\n {TILES.map((tile) => {\n const preview: TilePatternSpec = { tile, color, size: PREVIEW_SIZE };\n if (tile === 'chunks') preview.bg = '#00000000';\n const selected = spec?.tile === tile;\n return (\n <button\n key={tile}\n type=\"button\"\n className={[s.swatch, selected && s.swatchActive].filter(Boolean).join(' ')}\n style={{ ['--tile-preview' as string]: tilePreviewCssUrl(preview) } as CSSProperties}\n title={tile}\n aria-label={tile}\n aria-pressed={selected}\n onClick={() => pick(tile)}\n />\n );\n })}\n </div>\n <ToggleBar<number>\n items={TILE_SIZES}\n value={size}\n size=\"sm\"\n ariaLabel=\"Tile size\"\n onChange={(v) => v != null && resize(v)}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;GCUM,IAA4C;CAAC;CAAS;CAAc;CAAQ;CAAS,EAIrF,IAA0D;CAC9D;EAAE,OAAO;EAAG,OAAO;EAAK;CACxB;EAAE,OAAO;EAAG,OAAO;EAAK;CACxB;EAAE,OAAO;EAAI,OAAO;EAAK;CACzB;EAAE,OAAO;EAAI,OAAO;EAAM;CAC3B,EAEK,IAAoB,GAGpB,IAAe;AAKrB,SAAS,EAAO,GAA2C;AACzD,QAAO,UAAU,EAAK,UAAU,EAAK,UAAU;;AAOjD,SAAgB,EACd,GACA,GACA,IAAe,GACF;CACb,IAAM,IAAwB;EAAE;EAAM,OAAO;EAAM;EAAM;AAIzD,QADI,MAAS,aAAU,EAAK,KAAK,cAC1B;EAAE,MAAM;EAAW,SAAS;EAAM,OAAO;EAAU;;AAsB5D,SAAgB,EAAc,GAAyC;CACrE,IAAM,EAAE,UAAO,UAAO,aAAU,iBAAc,GACxC,IAAO,EAAO,EAAM,EACpB,IAAO,GAAM,QAAQ,GAErB,KAAQ,MAAwC;AACpD,IAAS,EAAY,GAAM,GAAO,EAAK,CAAC;IAGpC,KAAU,MAA2B;AACpC,OACL,EAAS;GAAE,GAAG;GAAO,SAAS;IAAE,GAAG;IAAM,MAAM;IAAU;GAAE,CAAC;;AAG9D,QACE,kBAAC,OAAD;EAAK,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA7D,CACE,kBAAC,OAAD;GAAK,WAAW,EAAE;aACf,EAAM,KAAK,MAAS;IACnB,IAAM,IAA2B;KAAE;KAAM;KAAO,MAAM;KAAc;AACpE,IAAI,MAAS,aAAU,EAAQ,KAAK;IACpC,IAAM,IAAW,GAAM,SAAS;AAChC,WACE,kBAAC,UAAD;KAEE,MAAK;KACL,WAAW,CAAC,EAAE,QAAQ,KAAY,EAAE,aAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;KAC3E,OAAO,EAAG,kBAA6B,EAAkB,EAAQ,EAAE;KACnE,OAAO;KACP,cAAY;KACZ,gBAAc;KACd,eAAe,EAAK,EAAK;KACzB,EARK,EAQL;KAEJ;GACE,CAAA,EACN,kBAAC,GAAD;GACE,OAAO;GACP,OAAO;GACP,MAAK;GACL,WAAU;GACV,WAAW,MAAM,KAAK,QAAQ,EAAO,EAAE;GACvC,CAAA,CACE"}
@@ -131,4 +131,4 @@ var d = {
131
131
  //#endregion
132
132
  export { l as n, u as r, f as t };
133
133
 
134
- //# sourceMappingURL=Plot2D-BtI_c25q.js.map
134
+ //# sourceMappingURL=Plot2D-C97pO13t.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Plot2D-BtI_c25q.js","names":[],"sources":["../../src/components/Plot2D/geometry.ts","../../src/components/Plot2D/Plot2D.module.css","../../src/components/Plot2D/Plot2D.tsx"],"sourcesContent":["/**\n * Coordinate transforms (model ↔ plot) for Plot2D. Pure / deterministic —\n * no DOM, no React, no state.\n *\n * Model space: caller-defined xRange × yRange. Y axis goes UP.\n * Plot space: 0..width × 0..height in CSS pixels. Y axis goes DOWN (SVG).\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\n/**\n * The caller-defined extent of model space, the coordinate system a plot's\n * data lives in.\n */\nexport interface ModelRange {\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n}\n\n/** The plot's drawing area in CSS pixels. */\nexport interface PlotSize {\n width: number;\n height: number;\n}\n\n/**\n * Maps a model-space point into plot space, flipping the y axis so model y\n * grows upward while SVG y grows downward.\n */\nexport function modelToPlot(p: Point, m: ModelRange, plot: PlotSize): Point {\n const xFrac = (p.x - m.xMin) / (m.xMax - m.xMin);\n const yFrac = (p.y - m.yMin) / (m.yMax - m.yMin);\n return {\n x: xFrac * plot.width,\n // Invert y so model y=0 sits at plot bottom (SVG y grows downward).\n y: (1 - yFrac) * plot.height,\n };\n}\n\n/** The inverse of {@link modelToPlot}. */\nexport function plotToModel(p: Point, m: ModelRange, plot: PlotSize): Point {\n const xFrac = p.x / plot.width;\n const yFrac = 1 - p.y / plot.height;\n return {\n x: m.xMin + xFrac * (m.xMax - m.xMin),\n y: m.yMin + yFrac * (m.yMax - m.yMin),\n };\n}\n",".root {\n /* Component-local vars layered over theme tokens. Consumers override\n * either the plot-* var (component scope) or the wzl-* token (app\n * scope). */\n --plot-bg: var(--wzl-surface-sunken);\n --plot-grid: var(--wzl-line);\n /* Axes get full foreground contrast — they're the plot frame, the\n * most prominent structural element after caller-rendered content. */\n --plot-axis: var(--wzl-fg);\n\n display: block;\n /* Caller-rendered marks at the very edge (e.g. CurveEditor anchors)\n * extend past the SVG box by their radius — let them paint over the\n * host page instead of being half-clipped. */\n overflow: visible;\n background: var(--plot-bg);\n user-select: none;\n touch-action: none;\n outline: none;\n}\n\n/* Keyboard-only focus ring. Caller opts in via `tabIndex`. */\n.root:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 2px;\n}\n\n.grid {\n stroke: var(--plot-grid);\n stroke-width: 1;\n fill: none;\n}\n\n.axis {\n stroke: var(--plot-axis);\n stroke-width: var(--wzl-line-width);\n fill: none;\n}\n","import {\n forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from 'react';\nimport {\n modelToPlot, plotToModel,\n type ModelRange, type Point,\n} from './geometry';\nimport s from './Plot2D.module.css';\nimport { dlog } from '../../dlog';\n\n/** Background grid configuration for a {@link Plot2D}. */\nexport interface GridSettings {\n /** Number of evenly-spaced internal grid lines per axis (excluding\n * the edges). Applied to both x and y. Default 3. */\n divisions?: number;\n /** Stroke color override. When omitted, uses `var(--plot-grid)`. */\n color?: string;\n}\n\n/** Axis-line configuration for a {@link Plot2D}. */\nexport interface AxesSettings {\n /** Stroke color override. When omitted, uses `var(--plot-axis)`. */\n color?: string;\n}\n\n/** A pointer position given in both of the plot's coordinate systems. */\nexport interface Plot2DCoords {\n plot: Point;\n model: Point;\n}\n\n/** Props for {@link Plot2D}. */\nexport interface Plot2DProps {\n width: number;\n height: number;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n /** Background grid. `false` / `null` / omitted = no grid. Pass `{}`\n * for default (3 divisions per axis) or populated GridSettings. */\n grid?: GridSettings | false | null;\n /** Axis lines. `false` / `null` = no axes. Omitted = default-styled\n * axes (on). Pass AxesSettings to customize. */\n axes?: AxesSettings | false | null;\n /** Forwarded to the underlying svg. Consumer opts into focus this way. */\n tabIndex?: number;\n className?: string;\n style?: CSSProperties;\n /** Pointer down on the SVG. Receives both plot- and model-space coords\n * pre-computed so consumers don't repeat the rect/transform dance. */\n onPointerDown?: (e: ReactPointerEvent<SVGSVGElement>, coords: Plot2DCoords) => void;\n onKeyDown?: (e: ReactKeyboardEvent<SVGSVGElement>) => void;\n children?: ReactNode;\n}\n\n/**\n * Imperative handle on a {@link Plot2D}: the SVG element, its size, and the\n * coordinate conversions, including ones that start from a raw DOM event so a\n * drag tracked on `window` can still map back into the plot.\n */\nexport interface Plot2DHandle {\n readonly svg: SVGSVGElement | null;\n plotToModel(pt: Point): Point;\n modelToPlot(pt: Point): Point;\n /** Convert a DOM event's clientX/clientY (e.g. from a window-attached\n * pointermove during drag) to plot-space. */\n clientToPlot(e: { clientX: number; clientY: number }): Point;\n /** Convenience: clientToPlot → plotToModel. */\n clientToModel(e: { clientX: number; clientY: number }): Point;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * An SVG plotting surface with an optional grid and axes. It draws the frame\n * and owns the model-space to plot-space mapping; the plotted content is\n * whatever children are passed, positioned in plot space.\n */\nexport const Plot2D = forwardRef<Plot2DHandle, Plot2DProps>(function Plot2D(props, ref) {\n const {\n width, height,\n xRange = [0, 1],\n yRange = [0, 1],\n grid,\n axes,\n tabIndex,\n className,\n style,\n onPointerDown,\n onKeyDown,\n children,\n } = props;\n\n const svgRef = useRef<SVGSVGElement | null>(null);\n\n const modelRange: ModelRange = useMemo(\n () => ({ xMin: xRange[0], xMax: xRange[1], yMin: yRange[0], yMax: yRange[1] }),\n [xRange, yRange],\n );\n\n const plotSize = useMemo(() => ({ width, height }), [width, height]);\n\n const clientToPlot = useCallback((e: { clientX: number; clientY: number }): Point => {\n const rect = svgRef.current?.getBoundingClientRect();\n const left = rect?.left ?? 0;\n const top = rect?.top ?? 0;\n return { x: e.clientX - left, y: e.clientY - top };\n }, []);\n\n useEffect(() => {\n dlog('plot2d', 'mount', { width, height });\n return () => dlog('plot2d', 'unmount');\n // Mount/unmount trace: re-running on a resize would log a spurious mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n useImperativeHandle(ref, () => ({\n get svg() { return svgRef.current; },\n plotToModel: (pt: Point) => plotToModel(pt, modelRange, plotSize),\n modelToPlot: (pt: Point) => modelToPlot(pt, modelRange, plotSize),\n clientToPlot,\n clientToModel: (e) => plotToModel(clientToPlot(e), modelRange, plotSize),\n get width() { return plotSize.width; },\n get height() { return plotSize.height; },\n }), [modelRange, plotSize, clientToPlot]);\n\n const handlePointerDown = useCallback((e: ReactPointerEvent<SVGSVGElement>) => {\n if (!onPointerDown) return;\n const plot = clientToPlot(e);\n const model = plotToModel(plot, modelRange, plotSize);\n onPointerDown(e, { plot, model });\n }, [onPointerDown, clientToPlot, modelRange, plotSize]);\n\n const cls = [s.root, className].filter(Boolean).join(' ');\n\n return (\n <svg\n ref={svgRef}\n className={cls}\n style={style}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n tabIndex={tabIndex}\n onPointerDown={onPointerDown ? handlePointerDown : undefined}\n onKeyDown={onKeyDown}\n >\n {grid && (() => {\n const divisions = grid.divisions ?? 3;\n const stroke = grid.color;\n // Evenly-spaced internal divisions, excluding the edges (which\n // belong to the axes). For divisions=3, fractions are 1/4, 2/4, 3/4.\n const fractions: number[] = [];\n for (let i = 1; i <= divisions; i++) fractions.push(i / (divisions + 1));\n return (\n <g>\n {fractions.map((f) => (\n <line\n key={`gx-${f}`}\n data-plot-element=\"grid\"\n className={s.grid}\n stroke={stroke}\n x1={f * width} x2={f * width}\n y1={0} y2={height}\n />\n ))}\n {fractions.map((f) => (\n <line\n key={`gy-${f}`}\n data-plot-element=\"grid\"\n className={s.grid}\n stroke={stroke}\n x1={0} x2={width}\n y1={f * height} y2={f * height}\n />\n ))}\n </g>\n );\n })()}\n {axes !== false && axes !== null && (() => {\n const stroke = axes?.color;\n return (\n <g>\n <line\n data-plot-element=\"axis\"\n className={s.axis}\n stroke={stroke}\n x1={0} x2={width}\n y1={height} y2={height}\n />\n <line\n data-plot-element=\"axis\"\n className={s.axis}\n stroke={stroke}\n x1={0} x2={0}\n y1={0} y2={height}\n />\n </g>\n );\n })()}\n {children}\n </svg>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"Plot2D-C97pO13t.js","names":[],"sources":["../../src/components/Plot2D/geometry.ts","../../src/components/Plot2D/Plot2D.module.css","../../src/components/Plot2D/Plot2D.tsx"],"sourcesContent":["/**\n * Coordinate transforms (model ↔ plot) for Plot2D. Pure / deterministic —\n * no DOM, no React, no state.\n *\n * Model space: caller-defined xRange × yRange. Y axis goes UP.\n * Plot space: 0..width × 0..height in CSS pixels. Y axis goes DOWN (SVG).\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\n/**\n * The caller-defined extent of model space, the coordinate system a plot's\n * data lives in.\n */\nexport interface ModelRange {\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n}\n\n/** The plot's drawing area in CSS pixels. */\nexport interface PlotSize {\n width: number;\n height: number;\n}\n\n/**\n * Maps a model-space point into plot space, flipping the y axis so model y\n * grows upward while SVG y grows downward.\n */\nexport function modelToPlot(p: Point, m: ModelRange, plot: PlotSize): Point {\n const xFrac = (p.x - m.xMin) / (m.xMax - m.xMin);\n const yFrac = (p.y - m.yMin) / (m.yMax - m.yMin);\n return {\n x: xFrac * plot.width,\n // Invert y so model y=0 sits at plot bottom (SVG y grows downward).\n y: (1 - yFrac) * plot.height,\n };\n}\n\n/** The inverse of {@link modelToPlot}. */\nexport function plotToModel(p: Point, m: ModelRange, plot: PlotSize): Point {\n const xFrac = p.x / plot.width;\n const yFrac = 1 - p.y / plot.height;\n return {\n x: m.xMin + xFrac * (m.xMax - m.xMin),\n y: m.yMin + yFrac * (m.yMax - m.yMin),\n };\n}\n",".root {\n /* Component-local vars layered over theme tokens. Consumers override\n * either the plot-* var (component scope) or the wzl-* token (app\n * scope). */\n --plot-bg: var(--wzl-surface-sunken);\n --plot-grid: var(--wzl-line);\n /* Axes get full foreground contrast — they're the plot frame, the\n * most prominent structural element after caller-rendered content. */\n --plot-axis: var(--wzl-fg);\n\n display: block;\n /* Caller-rendered marks at the very edge (e.g. CurveEditor anchors)\n * extend past the SVG box by their radius — let them paint over the\n * host page instead of being half-clipped. */\n overflow: visible;\n background: var(--plot-bg);\n user-select: none;\n touch-action: none;\n outline: none;\n}\n\n/* Keyboard-only focus ring. Caller opts in via `tabIndex`. */\n.root:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 2px;\n}\n\n.grid {\n stroke: var(--plot-grid);\n stroke-width: 1;\n fill: none;\n}\n\n.axis {\n stroke: var(--plot-axis);\n stroke-width: var(--wzl-line-width);\n fill: none;\n}\n","import {\n forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from 'react';\nimport {\n modelToPlot, plotToModel,\n type ModelRange, type Point,\n} from './geometry';\nimport s from './Plot2D.module.css';\nimport { dlog } from '../../dlog';\n\n/** Background grid configuration for a {@link Plot2D}. */\nexport interface GridSettings {\n /** Number of evenly-spaced internal grid lines per axis (excluding\n * the edges). Applied to both x and y. Default 3. */\n divisions?: number;\n /** Stroke color override. When omitted, uses `var(--plot-grid)`. */\n color?: string;\n}\n\n/** Axis-line configuration for a {@link Plot2D}. */\nexport interface AxesSettings {\n /** Stroke color override. When omitted, uses `var(--plot-axis)`. */\n color?: string;\n}\n\n/** A pointer position given in both of the plot's coordinate systems. */\nexport interface Plot2DCoords {\n plot: Point;\n model: Point;\n}\n\n/** Props for {@link Plot2D}. */\nexport interface Plot2DProps {\n width: number;\n height: number;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n /** Background grid. `false` / `null` / omitted = no grid. Pass `{}`\n * for default (3 divisions per axis) or populated GridSettings. */\n grid?: GridSettings | false | null;\n /** Axis lines. `false` / `null` = no axes. Omitted = default-styled\n * axes (on). Pass AxesSettings to customize. */\n axes?: AxesSettings | false | null;\n /** Forwarded to the underlying svg. Consumer opts into focus this way. */\n tabIndex?: number;\n className?: string;\n style?: CSSProperties;\n /** Pointer down on the SVG. Receives both plot- and model-space coords\n * pre-computed so consumers don't repeat the rect/transform dance. */\n onPointerDown?: (e: ReactPointerEvent<SVGSVGElement>, coords: Plot2DCoords) => void;\n onKeyDown?: (e: ReactKeyboardEvent<SVGSVGElement>) => void;\n children?: ReactNode;\n}\n\n/**\n * Imperative handle on a {@link Plot2D}: the SVG element, its size, and the\n * coordinate conversions, including ones that start from a raw DOM event so a\n * drag tracked on `window` can still map back into the plot.\n */\nexport interface Plot2DHandle {\n readonly svg: SVGSVGElement | null;\n plotToModel(pt: Point): Point;\n modelToPlot(pt: Point): Point;\n /** Convert a DOM event's clientX/clientY (e.g. from a window-attached\n * pointermove during drag) to plot-space. */\n clientToPlot(e: { clientX: number; clientY: number }): Point;\n /** Convenience: clientToPlot → plotToModel. */\n clientToModel(e: { clientX: number; clientY: number }): Point;\n readonly width: number;\n readonly height: number;\n}\n\n/**\n * An SVG plotting surface with an optional grid and axes. It draws the frame\n * and owns the model-space to plot-space mapping; the plotted content is\n * whatever children are passed, positioned in plot space.\n */\nexport const Plot2D = forwardRef<Plot2DHandle, Plot2DProps>(function Plot2D(props, ref) {\n const {\n width, height,\n xRange = [0, 1],\n yRange = [0, 1],\n grid,\n axes,\n tabIndex,\n className,\n style,\n onPointerDown,\n onKeyDown,\n children,\n } = props;\n\n const svgRef = useRef<SVGSVGElement | null>(null);\n\n const modelRange: ModelRange = useMemo(\n () => ({ xMin: xRange[0], xMax: xRange[1], yMin: yRange[0], yMax: yRange[1] }),\n [xRange, yRange],\n );\n\n const plotSize = useMemo(() => ({ width, height }), [width, height]);\n\n const clientToPlot = useCallback((e: { clientX: number; clientY: number }): Point => {\n const rect = svgRef.current?.getBoundingClientRect();\n const left = rect?.left ?? 0;\n const top = rect?.top ?? 0;\n return { x: e.clientX - left, y: e.clientY - top };\n }, []);\n\n useEffect(() => {\n dlog('plot2d', 'mount', { width, height });\n return () => dlog('plot2d', 'unmount');\n // Mount/unmount trace: re-running on a resize would log a spurious mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n useImperativeHandle(ref, () => ({\n get svg() { return svgRef.current; },\n plotToModel: (pt: Point) => plotToModel(pt, modelRange, plotSize),\n modelToPlot: (pt: Point) => modelToPlot(pt, modelRange, plotSize),\n clientToPlot,\n clientToModel: (e) => plotToModel(clientToPlot(e), modelRange, plotSize),\n get width() { return plotSize.width; },\n get height() { return plotSize.height; },\n }), [modelRange, plotSize, clientToPlot]);\n\n const handlePointerDown = useCallback((e: ReactPointerEvent<SVGSVGElement>) => {\n if (!onPointerDown) return;\n const plot = clientToPlot(e);\n const model = plotToModel(plot, modelRange, plotSize);\n onPointerDown(e, { plot, model });\n }, [onPointerDown, clientToPlot, modelRange, plotSize]);\n\n const cls = [s.root, className].filter(Boolean).join(' ');\n\n return (\n <svg\n ref={svgRef}\n className={cls}\n style={style}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n tabIndex={tabIndex}\n onPointerDown={onPointerDown ? handlePointerDown : undefined}\n onKeyDown={onKeyDown}\n >\n {grid && (() => {\n const divisions = grid.divisions ?? 3;\n const stroke = grid.color;\n // Evenly-spaced internal divisions, excluding the edges (which\n // belong to the axes). For divisions=3, fractions are 1/4, 2/4, 3/4.\n const fractions: number[] = [];\n for (let i = 1; i <= divisions; i++) fractions.push(i / (divisions + 1));\n return (\n <g>\n {fractions.map((f) => (\n <line\n key={`gx-${f}`}\n data-plot-element=\"grid\"\n className={s.grid}\n stroke={stroke}\n x1={f * width} x2={f * width}\n y1={0} y2={height}\n />\n ))}\n {fractions.map((f) => (\n <line\n key={`gy-${f}`}\n data-plot-element=\"grid\"\n className={s.grid}\n stroke={stroke}\n x1={0} x2={width}\n y1={f * height} y2={f * height}\n />\n ))}\n </g>\n );\n })()}\n {axes !== false && axes !== null && (() => {\n const stroke = axes?.color;\n return (\n <g>\n <line\n data-plot-element=\"axis\"\n className={s.axis}\n stroke={stroke}\n x1={0} x2={width}\n y1={height} y2={height}\n />\n <line\n data-plot-element=\"axis\"\n className={s.axis}\n stroke={stroke}\n x1={0} x2={0}\n y1={0} y2={height}\n />\n </g>\n );\n })()}\n {children}\n </svg>\n );\n});\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { n as e } from "./CurveEditor-BNbP2wgq.js";
1
+ import { n as e } from "./CurveEditor-DtjdpJVB.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  //#region src/components/PointPlotter/PointPlotter.tsx
4
4
  function n(n) {
@@ -13,4 +13,4 @@ function n(n) {
13
13
  //#endregion
14
14
  export { n as t };
15
15
 
16
- //# sourceMappingURL=PointPlotter-B-DvXjkL.js.map
16
+ //# sourceMappingURL=PointPlotter-CbDKVggs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PointPlotter-B-DvXjkL.js","names":[],"sources":["../../src/components/PointPlotter/PointPlotter.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\nimport {\n CurveEditor,\n type AnchorRenderProps,\n type ControlPoint,\n type CurveEditorProps,\n} from '../CurveEditor';\nimport type { GridSettings, AxesSettings } from '../Plot2D';\n\n/** Props for {@link PointPlotter}. */\nexport interface PointPlotterProps {\n /** Plotted points; caller-owned. */\n value: readonly ControlPoint[];\n /** Fires every frame during drag with the live in-flight value. */\n onInput: (next: ControlPoint[]) => void;\n /** Fires once per discrete user action (drag-end, add, delete) with\n * the new value and the value at gesture start. Wire history here. */\n onChange?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n width: number;\n height: number;\n grid?: GridSettings | false | null;\n axes?: AxesSettings | false | null;\n /** Minimum allowed point count. User-initiated deletion is refused\n * while `value.length <= minPoints`. */\n minPoints?: number;\n /** Maximum allowed point count. User-initiated insertion is refused\n * while `value.length >= maxPoints`. */\n maxPoints?: number;\n /** How new points are added. Default `'click-empty'`. Set to `'never'`\n * to disable insertion entirely. */\n addPointMode?: 'click-empty' | 'never';\n /** Built-in undo/redo via keyboard when the component has focus.\n * Default `true`. See `CurveEditor.history` for details. */\n history?: boolean;\n /** Custom per-anchor renderer. See `CurveEditor.renderAnchor`. */\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** Extra SVG content beneath the anchors. See `CurveEditor.decorations`. */\n decorations?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * 2D point plotter — a CurveEditor with the curve hidden. Anchors,\n * drag, shift+click delete, right-click delete, and click-empty insert\n * are inherited from CurveEditor verbatim, so the two components share\n * gesture semantics and stay in sync as CurveEditor evolves.\n *\n * Differences from CurveEditor: no curve `<path>` drawn, no fill, no\n * endpoint pinning, no interpolation, default `addPointMode` is\n * `'click-empty'` (CurveEditor defaults to `'click-curve'`, which\n * doesn't make sense without a curve to hit-test against).\n */\nexport function PointPlotter(props: PointPlotterProps) {\n const curveProps: CurveEditorProps = {\n ...props,\n addPointMode: props.addPointMode ?? 'click-empty',\n curve: false,\n fill: false,\n endpoints: 'free',\n };\n return <CurveEditor {...curveProps} />;\n}\n"],"mappings":";;;AAyDA,SAAgB,EAAa,GAA0B;AAQrD,QAAO,kBAAC,GAAD;EANL,GAAG;EACH,cAAc,EAAM,gBAAgB;EACpC,OAAO;EACP,MAAM;EACN,WAAW;EAEyB,CAAA"}
1
+ {"version":3,"file":"PointPlotter-CbDKVggs.js","names":[],"sources":["../../src/components/PointPlotter/PointPlotter.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\nimport {\n CurveEditor,\n type AnchorRenderProps,\n type ControlPoint,\n type CurveEditorProps,\n} from '../CurveEditor';\nimport type { GridSettings, AxesSettings } from '../Plot2D';\n\n/** Props for {@link PointPlotter}. */\nexport interface PointPlotterProps {\n /** Plotted points; caller-owned. */\n value: readonly ControlPoint[];\n /** Fires every frame during drag with the live in-flight value. */\n onInput: (next: ControlPoint[]) => void;\n /** Fires once per discrete user action (drag-end, add, delete) with\n * the new value and the value at gesture start. Wire history here. */\n onChange?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n width: number;\n height: number;\n grid?: GridSettings | false | null;\n axes?: AxesSettings | false | null;\n /** Minimum allowed point count. User-initiated deletion is refused\n * while `value.length <= minPoints`. */\n minPoints?: number;\n /** Maximum allowed point count. User-initiated insertion is refused\n * while `value.length >= maxPoints`. */\n maxPoints?: number;\n /** How new points are added. Default `'click-empty'`. Set to `'never'`\n * to disable insertion entirely. */\n addPointMode?: 'click-empty' | 'never';\n /** Built-in undo/redo via keyboard when the component has focus.\n * Default `true`. See `CurveEditor.history` for details. */\n history?: boolean;\n /** Custom per-anchor renderer. See `CurveEditor.renderAnchor`. */\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** Extra SVG content beneath the anchors. See `CurveEditor.decorations`. */\n decorations?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * 2D point plotter — a CurveEditor with the curve hidden. Anchors,\n * drag, shift+click delete, right-click delete, and click-empty insert\n * are inherited from CurveEditor verbatim, so the two components share\n * gesture semantics and stay in sync as CurveEditor evolves.\n *\n * Differences from CurveEditor: no curve `<path>` drawn, no fill, no\n * endpoint pinning, no interpolation, default `addPointMode` is\n * `'click-empty'` (CurveEditor defaults to `'click-curve'`, which\n * doesn't make sense without a curve to hit-test against).\n */\nexport function PointPlotter(props: PointPlotterProps) {\n const curveProps: CurveEditorProps = {\n ...props,\n addPointMode: props.addPointMode ?? 'click-empty',\n curve: false,\n fill: false,\n endpoints: 'free',\n };\n return <CurveEditor {...curveProps} />;\n}\n"],"mappings":";;;AAyDA,SAAgB,EAAa,GAA0B;AAQrD,QAAO,kBAAC,GAAD;EANL,GAAG;EACH,cAAc,EAAM,gBAAgB;EACpC,OAAO;EACP,MAAM;EACN,WAAW;EAEyB,CAAA"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./Badge-7yQseqZb.js";
1
+ import { t as e } from "./Badge-Bf8Rd1R6.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  var n = {
4
4
  row: "_row_q3wun_1",
@@ -33,4 +33,4 @@ function r({ segments: r, startCap: i = "flat", size: a, variant: o, depth: s, g
33
33
  //#endregion
34
34
  export { r as t };
35
35
 
36
- //# sourceMappingURL=Powerline-CJYCbqSc.js.map
36
+ //# sourceMappingURL=Powerline-BaHDJlcO.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Powerline-CJYCbqSc.js","names":[],"sources":["../../src/components/Powerline/Powerline.module.css","../../src/components/Powerline/Powerline.tsx"],"sourcesContent":[".row {\n display: inline-flex;\n align-items: stretch;\n gap: var(--powerline-gap, 0.2em);\n}\n\n.matrix {\n display: grid;\n gap: 12px;\n}\n\n/* Reset Badge's opinionated text defaults — Powerline segments should pick up\n * the surrounding text style instead of forcing uppercase / 10px / extra\n * tracking. Consumers can still override via className on Powerline. */\n.segment {\n font-size: inherit;\n text-transform: none;\n letter-spacing: normal;\n}\n","import type { CSSProperties, ReactNode } from 'react';\nimport { Badge } from '../Badge/Badge';\nimport type { BadgeSize, BadgeTone, BadgeVariant } from '../Badge/types';\nimport type { EdgeCap } from '../Badge/bases/edgeProfiles';\nimport s from './Powerline.module.css';\n\n/**\n * One segment of a {@link Powerline}. Giving it `onClick` or `href` makes it\n * interactive, the same way it does on a `Badge`.\n */\nexport interface PowerlineSegment {\n text: ReactNode;\n /** Cap on this segment's right edge. Next segment's left edge adopts the same profile. */\n endCap?: EdgeCap;\n tone?: BadgeTone;\n variant?: BadgeVariant;\n size?: BadgeSize;\n onClick?: () => void;\n href?: string;\n 'aria-label'?: string;\n}\n\n/** Props for {@link Powerline}. */\nexport interface PowerlineProps {\n segments: PowerlineSegment[];\n /** Left edge of the first segment. Defaults to 'flat'. */\n startCap?: EdgeCap;\n /** Default size for every segment (per-segment `size` wins). */\n size?: BadgeSize;\n /** Default variant for every segment (per-segment `variant` wins). */\n variant?: BadgeVariant;\n /** Protrusion depth in CSS px, passed through to every segment's base. */\n depth?: number;\n /** Visible gap between adjacent segments. Number → px; string → literal CSS length.\n * Default: `0.2em` (scales with the row's font size). Pass `0` for flush. */\n gap?: number | string;\n className?: string;\n 'aria-label'?: string;\n}\n\n/**\n * A row of chevron-linked badge segments, in the style of a shell powerline\n * prompt. Each segment's `endCap` shapes both its own right edge and the next\n * segment's left edge, so the run reads as one continuous chain.\n *\n * Built for showing a path through a sequence of steps — the inspector uses\n * it to display how input routes from tool to gesture to action to target.\n */\nexport function Powerline({\n segments,\n startCap = 'flat',\n size,\n variant,\n depth,\n gap,\n className,\n ...rest\n}: PowerlineProps) {\n const cls = [s.row, className].filter(Boolean).join(' ');\n const style: CSSProperties | undefined = gap !== undefined\n ? { ['--powerline-gap' as never]: typeof gap === 'number' ? `${gap}px` : gap }\n : undefined;\n return (\n <span className={cls} style={style} aria-label={rest['aria-label']}>\n {segments.map((seg, i) => {\n const leftEdge: EdgeCap = i === 0 ? startCap : (segments[i - 1].endCap ?? 'flat');\n const rightEdge: EdgeCap = seg.endCap ?? 'flat';\n return (\n <Badge\n key={i}\n base=\"powerline\"\n baseParams={{ leftEdge, rightEdge, ...(depth !== undefined && { depth }) }}\n tone={seg.tone}\n variant={seg.variant ?? variant}\n size={seg.size ?? size}\n onClick={seg.onClick}\n href={seg.href}\n aria-label={seg['aria-label']}\n className={s.segment}\n >\n {seg.text}\n </Badge>\n );\n })}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;ACgDA,SAAgB,EAAU,EACxB,aACA,cAAW,QACX,SACA,YACA,UACA,QACA,cACA,GAAG,KACc;AAKjB,QACE,kBAAC,QAAD;EAAM,WALI,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAKjC;EAAY,OAJU,MAAQ,KAAA,IAE7C,KAAA,IADA,EAAG,mBAA6B,OAAO,KAAQ,WAAW,GAAG,EAAI,MAAM,GAAK;EAG1C,cAAY,EAAK;YAClD,EAAS,KAAK,GAAK,MAIhB,kBAAC,GAAD;GAEE,MAAK;GACL,YAAY;IAAE,UANQ,MAAM,IAAI,IAAY,EAAS,IAAI,GAAG,UAAU;IAM9C,WALD,EAAI,UAAU;IAKF,GAAI,MAAU,KAAA,KAAa,EAAE,UAAO;IAAG;GAC1E,MAAM,EAAI;GACV,SAAS,EAAI,WAAW;GACxB,MAAM,EAAI,QAAQ;GAClB,SAAS,EAAI;GACb,MAAM,EAAI;GACV,cAAY,EAAI;GAChB,WAAW,EAAE;aAEZ,EAAI;GACC,EAZD,EAYC,CAEV;EACG,CAAA"}
1
+ {"version":3,"file":"Powerline-BaHDJlcO.js","names":[],"sources":["../../src/components/Powerline/Powerline.module.css","../../src/components/Powerline/Powerline.tsx"],"sourcesContent":[".row {\n display: inline-flex;\n align-items: stretch;\n gap: var(--powerline-gap, 0.2em);\n}\n\n.matrix {\n display: grid;\n gap: 12px;\n}\n\n/* Reset Badge's opinionated text defaults — Powerline segments should pick up\n * the surrounding text style instead of forcing uppercase / 10px / extra\n * tracking. Consumers can still override via className on Powerline. */\n.segment {\n font-size: inherit;\n text-transform: none;\n letter-spacing: normal;\n}\n","import type { CSSProperties, ReactNode } from 'react';\nimport { Badge } from '../Badge/Badge';\nimport type { BadgeSize, BadgeTone, BadgeVariant } from '../Badge/types';\nimport type { EdgeCap } from '../Badge/bases/edgeProfiles';\nimport s from './Powerline.module.css';\n\n/**\n * One segment of a {@link Powerline}. Giving it `onClick` or `href` makes it\n * interactive, the same way it does on a `Badge`.\n */\nexport interface PowerlineSegment {\n text: ReactNode;\n /** Cap on this segment's right edge. Next segment's left edge adopts the same profile. */\n endCap?: EdgeCap;\n tone?: BadgeTone;\n variant?: BadgeVariant;\n size?: BadgeSize;\n onClick?: () => void;\n href?: string;\n 'aria-label'?: string;\n}\n\n/** Props for {@link Powerline}. */\nexport interface PowerlineProps {\n segments: PowerlineSegment[];\n /** Left edge of the first segment. Defaults to 'flat'. */\n startCap?: EdgeCap;\n /** Default size for every segment (per-segment `size` wins). */\n size?: BadgeSize;\n /** Default variant for every segment (per-segment `variant` wins). */\n variant?: BadgeVariant;\n /** Protrusion depth in CSS px, passed through to every segment's base. */\n depth?: number;\n /** Visible gap between adjacent segments. Number → px; string → literal CSS length.\n * Default: `0.2em` (scales with the row's font size). Pass `0` for flush. */\n gap?: number | string;\n className?: string;\n 'aria-label'?: string;\n}\n\n/**\n * A row of chevron-linked badge segments, in the style of a shell powerline\n * prompt. Each segment's `endCap` shapes both its own right edge and the next\n * segment's left edge, so the run reads as one continuous chain.\n *\n * Built for showing a path through a sequence of steps — the inspector uses\n * it to display how input routes from tool to gesture to action to target.\n */\nexport function Powerline({\n segments,\n startCap = 'flat',\n size,\n variant,\n depth,\n gap,\n className,\n ...rest\n}: PowerlineProps) {\n const cls = [s.row, className].filter(Boolean).join(' ');\n const style: CSSProperties | undefined = gap !== undefined\n ? { ['--powerline-gap' as never]: typeof gap === 'number' ? `${gap}px` : gap }\n : undefined;\n return (\n <span className={cls} style={style} aria-label={rest['aria-label']}>\n {segments.map((seg, i) => {\n const leftEdge: EdgeCap = i === 0 ? startCap : (segments[i - 1].endCap ?? 'flat');\n const rightEdge: EdgeCap = seg.endCap ?? 'flat';\n return (\n <Badge\n key={i}\n base=\"powerline\"\n baseParams={{ leftEdge, rightEdge, ...(depth !== undefined && { depth }) }}\n tone={seg.tone}\n variant={seg.variant ?? variant}\n size={seg.size ?? size}\n onClick={seg.onClick}\n href={seg.href}\n aria-label={seg['aria-label']}\n className={s.segment}\n >\n {seg.text}\n </Badge>\n );\n })}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;ACgDA,SAAgB,EAAU,EACxB,aACA,cAAW,QACX,SACA,YACA,UACA,QACA,cACA,GAAG,KACc;AAKjB,QACE,kBAAC,QAAD;EAAM,WALI,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAKjC;EAAY,OAJU,MAAQ,KAAA,IAE7C,KAAA,IADA,EAAG,mBAA6B,OAAO,KAAQ,WAAW,GAAG,EAAI,MAAM,GAAK;EAG1C,cAAY,EAAK;YAClD,EAAS,KAAK,GAAK,MAIhB,kBAAC,GAAD;GAEE,MAAK;GACL,YAAY;IAAE,UANQ,MAAM,IAAI,IAAY,EAAS,IAAI,GAAG,UAAU;IAM9C,WALD,EAAI,UAAU;IAKF,GAAI,MAAU,KAAA,KAAa,EAAE,UAAO;IAAG;GAC1E,MAAM,EAAI;GACV,SAAS,EAAI,WAAW;GACxB,MAAM,EAAI,QAAQ;GAClB,SAAS,EAAI;GACb,MAAM,EAAI;GACV,cAAY,EAAI;GAChB,WAAW,EAAE;aAEZ,EAAI;GACC,EAZD,EAYC,CAEV;EACG,CAAA"}