@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":"DetentSlider-C6RTsiPi.js","names":[],"sources":["../../src/components/DetentSlider/DetentSlider.module.css","../../src/components/DetentSlider/DetentSlider.tsx"],"sourcesContent":[".root {\n position: relative;\n /* Read by Slider's track and stop marks. Detents run the full height of the\n * track so they read as notches rather than as tally marks beside it. */\n --rp-tick-inset: 0;\n --rp-tick-width: 1px;\n --rp-tick-color: var(--wzl-border-strong);\n --rp-track-cursor: pointer;\n font-family: var(--wzl-font-ui);\n}\n\n.slider {\n width: 100%;\n}\n\n.labels {\n position: relative;\n height: calc(var(--wzl-font-size-xs) * var(--wzl-leading-tight));\n margin-top: var(--wzl-space-xs);\n}\n\n.label {\n position: absolute;\n transform: translateX(-50%);\n white-space: nowrap;\n font-size: var(--wzl-font-size-xs);\n line-height: var(--wzl-leading-tight);\n font-variant-numeric: tabular-nums;\n color: var(--wzl-fg-subtle);\n}\n\n/* The end labels are pinned inside the track rather than centred on it, so a\n * wide one can't spill past the control and misalign whatever sits beside it. */\n.label[data-edge=\"start\"] {\n transform: none;\n}\n\n.label[data-edge=\"end\"] {\n transform: translateX(-100%);\n}\n\n.label[data-selected] {\n color: var(--wzl-fg);\n font-weight: var(--wzl-font-weight-medium);\n}\n","import { useEffect, useRef, type ReactElement, type ReactNode } from 'react';\nimport { Slider, type Thumb } from '../Slider/Slider';\nimport s from './DetentSlider.module.css';\n\n/** What a detent can carry. Narrowed so identity comparison and React keys\n * both work without a keying callback. */\nexport type DetentValue = string | number;\n\n/** One detent on a {@link DetentSlider}. */\nexport type DetentItem<V extends DetentValue = number> = {\n value: V;\n /** Drawn under the detent. Defaults to `formatLabel(value)`, then `value`. */\n label?: ReactNode;\n /** Spoken form, when the drawn label is not the right thing to say. */\n ariaLabel?: string;\n};\n\n/**\n * Props for {@link DetentSlider}.\n *\n * `items` takes bare values or `DetentItem`s interchangeably — a bare value is\n * a detent with no label of its own.\n *\n * `onChange` fires on every detent the control passes through, including\n * mid-drag: each one is a complete, legal choice, so there is no in-flight\n * value to withhold. `onCommit` fires once the gesture that changed things\n * ends, and is the one to write to history.\n */\nexport type DetentSliderProps<V extends DetentValue = number> = {\n items: readonly (V | DetentItem<V>)[];\n value: V;\n onChange: (value: V, index: number) => void;\n onCommit?: (value: V, index: number) => void;\n formatLabel?: (value: V, index: number) => ReactNode;\n labels?: 'all' | 'ends' | 'none';\n trackHeight?: number;\n ariaLabel?: string;\n className?: string;\n};\n\nfunction normalize<V extends DetentValue>(items: readonly (V | DetentItem<V>)[]): DetentItem<V>[] {\n return items.map(it => (typeof it === 'object' && it !== null ? it : ({ value: it } as DetentItem<V>)));\n}\n\n/**\n * The detent `value` selects. An exact match wins; a number that matches none\n * lands on the nearest one, so a value arriving from a numeric field or an\n * older save renders where it belongs rather than silently reading as the\n * first detent.\n */\nfunction resolveIndex<V extends DetentValue>(items: readonly DetentItem<V>[], value: V): number {\n const exact = items.findIndex(it => it.value === value);\n if (exact !== -1) return exact;\n if (typeof value !== 'number') return 0;\n let best = 0;\n let bestGap = Infinity;\n for (let i = 0; i < items.length; i++) {\n const v = items[i].value;\n if (typeof v !== 'number') continue;\n const gap = Math.abs(v - value);\n if (gap < bestGap) {\n bestGap = gap;\n best = i;\n }\n }\n return best;\n}\n\nfunction plainText(label: ReactNode, fallback: DetentValue): string {\n return typeof label === 'string' || typeof label === 'number' ? String(label) : String(fallback);\n}\n\n/**\n * Slider over a fixed list of values: one detent per value, evenly spaced, the\n * thumb resting on one of them.\n *\n * This is the shape to reach for when a numeric option has a small set of\n * allowed values along a line — playback rate, zoom step, stroke weight — in\n * place of a dropdown, which hides the ordering and the extent of the range\n * behind a click.\n *\n * The track addresses the *index* of `items`, not the value, which is what\n * makes the detents evenly spaced no matter how the values are distributed:\n * a geometric rate list (0.25/0.5/1/2/4) laid out linearly would crowd four\n * of its five detents into the first fifth of the track and leave the most\n * used one nearly unhittable. Since the index is meaningless to a screen\n * reader, the value is published as `aria-valuetext`.\n */\nexport function DetentSlider<V extends DetentValue = number>(props: DetentSliderProps<V>): ReactElement {\n const { value, onChange, onCommit, formatLabel, ariaLabel, className } = props;\n const items = normalize(props.items);\n const labelMode = props.labels ?? 'all';\n const index = resolveIndex(items, value);\n const lastIndex = Math.max(0, items.length - 1);\n\n const labelOf = (item: DetentItem<V>, i: number): ReactNode =>\n item.label ?? formatLabel?.(item.value, i) ?? String(item.value);\n\n // Deduped separately: `live` suppresses the repeat reports a drag produces\n // while the pointer crosses one detent, `changed` keeps a press that chose\n // nothing from landing a no-op entry in the consumer's history.\n const liveRef = useRef(index);\n const changedRef = useRef(false);\n useEffect(() => {\n liveRef.current = index;\n }, [index]);\n\n const at = (raw: number): number => Math.max(0, Math.min(lastIndex, Math.round(raw)));\n\n const handleInput = (next: Thumb[]) => {\n const i = at(next[0].value);\n if (i === liveRef.current) return;\n liveRef.current = i;\n changedRef.current = true;\n onChange(items[i].value, i);\n };\n\n const handleCommit = (next: Thumb[]) => {\n if (!changedRef.current) return;\n changedRef.current = false;\n const i = at(next[0].value);\n onCommit?.(items[i].value, i);\n };\n\n const current = items[index];\n if (!current) return <div className={className ? `${s.root} ${className}` : s.root} />;\n\n const thumbs: Thumb[] = [\n { value: index, valueText: current.ariaLabel ?? plainText(labelOf(current, index), current.value) },\n ];\n\n const shown =\n labelMode === 'ends'\n ? [...new Set([0, lastIndex])]\n : items.map((_, i) => i);\n\n return (\n <div className={className ? `${s.root} ${className}` : s.root}>\n <Slider\n className={s.slider}\n min={0}\n max={lastIndex}\n step={1}\n stops={items.map((_, i) => i)}\n trackClick=\"move-nearest\"\n trackHeight={props.trackHeight ?? 8}\n ariaLabel={ariaLabel}\n thumbs={thumbs}\n onInput={handleInput}\n onChange={handleCommit}\n />\n {labelMode !== 'none' && (\n <div className={s.labels} data-detent-labels aria-hidden=\"true\">\n {shown.map(i => (\n <span\n key={items[i].value}\n className={s.label}\n data-detent-label\n data-index={i}\n data-selected={i === index ? 'true' : undefined}\n data-edge={i === 0 ? 'start' : i === lastIndex ? 'end' : undefined}\n style={{ left: `${(lastIndex === 0 ? 0 : i / lastIndex) * 100}%` }}\n >\n {labelOf(items[i], i)}\n </span>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DetentSlider-BpD7bgjN.js","names":[],"sources":["../../src/components/DetentSlider/DetentSlider.module.css","../../src/components/DetentSlider/DetentSlider.tsx"],"sourcesContent":[".root {\n position: relative;\n /* Read by Slider's track and stop marks. Detents run the full height of the\n * track so they read as notches rather than as tally marks beside it. */\n --rp-tick-inset: 0;\n --rp-tick-width: 1px;\n --rp-tick-color: var(--wzl-border-strong);\n --rp-track-cursor: pointer;\n font-family: var(--wzl-font-ui);\n}\n\n.slider {\n width: 100%;\n}\n\n.labels {\n position: relative;\n height: calc(var(--wzl-font-size-xs) * var(--wzl-leading-tight));\n margin-top: var(--wzl-space-xs);\n}\n\n.label {\n position: absolute;\n transform: translateX(-50%);\n white-space: nowrap;\n font-size: var(--wzl-font-size-xs);\n line-height: var(--wzl-leading-tight);\n font-variant-numeric: tabular-nums;\n color: var(--wzl-fg-subtle);\n}\n\n/* The end labels are pinned inside the track rather than centred on it, so a\n * wide one can't spill past the control and misalign whatever sits beside it. */\n.label[data-edge=\"start\"] {\n transform: none;\n}\n\n.label[data-edge=\"end\"] {\n transform: translateX(-100%);\n}\n\n.label[data-selected] {\n color: var(--wzl-fg);\n font-weight: var(--wzl-font-weight-medium);\n}\n","import { useEffect, useRef, type ReactElement, type ReactNode } from 'react';\nimport { Slider, type Thumb } from '../Slider/Slider';\nimport s from './DetentSlider.module.css';\n\n/** What a detent can carry. Narrowed so identity comparison and React keys\n * both work without a keying callback. */\nexport type DetentValue = string | number;\n\n/** One detent on a {@link DetentSlider}. */\nexport type DetentItem<V extends DetentValue = number> = {\n value: V;\n /** Drawn under the detent. Defaults to `formatLabel(value)`, then `value`. */\n label?: ReactNode;\n /** Spoken form, when the drawn label is not the right thing to say. */\n ariaLabel?: string;\n};\n\n/**\n * Props for {@link DetentSlider}.\n *\n * `items` takes bare values or `DetentItem`s interchangeably — a bare value is\n * a detent with no label of its own.\n *\n * `onChange` fires on every detent the control passes through, including\n * mid-drag: each one is a complete, legal choice, so there is no in-flight\n * value to withhold. `onCommit` fires once the gesture that changed things\n * ends, and is the one to write to history.\n */\nexport type DetentSliderProps<V extends DetentValue = number> = {\n items: readonly (V | DetentItem<V>)[];\n value: V;\n onChange: (value: V, index: number) => void;\n onCommit?: (value: V, index: number) => void;\n formatLabel?: (value: V, index: number) => ReactNode;\n labels?: 'all' | 'ends' | 'none';\n trackHeight?: number;\n ariaLabel?: string;\n className?: string;\n};\n\nfunction normalize<V extends DetentValue>(items: readonly (V | DetentItem<V>)[]): DetentItem<V>[] {\n return items.map(it => (typeof it === 'object' && it !== null ? it : ({ value: it } as DetentItem<V>)));\n}\n\n/**\n * The detent `value` selects. An exact match wins; a number that matches none\n * lands on the nearest one, so a value arriving from a numeric field or an\n * older save renders where it belongs rather than silently reading as the\n * first detent.\n */\nfunction resolveIndex<V extends DetentValue>(items: readonly DetentItem<V>[], value: V): number {\n const exact = items.findIndex(it => it.value === value);\n if (exact !== -1) return exact;\n if (typeof value !== 'number') return 0;\n let best = 0;\n let bestGap = Infinity;\n for (let i = 0; i < items.length; i++) {\n const v = items[i].value;\n if (typeof v !== 'number') continue;\n const gap = Math.abs(v - value);\n if (gap < bestGap) {\n bestGap = gap;\n best = i;\n }\n }\n return best;\n}\n\nfunction plainText(label: ReactNode, fallback: DetentValue): string {\n return typeof label === 'string' || typeof label === 'number' ? String(label) : String(fallback);\n}\n\n/**\n * Slider over a fixed list of values: one detent per value, evenly spaced, the\n * thumb resting on one of them.\n *\n * This is the shape to reach for when a numeric option has a small set of\n * allowed values along a line — playback rate, zoom step, stroke weight — in\n * place of a dropdown, which hides the ordering and the extent of the range\n * behind a click.\n *\n * The track addresses the *index* of `items`, not the value, which is what\n * makes the detents evenly spaced no matter how the values are distributed:\n * a geometric rate list (0.25/0.5/1/2/4) laid out linearly would crowd four\n * of its five detents into the first fifth of the track and leave the most\n * used one nearly unhittable. Since the index is meaningless to a screen\n * reader, the value is published as `aria-valuetext`.\n */\nexport function DetentSlider<V extends DetentValue = number>(props: DetentSliderProps<V>): ReactElement {\n const { value, onChange, onCommit, formatLabel, ariaLabel, className } = props;\n const items = normalize(props.items);\n const labelMode = props.labels ?? 'all';\n const index = resolveIndex(items, value);\n const lastIndex = Math.max(0, items.length - 1);\n\n const labelOf = (item: DetentItem<V>, i: number): ReactNode =>\n item.label ?? formatLabel?.(item.value, i) ?? String(item.value);\n\n // Deduped separately: `live` suppresses the repeat reports a drag produces\n // while the pointer crosses one detent, `changed` keeps a press that chose\n // nothing from landing a no-op entry in the consumer's history.\n const liveRef = useRef(index);\n const changedRef = useRef(false);\n useEffect(() => {\n liveRef.current = index;\n }, [index]);\n\n const at = (raw: number): number => Math.max(0, Math.min(lastIndex, Math.round(raw)));\n\n const handleInput = (next: Thumb[]) => {\n const i = at(next[0].value);\n if (i === liveRef.current) return;\n liveRef.current = i;\n changedRef.current = true;\n onChange(items[i].value, i);\n };\n\n const handleCommit = (next: Thumb[]) => {\n if (!changedRef.current) return;\n changedRef.current = false;\n const i = at(next[0].value);\n onCommit?.(items[i].value, i);\n };\n\n const current = items[index];\n if (!current) return <div className={className ? `${s.root} ${className}` : s.root} />;\n\n const thumbs: Thumb[] = [\n { value: index, valueText: current.ariaLabel ?? plainText(labelOf(current, index), current.value) },\n ];\n\n const shown =\n labelMode === 'ends'\n ? [...new Set([0, lastIndex])]\n : items.map((_, i) => i);\n\n return (\n <div className={className ? `${s.root} ${className}` : s.root}>\n <Slider\n className={s.slider}\n min={0}\n max={lastIndex}\n step={1}\n stops={items.map((_, i) => i)}\n trackClick=\"move-nearest\"\n trackHeight={props.trackHeight ?? 8}\n ariaLabel={ariaLabel}\n thumbs={thumbs}\n onInput={handleInput}\n onChange={handleCommit}\n />\n {labelMode !== 'none' && (\n <div className={s.labels} data-detent-labels aria-hidden=\"true\">\n {shown.map(i => (\n <span\n key={items[i].value}\n className={s.label}\n data-detent-label\n data-index={i}\n data-selected={i === index ? 'true' : undefined}\n data-edge={i === 0 ? 'start' : i === lastIndex ? 'end' : undefined}\n style={{ left: `${(lastIndex === 0 ? 0 : i / lastIndex) * 100}%` }}\n >\n {labelOf(items[i], i)}\n </span>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import { r as e } from "./portalHost-CjpCnuGU.js";
2
+ import { Fragment as t, jsx as n, jsxs as r } from "react/jsx-runtime";
3
+ import { Dialog as i, Heading as a, Modal as o, ModalOverlay as s } from "react-aria-components";
4
+ var c = {
5
+ overlay: "_overlay_1og89_1",
6
+ modal: "_modal_1og89_29",
7
+ dialog: "_dialog_1og89_43",
8
+ header: "_header_1og89_51",
9
+ title: "_title_1og89_60",
10
+ close: "_close_1og89_68",
11
+ body: "_body_1og89_90",
12
+ footer: "_footer_1og89_97"
13
+ };
14
+ //#endregion
15
+ //#region src/components/Dialog/Dialog.tsx
16
+ function l(l) {
17
+ let { title: u, showCloseButton: d, children: f, footer: p, role: m = "dialog", className: h, isOpen: g, onOpenChange: _, portalContainer: v, ...y } = l, b = d ?? !!_, { anchor: x, portalProps: S } = e(v);
18
+ return /* @__PURE__ */ r(t, { children: [x, /* @__PURE__ */ n(s, {
19
+ ...y,
20
+ isOpen: g,
21
+ onOpenChange: _,
22
+ className: c.overlay,
23
+ "data-weasel-overlay": "",
24
+ ...S,
25
+ children: /* @__PURE__ */ n(o, {
26
+ className: [c.modal, h].filter(Boolean).join(" "),
27
+ children: /* @__PURE__ */ n(i, {
28
+ role: m,
29
+ className: c.dialog,
30
+ children: ({ close: e }) => /* @__PURE__ */ r(t, { children: [
31
+ (u !== void 0 || b) && /* @__PURE__ */ r("header", {
32
+ className: c.header,
33
+ children: [u !== void 0 && /* @__PURE__ */ n(a, {
34
+ slot: "title",
35
+ className: c.title,
36
+ children: u
37
+ }), b && /* @__PURE__ */ n("button", {
38
+ type: "button",
39
+ className: c.close,
40
+ onClick: e,
41
+ "aria-label": "Close dialog",
42
+ children: "×"
43
+ })]
44
+ }),
45
+ /* @__PURE__ */ n("div", {
46
+ className: c.body,
47
+ children: f
48
+ }),
49
+ p !== void 0 && /* @__PURE__ */ n("footer", {
50
+ className: c.footer,
51
+ children: p
52
+ })
53
+ ] })
54
+ })
55
+ })
56
+ })] });
57
+ }
58
+ //#endregion
59
+ export { l as t };
60
+
61
+ //# sourceMappingURL=Dialog-CYBLjwbd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dialog-CYBLjwbd.js","names":[],"sources":["../../src/components/Dialog/Dialog.module.css","../../src/components/Dialog/Dialog.tsx"],"sourcesContent":[".overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n opacity: 0;\n transition: opacity var(--wzl-motion-fast);\n}\n\n.overlay[data-entering] {\n opacity: 0;\n}\n\n.overlay[data-entering][data-rac] {\n opacity: 1;\n}\n\n.overlay[data-exiting] {\n opacity: 0;\n}\n\n.overlay[data-rac]:not([data-entering]):not([data-exiting]) {\n opacity: 1;\n}\n\n.modal {\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n max-width: min(900px, calc(100vw - 32px));\n max-height: calc(100vh - 32px);\n display: flex;\n flex-direction: column;\n outline: none;\n}\n\n.dialog {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1;\n outline: none;\n}\n\n.header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-bottom: 1px solid var(--wzl-border);\n flex: 0 0 auto;\n}\n\n.title {\n flex: 1;\n margin: 0;\n font-size: var(--wzl-font-size);\n font-weight: var(--wzl-font-weight-bold);\n color: var(--wzl-fg);\n}\n\n.close {\n flex: 0 0 auto;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid transparent;\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n font-size: var(--wzl-font-size-lg);\n line-height: 1;\n}\n\n.close:hover {\n background: var(--wzl-surface-hover);\n color: var(--wzl-fg);\n}\n\n.body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 16px;\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 16px;\n border-top: 1px solid var(--wzl-border);\n flex: 0 0 auto;\n}\n","import type { ReactNode } from 'react';\nimport {\n Modal as RACModal,\n ModalOverlay,\n Dialog as RACDialog,\n Heading,\n type ModalOverlayProps,\n type DialogProps as RACDialogProps,\n} from 'react-aria-components';\nimport { useOverlayPortal, type OverlayPortalProps, type WithoutPortalTarget } from '../../overlays/portalHost';\nimport s from './Dialog.module.css';\n\n/** Props for {@link Dialog}, on top of React Aria's `ModalOverlay` props. */\nexport type DialogProps = Omit<\n WithoutPortalTarget<ModalOverlayProps>,\n 'children' | 'className'\n> &\n OverlayPortalProps & {\n /** Heading rendered in the dialog's default header. Omit when supplying\n * a custom `header` slot via children. */\n title?: ReactNode;\n /** Show the built-in close button. Defaults to true when `onOpenChange`\n * is wired so the user has an escape hatch. */\n showCloseButton?: boolean;\n /** Body content. */\n children?: ReactNode;\n /** Footer slot — typically action buttons. */\n footer?: ReactNode;\n /** ARIA role. Defaults to `dialog`. Use `alertdialog` for confirms. */\n role?: RACDialogProps['role'];\n /** Class applied to the modal box (inside the overlay). */\n className?: string;\n };\n\n/**\n * Modal dialog wrapping React Aria. Supplies a default header (title +\n * close), scrollable body, and footer slot. Pass `isOpen` + `onOpenChange`\n * for controlled visibility, or omit both and use a `<DialogTrigger>` from\n * `react-aria-components` upstream of this component.\n *\n * Escape, click-outside, focus trap, and scroll lock all come from the\n * underlying primitives.\n */\nexport function Dialog(props: DialogProps) {\n const {\n title,\n showCloseButton,\n children,\n footer,\n role = 'dialog',\n className,\n isOpen,\n onOpenChange,\n portalContainer,\n ...rest\n } = props;\n\n const showClose = showCloseButton ?? Boolean(onOpenChange);\n const { anchor, portalProps } = useOverlayPortal(portalContainer);\n\n return (\n <>\n {anchor}\n <ModalOverlay\n {...rest}\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n className={s.overlay}\n data-weasel-overlay=\"\"\n {...portalProps}\n >\n <RACModal className={[s.modal, className].filter(Boolean).join(' ')}>\n <RACDialog role={role} className={s.dialog}>\n {({ close }) => (\n <>\n {(title !== undefined || showClose) && (\n <header className={s.header}>\n {title !== undefined && (\n <Heading slot=\"title\" className={s.title}>{title}</Heading>\n )}\n {showClose && (\n <button\n type=\"button\"\n className={s.close}\n onClick={close}\n aria-label=\"Close dialog\"\n >\n ×\n </button>\n )}\n </header>\n )}\n <div className={s.body}>{children}</div>\n {footer !== undefined && <footer className={s.footer}>{footer}</footer>}\n </>\n )}\n </RACDialog>\n </RACModal>\n </ModalOverlay>\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AC2CA,SAAgB,EAAO,GAAoB;CACzC,IAAM,EACJ,UACA,oBACA,aACA,WACA,UAAO,UACP,cACA,WACA,iBACA,oBACA,GAAG,MACD,GAEE,IAAY,KAAmB,EAAQ,GACvC,EAAE,WAAQ,mBAAgB,EAAiB,EAAgB;AAEjE,QACE,kBAAA,GAAA,EAAA,UAAA,CACG,GACD,kBAAC,GAAD;EACE,GAAI;EACI;EACM;EACd,WAAW,EAAE;EACb,uBAAoB;EACpB,GAAI;YAEJ,kBAAC,GAAD;GAAU,WAAW,CAAC,EAAE,OAAO,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;aACjE,kBAAC,GAAD;IAAiB;IAAM,WAAW,EAAE;eAChC,EAAE,eACF,kBAAA,GAAA,EAAA,UAAA;MACI,MAAU,KAAA,KAAa,MACvB,kBAAC,UAAD;MAAQ,WAAW,EAAE;gBAArB,CACG,MAAU,KAAA,KACT,kBAAC,GAAD;OAAS,MAAK;OAAQ,WAAW,EAAE;iBAAQ;OAAgB,CAAA,EAE5D,KACC,kBAAC,UAAD;OACE,MAAK;OACL,WAAW,EAAE;OACb,SAAS;OACT,cAAW;iBACZ;OAEQ,CAAA,CAEJ;;KAEX,kBAAC,OAAD;MAAK,WAAW,EAAE;MAAO;MAAe,CAAA;KACvC,MAAW,KAAA,KAAa,kBAAC,UAAD;MAAQ,WAAW,EAAE;gBAAS;MAAgB,CAAA;KACtE,EAAA,CAAA;IAEK,CAAA;GACH,CAAA;EACE,CAAA,CACd,EAAA,CAAA"}
@@ -44,4 +44,4 @@ function i({ children: i, className: a, ...o }) {
44
44
  //#endregion
45
45
  export { i as n, r as t };
46
46
 
47
- //# sourceMappingURL=Disclosure-aSX2CZlw.js.map
47
+ //# sourceMappingURL=Disclosure-DiA6UKdM.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Disclosure-aSX2CZlw.js","names":[],"sources":["../../src/components/Disclosure/Disclosure.module.css","../../src/components/Disclosure/Disclosure.tsx"],"sourcesContent":["/* Every dimension is stated because a host app's bare-element defaults can be\n authored at zero specificity — labkit's `:where(.lk-root) :where(button)`\n sets `height: var(--wzl-control-h)` and horizontal padding, which would\n otherwise stretch a 12px mark into a full-height control. */\n.twisty {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* A floor, not a size: a control sized to a 12px glyph is a 12px target,\n which is under every pointer-size guideline there is. Stated as a minimum\n so a larger mark grows the target with it rather than being shrunk to fit\n — a flex item's default `flex-shrink: 1` would otherwise crush it. */\n min-width: var(--wzl-disclosure-target, 20px);\n min-height: var(--wzl-disclosure-target, 20px);\n padding: 0;\n border: 0;\n border-radius: var(--wzl-radius-sm);\n background: none;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n color: var(--wzl-fg-muted);\n line-height: 1;\n cursor: pointer;\n}\n\n.twisty:hover:not(:disabled) {\n color: var(--wzl-fg);\n background: var(--wzl-surface-raised);\n}\n\n.twisty:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: -2px;\n color: var(--wzl-fg);\n}\n\n.twisty:disabled {\n color: var(--wzl-fg-subtle);\n cursor: not-allowed;\n}\n\n/* `currentColor` so the mark follows the button's own hover and disabled\n colors, and so a consumer can recolor the pair with one declaration. */\n.mark {\n display: block;\n flex: none;\n fill: currentColor;\n transition: transform var(--wzl-motion-fast, 90ms) var(--wzl-ease-out, ease);\n}\n\n.twisty[aria-expanded='true'] .mark {\n transform: rotate(90deg);\n}\n\n/* `direction=\"down\"` starts pointing down and lifts to point up. */\n.down .mark {\n transform: rotate(90deg);\n}\n\n.down[aria-expanded='true'] .mark {\n transform: rotate(-90deg);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .mark {\n transition: none;\n }\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--wzl-disclosure-gap, 4px);\n}\n\n/* The row's content takes the rest and is allowed to shrink; the twisty is\n not, so a long label truncates rather than crushing the control. */\n.rowBody {\n flex: 1 1 auto;\n min-width: 0;\n}\n","import type { ReactNode } from 'react';\nimport s from './Disclosure.module.css';\n\n/** Which way the mark points when the section is closed. */\nexport type DisclosureDirection = 'right' | 'down';\n\n/** Props for `<Disclosure>`. */\nexport interface DisclosureProps {\n /** Whether the section it controls is open. The consumer owns this. */\n open: boolean;\n onToggle: () => void;\n /**\n * Names the section, for a screen reader. The control has no text of its\n * own, so without this it announces as an unlabeled button.\n */\n label: string;\n /**\n * `id` of the element this expands. Sets `aria-controls`, which lets a\n * screen reader move to the revealed content.\n */\n controls?: string;\n /** Which way the closed mark points. `'right'` (default) rotates down when\n * open; `'down'` rotates up. */\n direction?: DisclosureDirection;\n /** Mark size in px. The hit target is at least 20px and grows with it.\n * Default 12. */\n size?: number;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * The twisty on a collapsible section: a triangle that turns as it opens.\n *\n * Presentational — it holds no open/closed state and renders no children.\n * The consumer owns the state and the panel; this is the control that toggles\n * it, and `aria-expanded` is what ties the two together.\n *\n * Three things it settles that a hand-rolled twisty keeps getting wrong. The\n * mark is drawn rather than typed, because `--wzl-font-ui` carries no ▸/▾ and\n * a text glyph falls back to whatever the system offers at whatever size that\n * font renders it — around 6px against 13px body text, which reads as dirt on\n * the screen. The hit target is larger than the mark. And it is a sibling of\n * the row's label rather than a child, so clicking to expand does not actuate\n * the label's own control.\n *\n * Deliberately not part of the icon register, on the same grounds as\n * `DragHandleGlyph`: that register is outline strokes at a fixed weight, and\n * `base.mjs` rejects a solid triangle in it by name. A disclosure mark is\n * filled.\n */\nexport function Disclosure({\n open,\n onToggle,\n label,\n controls,\n direction = 'right',\n size = 12,\n disabled,\n className,\n}: DisclosureProps) {\n const cls = [s.twisty, direction === 'down' && s.down, className].filter(Boolean).join(' ');\n return (\n <button\n type=\"button\"\n className={cls}\n aria-expanded={open}\n aria-controls={controls}\n aria-label={label}\n disabled={disabled}\n onClick={onToggle}\n >\n <svg\n className={s.mark}\n viewBox=\"0 0 12 12\"\n width={size}\n height={size}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n {/* Vertex on the right edge at the vertical center, so the rotation\n about the box center keeps the point on the same circle. */}\n <path d=\"M4 2.5 L8.5 6 L4 9.5 Z\" />\n </svg>\n </button>\n );\n}\n\n/** Props for `<DisclosureRow>`. */\nexport interface DisclosureRowProps extends Omit<DisclosureProps, 'className'> {\n /** The row's own content — a label, a checkbox, a count. */\n children: ReactNode;\n className?: string;\n}\n\n/**\n * A `<Disclosure>` and a row of content beside it, laid out so the twisty\n * leads and the content takes the rest.\n *\n * The layout is the point: the twisty sits *outside* whatever the row puts in\n * it, so a row whose content is a `<label>` wrapping a checkbox stays\n * clickable as a label without the twisty actuating it.\n */\nexport function DisclosureRow({ children, className, ...twisty }: DisclosureRowProps) {\n return (\n <div className={className ? `${s.row} ${className}` : s.row}>\n <Disclosure {...twisty} />\n <div className={s.rowBody}>{children}</div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;ACmDA,SAAgB,EAAW,EACzB,SACA,aACA,UACA,aACA,eAAY,SACZ,UAAO,IACP,aACA,gBACkB;AAElB,QACE,kBAAC,UAAD;EACE,MAAK;EACL,WAJQ;GAAC,EAAE;GAAQ,MAAc,UAAU,EAAE;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAIxE;EACX,iBAAe;EACf,iBAAe;EACf,cAAY;EACF;EACV,SAAS;YAET,kBAAC,OAAD;GACE,WAAW,EAAE;GACb,SAAQ;GACR,OAAO;GACP,QAAQ;GACR,eAAY;GACZ,WAAU;aAIV,kBAAC,QAAD,EAAM,GAAE,0BAA2B,CAAA;GAC/B,CAAA;EACC,CAAA;;AAmBb,SAAgB,EAAc,EAAE,aAAU,cAAW,GAAG,KAA8B;AACpF,QACE,kBAAC,OAAD;EAAK,WAAW,IAAY,GAAG,EAAE,IAAI,GAAG,MAAc,EAAE;YAAxD,CACE,kBAAC,GAAD,EAAY,GAAI,GAAU,CAAA,EAC1B,kBAAC,OAAD;GAAK,WAAW,EAAE;GAAU;GAAe,CAAA,CACvC"}
1
+ {"version":3,"file":"Disclosure-DiA6UKdM.js","names":[],"sources":["../../src/components/Disclosure/Disclosure.module.css","../../src/components/Disclosure/Disclosure.tsx"],"sourcesContent":["/* Every dimension is stated because a host app's bare-element defaults can be\n authored at zero specificity — labkit's `:where(.lk-root) :where(button)`\n sets `height: var(--wzl-control-h)` and horizontal padding, which would\n otherwise stretch a 12px mark into a full-height control. */\n.twisty {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n /* A floor, not a size: a control sized to a 12px glyph is a 12px target,\n which is under every pointer-size guideline there is. Stated as a minimum\n so a larger mark grows the target with it rather than being shrunk to fit\n — a flex item's default `flex-shrink: 1` would otherwise crush it. */\n min-width: var(--wzl-disclosure-target, 20px);\n min-height: var(--wzl-disclosure-target, 20px);\n padding: 0;\n border: 0;\n border-radius: var(--wzl-radius-sm);\n background: none;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n color: var(--wzl-fg-muted);\n line-height: 1;\n cursor: pointer;\n}\n\n.twisty:hover:not(:disabled) {\n color: var(--wzl-fg);\n background: var(--wzl-surface-raised);\n}\n\n.twisty:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: -2px;\n color: var(--wzl-fg);\n}\n\n.twisty:disabled {\n color: var(--wzl-fg-subtle);\n cursor: not-allowed;\n}\n\n/* `currentColor` so the mark follows the button's own hover and disabled\n colors, and so a consumer can recolor the pair with one declaration. */\n.mark {\n display: block;\n flex: none;\n fill: currentColor;\n transition: transform var(--wzl-motion-fast, 90ms) var(--wzl-ease-out, ease);\n}\n\n.twisty[aria-expanded='true'] .mark {\n transform: rotate(90deg);\n}\n\n/* `direction=\"down\"` starts pointing down and lifts to point up. */\n.down .mark {\n transform: rotate(90deg);\n}\n\n.down[aria-expanded='true'] .mark {\n transform: rotate(-90deg);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .mark {\n transition: none;\n }\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--wzl-disclosure-gap, 4px);\n}\n\n/* The row's content takes the rest and is allowed to shrink; the twisty is\n not, so a long label truncates rather than crushing the control. */\n.rowBody {\n flex: 1 1 auto;\n min-width: 0;\n}\n","import type { ReactNode } from 'react';\nimport s from './Disclosure.module.css';\n\n/** Which way the mark points when the section is closed. */\nexport type DisclosureDirection = 'right' | 'down';\n\n/** Props for `<Disclosure>`. */\nexport interface DisclosureProps {\n /** Whether the section it controls is open. The consumer owns this. */\n open: boolean;\n onToggle: () => void;\n /**\n * Names the section, for a screen reader. The control has no text of its\n * own, so without this it announces as an unlabeled button.\n */\n label: string;\n /**\n * `id` of the element this expands. Sets `aria-controls`, which lets a\n * screen reader move to the revealed content.\n */\n controls?: string;\n /** Which way the closed mark points. `'right'` (default) rotates down when\n * open; `'down'` rotates up. */\n direction?: DisclosureDirection;\n /** Mark size in px. The hit target is at least 20px and grows with it.\n * Default 12. */\n size?: number;\n disabled?: boolean;\n className?: string;\n}\n\n/**\n * The twisty on a collapsible section: a triangle that turns as it opens.\n *\n * Presentational — it holds no open/closed state and renders no children.\n * The consumer owns the state and the panel; this is the control that toggles\n * it, and `aria-expanded` is what ties the two together.\n *\n * Three things it settles that a hand-rolled twisty keeps getting wrong. The\n * mark is drawn rather than typed, because `--wzl-font-ui` carries no ▸/▾ and\n * a text glyph falls back to whatever the system offers at whatever size that\n * font renders it — around 6px against 13px body text, which reads as dirt on\n * the screen. The hit target is larger than the mark. And it is a sibling of\n * the row's label rather than a child, so clicking to expand does not actuate\n * the label's own control.\n *\n * Deliberately not part of the icon register, on the same grounds as\n * `DragHandleGlyph`: that register is outline strokes at a fixed weight, and\n * `base.mjs` rejects a solid triangle in it by name. A disclosure mark is\n * filled.\n */\nexport function Disclosure({\n open,\n onToggle,\n label,\n controls,\n direction = 'right',\n size = 12,\n disabled,\n className,\n}: DisclosureProps) {\n const cls = [s.twisty, direction === 'down' && s.down, className].filter(Boolean).join(' ');\n return (\n <button\n type=\"button\"\n className={cls}\n aria-expanded={open}\n aria-controls={controls}\n aria-label={label}\n disabled={disabled}\n onClick={onToggle}\n >\n <svg\n className={s.mark}\n viewBox=\"0 0 12 12\"\n width={size}\n height={size}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n {/* Vertex on the right edge at the vertical center, so the rotation\n about the box center keeps the point on the same circle. */}\n <path d=\"M4 2.5 L8.5 6 L4 9.5 Z\" />\n </svg>\n </button>\n );\n}\n\n/** Props for `<DisclosureRow>`. */\nexport interface DisclosureRowProps extends Omit<DisclosureProps, 'className'> {\n /** The row's own content — a label, a checkbox, a count. */\n children: ReactNode;\n className?: string;\n}\n\n/**\n * A `<Disclosure>` and a row of content beside it, laid out so the twisty\n * leads and the content takes the rest.\n *\n * The layout is the point: the twisty sits *outside* whatever the row puts in\n * it, so a row whose content is a `<label>` wrapping a checkbox stays\n * clickable as a label without the twisty actuating it.\n */\nexport function DisclosureRow({ children, className, ...twisty }: DisclosureRowProps) {\n return (\n <div className={className ? `${s.row} ${className}` : s.row}>\n <Disclosure {...twisty} />\n <div className={s.rowBody}>{children}</div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;ACmDA,SAAgB,EAAW,EACzB,SACA,aACA,UACA,aACA,eAAY,SACZ,UAAO,IACP,aACA,gBACkB;AAElB,QACE,kBAAC,UAAD;EACE,MAAK;EACL,WAJQ;GAAC,EAAE;GAAQ,MAAc,UAAU,EAAE;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAIxE;EACX,iBAAe;EACf,iBAAe;EACf,cAAY;EACF;EACV,SAAS;YAET,kBAAC,OAAD;GACE,WAAW,EAAE;GACb,SAAQ;GACR,OAAO;GACP,QAAQ;GACR,eAAY;GACZ,WAAU;aAIV,kBAAC,QAAD,EAAM,GAAE,0BAA2B,CAAA;GAC/B,CAAA;EACC,CAAA;;AAmBb,SAAgB,EAAc,EAAE,aAAU,cAAW,GAAG,KAA8B;AACpF,QACE,kBAAC,OAAD;EAAK,WAAW,IAAY,GAAG,EAAE,IAAI,GAAG,MAAc,EAAE;YAAxD,CACE,kBAAC,GAAD,EAAY,GAAI,GAAU,CAAA,EAC1B,kBAAC,OAAD;GAAK,WAAW,EAAE;GAAU;GAAe,CAAA,CACvC"}
@@ -44,4 +44,4 @@ function n({ size: n = 16 }) {
44
44
  //#endregion
45
45
  export { n as t };
46
46
 
47
- //# sourceMappingURL=DragHandleGlyph-C6GJyfnm.js.map
47
+ //# sourceMappingURL=DragHandleGlyph-CppAz4_8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DragHandleGlyph-C6GJyfnm.js","names":[],"sources":["../../src/components/DragHandleGlyph/DragHandleGlyph.tsx"],"sourcesContent":["/** Props for `<DragHandleGlyph>`. */\nexport interface DragHandleGlyphProps {\n /** Height in px; width scales with it. Default 16. */\n size?: number;\n}\n\n/** The two-column dot grip used on anything draggable by a handle.\n *\n * Deliberately not part of the icon register: that register is outline strokes\n * at a fixed weight, and a grip is filled dots. Forcing it in would either\n * break the register's rule or produce a worse glyph. */\nexport function DragHandleGlyph({ size = 16 }: DragHandleGlyphProps) {\n return (\n <svg\n width={(size * 8) / 16}\n height={size}\n viewBox=\"0 0 8 16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <circle cx=\"2\" cy=\"3\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"3\" r=\"1.1\" />\n <circle cx=\"2\" cy=\"8\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"8\" r=\"1.1\" />\n <circle cx=\"2\" cy=\"13\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"13\" r=\"1.1\" />\n </svg>\n );\n}\n"],"mappings":";;AAWA,SAAgB,EAAgB,EAAE,UAAO,MAA4B;AACnE,QACE,kBAAC,OAAD;EACE,OAAQ,IAAO,IAAK;EACpB,QAAQ;EACR,SAAQ;EACR,MAAK;EACL,eAAY;YALd;GAOE,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAI,GAAE;IAAQ,CAAA;GAChC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAI,GAAE;IAAQ,CAAA;GAChC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAI,GAAE;IAAQ,CAAA;GAChC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAI,GAAE;IAAQ,CAAA;GAChC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAK,GAAE;IAAQ,CAAA;GACjC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAK,GAAE;IAAQ,CAAA;GAC7B"}
1
+ {"version":3,"file":"DragHandleGlyph-CppAz4_8.js","names":[],"sources":["../../src/components/DragHandleGlyph/DragHandleGlyph.tsx"],"sourcesContent":["/** Props for `<DragHandleGlyph>`. */\nexport interface DragHandleGlyphProps {\n /** Height in px; width scales with it. Default 16. */\n size?: number;\n}\n\n/** The two-column dot grip used on anything draggable by a handle.\n *\n * Deliberately not part of the icon register: that register is outline strokes\n * at a fixed weight, and a grip is filled dots. Forcing it in would either\n * break the register's rule or produce a worse glyph. */\nexport function DragHandleGlyph({ size = 16 }: DragHandleGlyphProps) {\n return (\n <svg\n width={(size * 8) / 16}\n height={size}\n viewBox=\"0 0 8 16\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n >\n <circle cx=\"2\" cy=\"3\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"3\" r=\"1.1\" />\n <circle cx=\"2\" cy=\"8\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"8\" r=\"1.1\" />\n <circle cx=\"2\" cy=\"13\" r=\"1.1\" />\n <circle cx=\"6\" cy=\"13\" r=\"1.1\" />\n </svg>\n );\n}\n"],"mappings":";;AAWA,SAAgB,EAAgB,EAAE,UAAO,MAA4B;AACnE,QACE,kBAAC,OAAD;EACE,OAAQ,IAAO,IAAK;EACpB,QAAQ;EACR,SAAQ;EACR,MAAK;EACL,eAAY;YALd;GAOE,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAI,GAAE;IAAQ,CAAA;GAChC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAI,GAAE;IAAQ,CAAA;GAChC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAI,GAAE;IAAQ,CAAA;GAChC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAI,GAAE;IAAQ,CAAA;GAChC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAK,GAAE;IAAQ,CAAA;GACjC,kBAAC,UAAD;IAAQ,IAAG;IAAI,IAAG;IAAK,GAAE;IAAQ,CAAA;GAC7B"}
@@ -28,4 +28,4 @@ var r = {
28
28
  //#endregion
29
29
  export { r as n, n as t };
30
30
 
31
- //# sourceMappingURL=Field-BUroh92b.js.map
31
+ //# sourceMappingURL=Field-dzlxOGxT.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Field-BUroh92b.js","names":[],"sources":["../../src/components/Field/Field.module.css","../../src/components/Field/Field.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.label {\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-medium);\n color: var(--wzl-fg);\n cursor: default;\n}\n\n.hint {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n line-height: 1.4;\n}\n\n.error {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-danger);\n line-height: 1.4;\n}\n\n.row {\n flex-direction: row;\n align-items: center;\n gap: 8px;\n}\n\n.row .label {\n flex: 1;\n min-width: 0;\n}\n","import type { ReactNode } from 'react';\nimport s from './Field.module.css';\n\n/**\n * Whether a {@link Field} stacks its label above the control or sits them\n * side by side.\n */\nexport type FieldOrientation = 'stacked' | 'row';\n\n/** Props for {@link Field}. */\nexport type FieldProps = {\n orientation?: FieldOrientation;\n className?: string;\n children?: ReactNode;\n};\n\n/**\n * Layout primitive that arranges a label, control, hint, and error in the\n * canonical weasel-ui form-row shape. The control itself owns its semantics\n * (RAC `TextField`, `Checkbox`, etc.); this just paints the layout.\n *\n * Most users will instead reach for `Input` / `Checkbox` / `Switch`, which\n * compose Field internally. Use Field directly when you need a custom\n * control to sit in the same visual rhythm.\n */\nexport function Field({ orientation = 'stacked', className, children }: FieldProps) {\n const cls = [s.field, orientation === 'row' && s.row, className].filter(Boolean).join(' ');\n return <div className={cls}>{children}</div>;\n}\n\n/**\n * The class names {@link Field} paints its slots with, so a control composing\n * its own layout can match the same form-row rhythm.\n */\nexport const fieldClasses = {\n root: s.field,\n row: s.row,\n label: s.label,\n hint: s.hint,\n error: s.error,\n};\n"],"mappings":";;;;;;;;;;ACyBA,SAAgB,EAAM,EAAE,iBAAc,WAAW,cAAW,eAAwB;AAElF,QAAO,kBAAC,OAAD;EAAK,WADA;GAAC,EAAE;GAAO,MAAgB,SAAS,EAAE;GAAK;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAC/D;EAAM;EAAe,CAAA;;AAO9C,IAAa,IAAe;CAC1B,MAAM,EAAE;CACR,KAAK,EAAE;CACP,OAAO,EAAE;CACT,MAAM,EAAE;CACR,OAAO,EAAE;CACV"}
1
+ {"version":3,"file":"Field-dzlxOGxT.js","names":[],"sources":["../../src/components/Field/Field.module.css","../../src/components/Field/Field.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.label {\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-medium);\n color: var(--wzl-fg);\n cursor: default;\n}\n\n.hint {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n line-height: 1.4;\n}\n\n.error {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-danger);\n line-height: 1.4;\n}\n\n.row {\n flex-direction: row;\n align-items: center;\n gap: 8px;\n}\n\n.row .label {\n flex: 1;\n min-width: 0;\n}\n","import type { ReactNode } from 'react';\nimport s from './Field.module.css';\n\n/**\n * Whether a {@link Field} stacks its label above the control or sits them\n * side by side.\n */\nexport type FieldOrientation = 'stacked' | 'row';\n\n/** Props for {@link Field}. */\nexport type FieldProps = {\n orientation?: FieldOrientation;\n className?: string;\n children?: ReactNode;\n};\n\n/**\n * Layout primitive that arranges a label, control, hint, and error in the\n * canonical weasel-ui form-row shape. The control itself owns its semantics\n * (RAC `TextField`, `Checkbox`, etc.); this just paints the layout.\n *\n * Most users will instead reach for `Input` / `Checkbox` / `Switch`, which\n * compose Field internally. Use Field directly when you need a custom\n * control to sit in the same visual rhythm.\n */\nexport function Field({ orientation = 'stacked', className, children }: FieldProps) {\n const cls = [s.field, orientation === 'row' && s.row, className].filter(Boolean).join(' ');\n return <div className={cls}>{children}</div>;\n}\n\n/**\n * The class names {@link Field} paints its slots with, so a control composing\n * its own layout can match the same form-row rhythm.\n */\nexport const fieldClasses = {\n root: s.field,\n row: s.row,\n label: s.label,\n hint: s.hint,\n error: s.error,\n};\n"],"mappings":";;;;;;;;;;ACyBA,SAAgB,EAAM,EAAE,iBAAc,WAAW,cAAW,eAAwB;AAElF,QAAO,kBAAC,OAAD;EAAK,WADA;GAAC,EAAE;GAAO,MAAgB,SAAS,EAAE;GAAK;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAC/D;EAAM;EAAe,CAAA;;AAO9C,IAAa,IAAe;CAC1B,MAAM,EAAE;CACR,KAAK,EAAE;CACP,OAAO,EAAE;CACT,MAAM,EAAE;CACR,OAAO,EAAE;CACV"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./Select-DGafwTF0.js";
1
+ import { t as e } from "./Select-CacMfs4v.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  import { listCanvasFonts as n, listFonts as r, resolveFontVariant as i } from "@weasel-js/font";
4
4
  //#region src/components/FontFamilySelect/FontFamilySelect.tsx
@@ -26,4 +26,4 @@ function o(i) {
26
26
  //#endregion
27
27
  export { o as t };
28
28
 
29
- //# sourceMappingURL=FontFamilySelect-xjOHQMsK.js.map
29
+ //# sourceMappingURL=FontFamilySelect-DIJ4phXz.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FontFamilySelect-xjOHQMsK.js","names":[],"sources":["../../src/components/FontFamilySelect/FontFamilySelect.tsx"],"sourcesContent":["/**\n * Font-family picker driven by the live font registry.\n *\n * **What it offers.** Both tiers that can actually paint: `listFonts()` for\n * families with a baked atlas, and `listCanvasFonts()` for families the\n * dynamic canvas-SDF tier will serve. Offer what will render, invent\n * nothing — and `listCanvasFonts` reports service rather than membership,\n * so a family auto-enrolled under a policy that is no longer in force\n * doesn't appear.\n *\n * **What it does with a value it can't offer.** Nothing about the model\n * stops a node from naming a family that was never registered — a pasted\n * node, an imported SVG, a document from a session that loaded more fonts.\n * The control keeps that value visible as its own entry and names what is\n * actually painting instead, read structurally off\n * `resolveFontVariant().substituted`. Dropping the value would silently\n * rewrite the document on the next edit; showing it bare would claim a font\n * is in use that isn't.\n */\nimport { listCanvasFonts, listFonts, resolveFontVariant } from '@weasel-js/font';\nimport { Select } from '../Select';\n\nexport interface FontFamilySelectProps {\n /** Current family, or `undefined` when the sources disagree (`mixed`). */\n value?: string;\n /** Indeterminate presentation — the aggregated sources name different families. */\n mixed?: boolean;\n onChange: (family: string) => void;\n /** The weight / style the text actually renders at, so the substitution\n * probe reports the variant that paints rather than a nominal 400/normal. */\n weight?: number;\n fontStyle?: 'normal' | 'italic';\n 'aria-label'?: string;\n className?: string;\n}\n\n/** Label for a value that isn't in the registry: name the substitute when the\n * fallback chain found one, otherwise say only what is known. */\nfunction unregisteredLabel(family: string, weight: number, style: 'normal' | 'italic'): string {\n const { substituted } = resolveFontVariant(family, weight, style);\n return substituted\n ? `${family} — not loaded, showing ${substituted.resolved}`\n : `${family} — not loaded`;\n}\n\nexport function FontFamilySelect(props: FontFamilySelectProps) {\n const { value, mixed = false, onChange, weight = 400, fontStyle = 'normal', className } = props;\n // Both tiers that actually render: families with a baked atlas, and\n // families the dynamic canvas-SDF tier will serve. Neither list is a guess\n // — `listCanvasFonts` reports what the registry will really route, so a\n // family reaches this menu only if it can paint.\n const baked = listFonts();\n const canvas = listCanvasFonts().filter((c) => !baked.some((f) => f.family === c.family));\n const available = [...baked.map((f) => f.family), ...canvas.map((c) => c.family)];\n const options = available.map((family) => ({ value: family, label: family }));\n if (value !== undefined && !available.includes(value)) {\n options.push({ value, label: unregisteredLabel(value, weight, fontStyle) });\n }\n return (\n <Select<string>\n className={className}\n options={options}\n selectedKey={mixed ? null : (value ?? null)}\n placeholder={mixed ? 'Mixed' : undefined}\n onSelectionChange={onChange}\n aria-label={props['aria-label'] ?? 'Font'}\n />\n );\n}\n"],"mappings":";;;;AAsCA,SAAS,EAAkB,GAAgB,GAAgB,GAAoC;CAC7F,IAAM,EAAE,mBAAgB,EAAmB,GAAQ,GAAQ,EAAM;AACjE,QAAO,IACH,GAAG,EAAO,yBAAyB,EAAY,aAC/C,GAAG,EAAO;;AAGhB,SAAgB,EAAiB,GAA8B;CAC7D,IAAM,EAAE,UAAO,WAAQ,IAAO,aAAU,YAAS,KAAK,eAAY,UAAU,iBAAc,GAKpF,IAAQ,GAAW,EACnB,IAAS,GAAiB,CAAC,QAAQ,MAAM,CAAC,EAAM,MAAM,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EACnF,IAAY,CAAC,GAAG,EAAM,KAAK,MAAM,EAAE,OAAO,EAAE,GAAG,EAAO,KAAK,MAAM,EAAE,OAAO,CAAC,EAC3E,IAAU,EAAU,KAAK,OAAY;EAAE,OAAO;EAAQ,OAAO;EAAQ,EAAE;AAI7E,QAHI,MAAU,KAAA,KAAa,CAAC,EAAU,SAAS,EAAM,IACnD,EAAQ,KAAK;EAAE;EAAO,OAAO,EAAkB,GAAO,GAAQ,EAAU;EAAE,CAAC,EAG3E,kBAAC,GAAD;EACa;EACF;EACT,aAAa,IAAQ,OAAQ,KAAS;EACtC,aAAa,IAAQ,UAAU,KAAA;EAC/B,mBAAmB;EACnB,cAAY,EAAM,iBAAiB;EACnC,CAAA"}
1
+ {"version":3,"file":"FontFamilySelect-DIJ4phXz.js","names":[],"sources":["../../src/components/FontFamilySelect/FontFamilySelect.tsx"],"sourcesContent":["/**\n * Font-family picker driven by the live font registry.\n *\n * **What it offers.** Both tiers that can actually paint: `listFonts()` for\n * families with a baked atlas, and `listCanvasFonts()` for families the\n * dynamic canvas-SDF tier will serve. Offer what will render, invent\n * nothing — and `listCanvasFonts` reports service rather than membership,\n * so a family auto-enrolled under a policy that is no longer in force\n * doesn't appear.\n *\n * **What it does with a value it can't offer.** Nothing about the model\n * stops a node from naming a family that was never registered — a pasted\n * node, an imported SVG, a document from a session that loaded more fonts.\n * The control keeps that value visible as its own entry and names what is\n * actually painting instead, read structurally off\n * `resolveFontVariant().substituted`. Dropping the value would silently\n * rewrite the document on the next edit; showing it bare would claim a font\n * is in use that isn't.\n */\nimport { listCanvasFonts, listFonts, resolveFontVariant } from '@weasel-js/font';\nimport { Select } from '../Select';\n\nexport interface FontFamilySelectProps {\n /** Current family, or `undefined` when the sources disagree (`mixed`). */\n value?: string;\n /** Indeterminate presentation — the aggregated sources name different families. */\n mixed?: boolean;\n onChange: (family: string) => void;\n /** The weight / style the text actually renders at, so the substitution\n * probe reports the variant that paints rather than a nominal 400/normal. */\n weight?: number;\n fontStyle?: 'normal' | 'italic';\n 'aria-label'?: string;\n className?: string;\n}\n\n/** Label for a value that isn't in the registry: name the substitute when the\n * fallback chain found one, otherwise say only what is known. */\nfunction unregisteredLabel(family: string, weight: number, style: 'normal' | 'italic'): string {\n const { substituted } = resolveFontVariant(family, weight, style);\n return substituted\n ? `${family} — not loaded, showing ${substituted.resolved}`\n : `${family} — not loaded`;\n}\n\nexport function FontFamilySelect(props: FontFamilySelectProps) {\n const { value, mixed = false, onChange, weight = 400, fontStyle = 'normal', className } = props;\n // Both tiers that actually render: families with a baked atlas, and\n // families the dynamic canvas-SDF tier will serve. Neither list is a guess\n // — `listCanvasFonts` reports what the registry will really route, so a\n // family reaches this menu only if it can paint.\n const baked = listFonts();\n const canvas = listCanvasFonts().filter((c) => !baked.some((f) => f.family === c.family));\n const available = [...baked.map((f) => f.family), ...canvas.map((c) => c.family)];\n const options = available.map((family) => ({ value: family, label: family }));\n if (value !== undefined && !available.includes(value)) {\n options.push({ value, label: unregisteredLabel(value, weight, fontStyle) });\n }\n return (\n <Select<string>\n className={className}\n options={options}\n selectedKey={mixed ? null : (value ?? null)}\n placeholder={mixed ? 'Mixed' : undefined}\n onSelectionChange={onChange}\n aria-label={props['aria-label'] ?? 'Font'}\n />\n );\n}\n"],"mappings":";;;;AAsCA,SAAS,EAAkB,GAAgB,GAAgB,GAAoC;CAC7F,IAAM,EAAE,mBAAgB,EAAmB,GAAQ,GAAQ,EAAM;AACjE,QAAO,IACH,GAAG,EAAO,yBAAyB,EAAY,aAC/C,GAAG,EAAO;;AAGhB,SAAgB,EAAiB,GAA8B;CAC7D,IAAM,EAAE,UAAO,WAAQ,IAAO,aAAU,YAAS,KAAK,eAAY,UAAU,iBAAc,GAKpF,IAAQ,GAAW,EACnB,IAAS,GAAiB,CAAC,QAAQ,MAAM,CAAC,EAAM,MAAM,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,EACnF,IAAY,CAAC,GAAG,EAAM,KAAK,MAAM,EAAE,OAAO,EAAE,GAAG,EAAO,KAAK,MAAM,EAAE,OAAO,CAAC,EAC3E,IAAU,EAAU,KAAK,OAAY;EAAE,OAAO;EAAQ,OAAO;EAAQ,EAAE;AAI7E,QAHI,MAAU,KAAA,KAAa,CAAC,EAAU,SAAS,EAAM,IACnD,EAAQ,KAAK;EAAE;EAAO,OAAO,EAAkB,GAAO,GAAQ,EAAU;EAAE,CAAC,EAG3E,kBAAC,GAAD;EACa;EACF;EACT,aAAa,IAAQ,OAAQ,KAAS;EACtC,aAAa,IAAQ,UAAU,KAAA;EAC/B,mBAAmB;EACnB,cAAY,EAAM,iBAAiB;EACnC,CAAA"}
@@ -1,6 +1,6 @@
1
- import { t as e } from "./Slider-X6WcO-6s.js";
2
- import { t } from "./ToggleBar-fQGk_Wf_.js";
3
- import { t as n } from "./ColorField-CCBSv8dp.js";
1
+ import { t as e } from "./Slider-Bs-KSYgQ.js";
2
+ import { t } from "./ToggleBar-Bxwv6xvj.js";
3
+ import { t as n } from "./ColorField-C20aRvCg.js";
4
4
  import { Fragment as r, jsx as i, jsxs as a } from "react/jsx-runtime";
5
5
  import { fillInPoseFrame as o, fillToBoundsFrame as s, gradientGeometry as c, isGradientFill as l, sampleGradientStops as u, useActionsRegistry as d, useHandleDrag as f, useNodeOverlayFrame as p, withGradientKind as m } from "@weasel-js/core";
6
6
  import { useCallback as h, useRef as g } from "react";
@@ -307,4 +307,4 @@ function N(e) {
307
307
  //#endregion
308
308
  export { v as i, w as n, S as r, j as t };
309
309
 
310
- //# sourceMappingURL=GradientEditor-D1IVEkHO.js.map
310
+ //# sourceMappingURL=GradientEditor-BLpAtMDK.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GradientEditor-D1IVEkHO.js","names":[],"sources":["../../src/paintGradientTrack.tsx","../../src/components/GradientEditor/GradientEditor.module.css","../../src/components/GradientEditor/GradientEditor.tsx","../../src/components/GradientEditor/GradientHandles.module.css","../../src/components/GradientEditor/GradientHandles.tsx","../../src/components/GradientEditor/SceneGradientHandles.tsx"],"sourcesContent":["import type { ReactNode, CSSProperties } from 'react';\nimport type { TrackCtx } from './components/Slider';\n\n/**\n * Options for {@link paintGradientTrack}.\n *\n * `gradient` maps a normalized position along the track (0 to 1) to a CSS\n * color; `samples` is how many stops the resulting linear-gradient uses.\n * `activeRange`, given in the slider's own value units, keeps that span at\n * full strength and dims + hatches the rest.\n */\nexport type GradientTrackOpts = {\n gradient: (t: number) => string;\n samples?: number;\n activeRange?: [number, number];\n hatch?: {\n angleDeg?: number;\n stripe?: number;\n gap?: number;\n dim?: number;\n };\n};\n\nconst DEFAULT_HATCH = { angleDeg: 135, stripe: 2, gap: 4, dim: 75 };\n\n/**\n * Builds a `Slider` `renderTrack` function that paints the track as a\n * sampled color gradient, optionally dimming and hatching the portions\n * outside an active range.\n */\nexport function paintGradientTrack(opts: GradientTrackOpts): (ctx: TrackCtx) => ReactNode {\n const { gradient, samples = 16, activeRange, hatch } = opts;\n\n return (ctx: TrackCtx) => {\n const stops: string[] = [];\n for (let i = 0; i <= samples; i++) {\n const t = i / samples;\n stops.push(`${gradient(t)} ${(t * 100).toFixed(1)}%`);\n }\n const baseGradient = `linear-gradient(to right, ${stops.join(', ')})`;\n\n const layers: string[] = [];\n if (activeRange) {\n const lowPct = ctx.valueToFraction(activeRange[0]) * 100;\n const highPct = ctx.valueToFraction(activeRange[1]) * 100;\n const h = { ...DEFAULT_HATCH, ...hatch };\n const stripe = `repeating-linear-gradient(${h.angleDeg}deg, transparent 0 ${h.stripe}px, var(--wzl-surface) ${h.stripe}px ${h.stripe + h.gap}px)`;\n const dimColor = `color-mix(in srgb, var(--wzl-surface) ${h.dim}%, transparent)`;\n const dimOverlay = `linear-gradient(${dimColor}, ${dimColor})`;\n\n if (lowPct > 0) {\n layers.push(`${dimOverlay} left 0 / ${lowPct.toFixed(2)}% 100% no-repeat`);\n layers.push(`${stripe} left 0 / ${lowPct.toFixed(2)}% 100% no-repeat`);\n }\n if (highPct < 100) {\n const wR = (100 - highPct).toFixed(2);\n layers.push(`${dimOverlay} right 0 / ${wR}% 100% no-repeat`);\n layers.push(`${stripe} right 0 / ${wR}% 100% no-repeat`);\n }\n }\n layers.push(baseGradient);\n\n const style: CSSProperties = {\n position: 'absolute',\n inset: 0,\n background: layers.join(', '),\n };\n return <div style={style} />;\n };\n}\n",".root {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-inline-size: 0;\n}\n\n.swatches {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* Each stop's opacity slider would otherwise stretch the row to a width no\n * properties panel has; the chip stays legible, the slider yields. */\n.swatch {\n flex: 0 1 auto;\n min-inline-size: 0;\n}\n","import { useCallback, type ReactElement } from 'react';\nimport {\n sampleGradientStops,\n withGradientKind,\n type GradStop,\n type GradientFill,\n type GradientKind,\n} from '@weasel-js/core';\nimport { Slider, type Thumb } from '../Slider';\nimport { ColorField } from '../ColorField';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport { paintGradientTrack } from '../../paintGradientTrack';\nimport s from './GradientEditor.module.css';\n\nconst KINDS: readonly ToggleBarItem<GradientKind>[] = [\n { value: 'linear-gradient', label: 'Linear' },\n { value: 'radial-gradient', label: 'Radial' },\n { value: 'conic-gradient', label: 'Conic' },\n];\n\n/** Fewer than two stops is not a gradient any renderer can ramp between. */\nconst MIN_STOPS = 2;\n\n/**\n * Props for {@link GradientEditor}. `onInput` fires throughout a gesture and\n * `onChange` once at its end.\n */\nexport interface GradientEditorProps {\n /** The gradient being edited. */\n value: GradientFill;\n /**\n * Live value during a gesture — a stop drag, a color-picker scrub. Wire\n * it for preview; it fires many times per gesture and must not be\n * written to history.\n */\n onInput?: (next: GradientFill) => void;\n /** Committed value: one call per completed gesture. Pair with an\n * undoable write. */\n onChange: (next: GradientFill) => void;\n /** Show the linear / radial / conic switch. Default true; turn it off\n * when the surrounding UI already owns the kind. */\n kindSwitch?: boolean;\n className?: string;\n}\n\ntype StopThumb = Thumb & { color: string };\n\n/**\n * Editor for a gradient's kind and stop list.\n *\n * Geometry (`from`/`to`, `center`, `radius`, `angle`) is deliberately not\n * edited here — on a canvas that belongs on the artwork, via\n * `<GradientHandles>`. This component owns the parts with no spatial\n * meaning, so it composes into a properties panel at any width.\n *\n * Stops are addressed by their position in `value.stops`, which is never\n * reordered — dragging one stop past another leaves both indices alone, so\n * a drag can cross a neighbour without the two swapping under the pointer.\n * Rendering sorts a copy.\n */\nexport function GradientEditor(props: GradientEditorProps): ReactElement {\n const { value, onInput, onChange, kindSwitch = true, className } = props;\n const stops = value.stops;\n\n const withStops = useCallback(\n (next: GradStop[]): GradientFill => ({ ...value, stops: next }),\n [value],\n );\n\n const thumbs: StopThumb[] = stops.map((stop) => ({ value: stop.offset, color: stop.color }));\n\n const applyThumbs = (next: StopThumb[]): GradStop[] =>\n next.map((t) => ({ offset: t.value, color: t.color }));\n\n const setStopColor = (index: number, color: string): GradStop[] =>\n stops.map((stop, i) => (i === index ? { ...stop, color } : stop));\n\n // Sorted view for the swatch row, carrying each stop's real index so a\n // recolor writes back to the right entry.\n const ordered = stops\n .map((stop, index) => ({ stop, index }))\n .sort((a, b) => a.stop.offset - b.stop.offset);\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n {kindSwitch && (\n <ToggleBar<GradientKind>\n items={KINDS}\n value={value.fill}\n size=\"sm\"\n ariaLabel=\"Gradient kind\"\n onChange={(kind) => kind && onChange(withGradientKind(value, kind))}\n />\n )}\n\n <Slider<StopThumb>\n min={0}\n max={1}\n step={0.005}\n constraint=\"free\"\n thumbs={thumbs}\n ariaLabel=\"Gradient stops\"\n readoutPlacement=\"none\"\n onInput={(next) => onInput?.(withStops(applyThumbs(next)))}\n onChange={(next) => onChange(withStops(applyThumbs(next)))}\n onAddThumb={(at) => ({ value: at, color: sampleGradientStops(stops, at) })}\n onRemoveThumb={() => stops.length > MIN_STOPS}\n renderTrack={paintGradientTrack({\n gradient: (t) => sampleGradientStops(stops, t),\n samples: 32,\n })}\n />\n\n <div className={s.swatches}>\n {ordered.map(({ stop, index }) => (\n <ColorField\n key={index}\n value={stop.color}\n alpha\n aria-label={`Stop ${index + 1} at ${Math.round(stop.offset * 100)}%`}\n className={s.swatch}\n onInput={(hex) => onInput?.(withStops(setStopColor(index, hex)))}\n onChange={(hex) => onChange(withStops(setStopColor(index, hex)))}\n />\n ))}\n </div>\n </div>\n );\n}\n","/* The overlay covers the canvas but must not intercept tool input; only the\n * handles opt back into hit-testing. */\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* These sit over artwork of unknown color, not over the app's own surface,\n * so they are deliberately not themed: a themed handle disappears against\n * whatever the user painted underneath. White-on-dark reads on anything,\n * which is why every editor draws handles this way. */\n.guide {\n fill: none;\n stroke: #ffffff;\n stroke-opacity: 0.7;\n stroke-dasharray: 4 4;\n paint-order: stroke;\n}\n\n.handle {\n fill: #ffffff;\n stroke: #222222;\n stroke-width: 2;\n cursor: grab;\n pointer-events: auto;\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.handle:focus-visible {\n outline: 2px solid var(--wzl-accent, #4a9eff);\n outline-offset: 2px;\n}\n","import { useRef, type KeyboardEvent as ReactKeyboardEvent, type ReactElement, type ReactNode } from 'react';\nimport { useHandleDrag, gradientGeometry, type GradientFill } from '@weasel-js/core';\nimport s from './GradientHandles.module.css';\n\n/** Structural, and deliberately not exported — `Plot2D` and `CurveEditor`\n * each publish their own `Point`, and a third would only be ambiguous at\n * the package barrel. Consumers pass any `{ x, y }`. */\ninterface Point {\n x: number;\n y: number;\n}\n\n/**\n * Props for {@link GradientHandles}. `onInput` fires throughout a drag and\n * `onChange` once at its end.\n */\nexport interface GradientHandlesProps {\n /**\n * The gradient whose geometry these handles move, in a **resolved,\n * isotropic** frame — one where `radius` is a length in the same units as\n * `center.x` and a right angle is a right angle.\n *\n * A `units: 'bounds'` gradient is not such a frame: `x` and `y` are\n * fractions of two different lengths, so a circle there is an ellipse on\n * screen and polar math silently mixes scales. Resolve it first with\n * `fillInPoseFrame(fill, box)` and convert edits back with\n * `fillToBoundsFrame(next, box)`.\n */\n value: GradientFill;\n /**\n * Gradient space → overlay pixels. For a `units: 'local'` gradient this\n * is the node's local-to-screen transform; for `'world'`, the view.\n */\n toScreen: (p: Point) => Point;\n /** Overlay pixels → gradient space. Must invert `toScreen`. */\n toLocal: (p: Point) => Point;\n /** Live during a drag — wire for preview, do not write to history. */\n onInput?: (next: GradientFill) => void;\n /** Committed at drag end: one call per gesture. */\n onChange: (next: GradientFill) => void;\n /** Overlay size in CSS pixels. */\n width: number;\n height: number;\n className?: string;\n}\n\n/**\n * Direct-manipulation handles for a gradient's geometry, drawn as an SVG\n * overlay above a canvas: endpoints for linear, center and radius for\n * radial, center and angle arm for conic.\n *\n * Positioning is entirely the consumer's `toScreen` / `toLocal` — this\n * component never sees a view or a scene node, so the same handles serve a\n * node-local gradient, a world-space one, and a plain unzoomed preview.\n *\n * The overlay ignores pointer events except on the handles themselves, so\n * it can sit over live canvas content without swallowing tool input.\n */\nexport function GradientHandles(props: GradientHandlesProps): ReactElement {\n const { value, toScreen, toLocal, onInput, onChange, width, height, className } = props;\n\n return (\n <svg\n className={[s.overlay, className].filter(Boolean).join(' ')}\n width={width}\n height={height}\n >\n {renderForKind(value, toScreen, toLocal, onInput, onChange)}\n </svg>\n );\n}\n\nfunction renderForKind(\n value: GradientFill,\n toScreen: (p: Point) => Point,\n toLocal: (p: Point) => Point,\n onInput: ((next: GradientFill) => void) | undefined,\n onChange: (next: GradientFill) => void,\n): ReactNode {\n const emit = (next: GradientFill, phase: 'input' | 'commit'): void => {\n if (phase === 'input') onInput?.(next);\n else onChange(next);\n };\n\n if (value.fill === 'linear-gradient') {\n const from = toScreen(value.from);\n const to = toScreen(value.to);\n return (\n <>\n <Guide x1={from.x} y1={from.y} x2={to.x} y2={to.y} />\n <DragPoint\n at={from}\n label=\"Gradient start\"\n onDrag={(p, phase) => emit({ ...value, from: toLocal(p) }, phase)}\n />\n <DragPoint\n at={to}\n label=\"Gradient end\"\n onDrag={(p, phase) => emit({ ...value, to: toLocal(p) }, phase)}\n />\n </>\n );\n }\n\n if (value.fill === 'radial-gradient') {\n const center = toScreen(value.center);\n // The radius handle rides the +x axis of gradient space, so it stays on\n // the drawn circle under a rotated or anisotropic transform.\n const edge = toScreen({ x: value.center.x + value.radius, y: value.center.y });\n const screenRadius = Math.hypot(edge.x - center.x, edge.y - center.y);\n return (\n <>\n <circle className={s.guide} cx={center.x} cy={center.y} r={screenRadius} />\n <DragPoint\n at={center}\n label=\"Gradient center\"\n onDrag={(p, phase) => emit({ ...value, center: toLocal(p) }, phase)}\n />\n <DragPoint\n at={edge}\n label=\"Gradient radius\"\n onDrag={(p, phase) => {\n const local = toLocal(p);\n const radius = Math.hypot(local.x - value.center.x, local.y - value.center.y);\n emit({ ...value, radius: Math.max(MIN_RADIUS, radius) }, phase);\n }}\n />\n </>\n );\n }\n\n const center = toScreen(value.center);\n const { radius } = gradientGeometry(value);\n const tip = toScreen({\n x: value.center.x + Math.cos(value.angle) * radius,\n y: value.center.y + Math.sin(value.angle) * radius,\n });\n return (\n <>\n <Guide x1={center.x} y1={center.y} x2={tip.x} y2={tip.y} />\n <DragPoint\n at={center}\n label=\"Gradient center\"\n onDrag={(p, phase) => emit({ ...value, center: toLocal(p) }, phase)}\n />\n <DragPoint\n at={tip}\n label=\"Gradient angle\"\n onDrag={(p, phase) => {\n const local = toLocal(p);\n const angle = Math.atan2(local.y - value.center.y, local.x - value.center.x);\n emit({ ...value, angle }, phase);\n }}\n />\n </>\n );\n}\n\n/** A radius of zero divides by zero in the shader's `t`; keep it off the floor. */\nconst MIN_RADIUS = 1;\n\nfunction Guide(props: { x1: number; y1: number; x2: number; y2: number }): ReactElement {\n return <line className={s.guide} {...props} />;\n}\n\nfunction DragPoint({\n at,\n label,\n onDrag,\n}: {\n at: Point;\n label: string;\n onDrag: (p: Point, phase: 'input' | 'commit') => void;\n}): ReactElement {\n const start = useRef<Point>(at);\n const drag = useHandleDrag<SVGCircleElement>({\n onStart: () => { start.current = at; },\n onMove: (p) => { onDrag(p, 'input'); },\n onEnd: ({ point, moved }) => {\n // A press that never moved is not an edit and must not write anything.\n if (moved) onDrag(point, 'commit');\n },\n // An abandoned gesture is not an edit either — put the live preview back\n // where it started rather than committing where it was left.\n onCancel: () => { onDrag(start.current, 'input'); },\n });\n const onKeyDown = (e: ReactKeyboardEvent<SVGCircleElement>): void => {\n const amount = e.shiftKey ? KEY_STEP * 10 : KEY_STEP;\n let dx = 0;\n let dy = 0;\n if (e.key === 'ArrowLeft') dx = -amount;\n else if (e.key === 'ArrowRight') dx = amount;\n else if (e.key === 'ArrowUp') dy = -amount;\n else if (e.key === 'ArrowDown') dy = amount;\n else return;\n e.preventDefault();\n const next = { x: at.x + dx, y: at.y + dy };\n onDrag(next, 'input');\n onDrag(next, 'commit');\n };\n return (\n <circle\n className={s.handle}\n cx={at.x}\n cy={at.y}\n r={HANDLE_RADIUS}\n // Not `slider`: the handle carries a 2-D position, not one value, so\n // it has no `aria-valuenow` to honor the role's contract with.\n role=\"button\"\n aria-label={label}\n tabIndex={0}\n onKeyDown={onKeyDown}\n {...drag}\n />\n );\n}\n\nconst HANDLE_RADIUS = 7;\n\n/** One arrow-key step, in overlay pixels. */\nconst KEY_STEP = 1;\n","import { useRef, type ReactElement, type RefObject } from 'react';\nimport {\n fillInPoseFrame,\n fillToBoundsFrame,\n isGradientFill,\n useActionsRegistry,\n useNodeOverlayFrame,\n type FillStyle,\n type GradientFill,\n type RectPose,\n type Scene,\n type Stroke,\n type UiOngoingControl,\n type View,\n} from '@weasel-js/core';\nimport { GradientHandles } from './GradientHandles';\n\n/** The paint slots a node carries. Either may hold a gradient, and each has\n * its own action, so the overlay is told which one it is editing. */\nexport type PaintSlot = 'fill' | 'stroke';\n\n/** What `SceneGradientHandles` reads off a node. Every field is optional, so\n * any richer app data shape satisfies it. */\nexport interface PaintedNodeData {\n fill?: FillStyle | null;\n stroke?: Stroke | null;\n}\n\nexport interface SceneGradientHandlesProps<\n TData extends PaintedNodeData,\n TLayer extends string,\n TPose extends RectPose,\n> {\n scene: Scene<TData, TLayer, TPose>;\n /** The element the overlay is positioned in. Must be the canvas's own box,\n * or the handles land somewhere other than the paint. */\n containerRef: RefObject<HTMLElement | null>;\n /** The node whose paint is being edited — usually the lone selected id. */\n nodeId: string | null | undefined;\n /** Which of the node's two paints these handles move. */\n slot: PaintSlot;\n /** Current viewport; a thunk is re-read per projection. See\n * `useNodeOverlayFrame`. */\n view?: View | (() => View);\n className?: string;\n}\n\n/**\n * Scene-aware `GradientHandles`: on-canvas geometry handles for the gradient\n * in one node's `fill` or `stroke`, committed through the `setFill` /\n * `setStroke` actions as a single undo entry per drag.\n *\n * Renders nothing unless the targeted slot holds a gradient. The node stores\n * its gradient in `units: 'bounds'` — fractions of its own box, so the paint\n * survives pan, zoom and resize — which is not a frame polar math can work\n * in; the handles get it resolved onto the box and every edit is normalized\n * back on the way out.\n *\n * `slot` is a prop rather than state the kit keeps: an app that edits fill\n * and stroke separately already knows which one has focus.\n */\nexport function SceneGradientHandles<\n TData extends PaintedNodeData,\n TLayer extends string,\n TPose extends RectPose,\n>(props: SceneGradientHandlesProps<TData, TLayer, TPose>): ReactElement | null {\n const { scene, containerRef, nodeId, slot, view, className } = props;\n const actions = useActionsRegistry();\n const frame = useNodeOverlayFrame(scene, containerRef, nodeId, { view });\n const ctrlRef = useRef<UiOngoingControl | null>(null);\n\n const gradient = frame ? gradientInSlot(paintOf(scene, nodeId, slot)) : null;\n if (!frame || !gradient) return null;\n\n const dispatch = (next: GradientFill, phase: 'input' | 'commit'): void => {\n const paint = fillToBoundsFrame(next, frame.box);\n const id = slot === 'fill' ? 'setFill' : 'setStroke';\n if (!ctrlRef.current) ctrlRef.current = actions?.begin(id, { paint }) ?? null;\n else ctrlRef.current.update({ paint });\n if (phase === 'commit' && ctrlRef.current) {\n ctrlRef.current.end('commit');\n ctrlRef.current = null;\n }\n };\n\n return (\n <GradientHandles\n value={fillInPoseFrame(gradient, frame.box) as GradientFill}\n toScreen={frame.toScreen}\n toLocal={frame.toLocal}\n width={frame.width}\n height={frame.height}\n className={className}\n onInput={(next) => dispatch(next, 'input')}\n onChange={(next) => dispatch(next, 'commit')}\n />\n );\n}\n\nfunction paintOf<TData extends PaintedNodeData, TLayer extends string, TPose extends RectPose>(\n scene: Scene<TData, TLayer, TPose>,\n nodeId: string | null | undefined,\n slot: PaintSlot,\n): FillStyle | null | undefined {\n if (nodeId == null) return undefined;\n const data = (scene.nodes as ReadonlyMap<string, { data: TData }>).get(nodeId)?.data;\n if (!data) return undefined;\n return slot === 'fill' ? data.fill : data.stroke?.paint;\n}\n\nfunction gradientInSlot(paint: FillStyle | null | undefined): GradientFill | null {\n return isGradientFill(paint) ? paint : null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"GradientEditor-BLpAtMDK.js","names":[],"sources":["../../src/paintGradientTrack.tsx","../../src/components/GradientEditor/GradientEditor.module.css","../../src/components/GradientEditor/GradientEditor.tsx","../../src/components/GradientEditor/GradientHandles.module.css","../../src/components/GradientEditor/GradientHandles.tsx","../../src/components/GradientEditor/SceneGradientHandles.tsx"],"sourcesContent":["import type { ReactNode, CSSProperties } from 'react';\nimport type { TrackCtx } from './components/Slider';\n\n/**\n * Options for {@link paintGradientTrack}.\n *\n * `gradient` maps a normalized position along the track (0 to 1) to a CSS\n * color; `samples` is how many stops the resulting linear-gradient uses.\n * `activeRange`, given in the slider's own value units, keeps that span at\n * full strength and dims + hatches the rest.\n */\nexport type GradientTrackOpts = {\n gradient: (t: number) => string;\n samples?: number;\n activeRange?: [number, number];\n hatch?: {\n angleDeg?: number;\n stripe?: number;\n gap?: number;\n dim?: number;\n };\n};\n\nconst DEFAULT_HATCH = { angleDeg: 135, stripe: 2, gap: 4, dim: 75 };\n\n/**\n * Builds a `Slider` `renderTrack` function that paints the track as a\n * sampled color gradient, optionally dimming and hatching the portions\n * outside an active range.\n */\nexport function paintGradientTrack(opts: GradientTrackOpts): (ctx: TrackCtx) => ReactNode {\n const { gradient, samples = 16, activeRange, hatch } = opts;\n\n return (ctx: TrackCtx) => {\n const stops: string[] = [];\n for (let i = 0; i <= samples; i++) {\n const t = i / samples;\n stops.push(`${gradient(t)} ${(t * 100).toFixed(1)}%`);\n }\n const baseGradient = `linear-gradient(to right, ${stops.join(', ')})`;\n\n const layers: string[] = [];\n if (activeRange) {\n const lowPct = ctx.valueToFraction(activeRange[0]) * 100;\n const highPct = ctx.valueToFraction(activeRange[1]) * 100;\n const h = { ...DEFAULT_HATCH, ...hatch };\n const stripe = `repeating-linear-gradient(${h.angleDeg}deg, transparent 0 ${h.stripe}px, var(--wzl-surface) ${h.stripe}px ${h.stripe + h.gap}px)`;\n const dimColor = `color-mix(in srgb, var(--wzl-surface) ${h.dim}%, transparent)`;\n const dimOverlay = `linear-gradient(${dimColor}, ${dimColor})`;\n\n if (lowPct > 0) {\n layers.push(`${dimOverlay} left 0 / ${lowPct.toFixed(2)}% 100% no-repeat`);\n layers.push(`${stripe} left 0 / ${lowPct.toFixed(2)}% 100% no-repeat`);\n }\n if (highPct < 100) {\n const wR = (100 - highPct).toFixed(2);\n layers.push(`${dimOverlay} right 0 / ${wR}% 100% no-repeat`);\n layers.push(`${stripe} right 0 / ${wR}% 100% no-repeat`);\n }\n }\n layers.push(baseGradient);\n\n const style: CSSProperties = {\n position: 'absolute',\n inset: 0,\n background: layers.join(', '),\n };\n return <div style={style} />;\n };\n}\n",".root {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-inline-size: 0;\n}\n\n.swatches {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* Each stop's opacity slider would otherwise stretch the row to a width no\n * properties panel has; the chip stays legible, the slider yields. */\n.swatch {\n flex: 0 1 auto;\n min-inline-size: 0;\n}\n","import { useCallback, type ReactElement } from 'react';\nimport {\n sampleGradientStops,\n withGradientKind,\n type GradStop,\n type GradientFill,\n type GradientKind,\n} from '@weasel-js/core';\nimport { Slider, type Thumb } from '../Slider';\nimport { ColorField } from '../ColorField';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport { paintGradientTrack } from '../../paintGradientTrack';\nimport s from './GradientEditor.module.css';\n\nconst KINDS: readonly ToggleBarItem<GradientKind>[] = [\n { value: 'linear-gradient', label: 'Linear' },\n { value: 'radial-gradient', label: 'Radial' },\n { value: 'conic-gradient', label: 'Conic' },\n];\n\n/** Fewer than two stops is not a gradient any renderer can ramp between. */\nconst MIN_STOPS = 2;\n\n/**\n * Props for {@link GradientEditor}. `onInput` fires throughout a gesture and\n * `onChange` once at its end.\n */\nexport interface GradientEditorProps {\n /** The gradient being edited. */\n value: GradientFill;\n /**\n * Live value during a gesture — a stop drag, a color-picker scrub. Wire\n * it for preview; it fires many times per gesture and must not be\n * written to history.\n */\n onInput?: (next: GradientFill) => void;\n /** Committed value: one call per completed gesture. Pair with an\n * undoable write. */\n onChange: (next: GradientFill) => void;\n /** Show the linear / radial / conic switch. Default true; turn it off\n * when the surrounding UI already owns the kind. */\n kindSwitch?: boolean;\n className?: string;\n}\n\ntype StopThumb = Thumb & { color: string };\n\n/**\n * Editor for a gradient's kind and stop list.\n *\n * Geometry (`from`/`to`, `center`, `radius`, `angle`) is deliberately not\n * edited here — on a canvas that belongs on the artwork, via\n * `<GradientHandles>`. This component owns the parts with no spatial\n * meaning, so it composes into a properties panel at any width.\n *\n * Stops are addressed by their position in `value.stops`, which is never\n * reordered — dragging one stop past another leaves both indices alone, so\n * a drag can cross a neighbour without the two swapping under the pointer.\n * Rendering sorts a copy.\n */\nexport function GradientEditor(props: GradientEditorProps): ReactElement {\n const { value, onInput, onChange, kindSwitch = true, className } = props;\n const stops = value.stops;\n\n const withStops = useCallback(\n (next: GradStop[]): GradientFill => ({ ...value, stops: next }),\n [value],\n );\n\n const thumbs: StopThumb[] = stops.map((stop) => ({ value: stop.offset, color: stop.color }));\n\n const applyThumbs = (next: StopThumb[]): GradStop[] =>\n next.map((t) => ({ offset: t.value, color: t.color }));\n\n const setStopColor = (index: number, color: string): GradStop[] =>\n stops.map((stop, i) => (i === index ? { ...stop, color } : stop));\n\n // Sorted view for the swatch row, carrying each stop's real index so a\n // recolor writes back to the right entry.\n const ordered = stops\n .map((stop, index) => ({ stop, index }))\n .sort((a, b) => a.stop.offset - b.stop.offset);\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n {kindSwitch && (\n <ToggleBar<GradientKind>\n items={KINDS}\n value={value.fill}\n size=\"sm\"\n ariaLabel=\"Gradient kind\"\n onChange={(kind) => kind && onChange(withGradientKind(value, kind))}\n />\n )}\n\n <Slider<StopThumb>\n min={0}\n max={1}\n step={0.005}\n constraint=\"free\"\n thumbs={thumbs}\n ariaLabel=\"Gradient stops\"\n readoutPlacement=\"none\"\n onInput={(next) => onInput?.(withStops(applyThumbs(next)))}\n onChange={(next) => onChange(withStops(applyThumbs(next)))}\n onAddThumb={(at) => ({ value: at, color: sampleGradientStops(stops, at) })}\n onRemoveThumb={() => stops.length > MIN_STOPS}\n renderTrack={paintGradientTrack({\n gradient: (t) => sampleGradientStops(stops, t),\n samples: 32,\n })}\n />\n\n <div className={s.swatches}>\n {ordered.map(({ stop, index }) => (\n <ColorField\n key={index}\n value={stop.color}\n alpha\n aria-label={`Stop ${index + 1} at ${Math.round(stop.offset * 100)}%`}\n className={s.swatch}\n onInput={(hex) => onInput?.(withStops(setStopColor(index, hex)))}\n onChange={(hex) => onChange(withStops(setStopColor(index, hex)))}\n />\n ))}\n </div>\n </div>\n );\n}\n","/* The overlay covers the canvas but must not intercept tool input; only the\n * handles opt back into hit-testing. */\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* These sit over artwork of unknown color, not over the app's own surface,\n * so they are deliberately not themed: a themed handle disappears against\n * whatever the user painted underneath. White-on-dark reads on anything,\n * which is why every editor draws handles this way. */\n.guide {\n fill: none;\n stroke: #ffffff;\n stroke-opacity: 0.7;\n stroke-dasharray: 4 4;\n paint-order: stroke;\n}\n\n.handle {\n fill: #ffffff;\n stroke: #222222;\n stroke-width: 2;\n cursor: grab;\n pointer-events: auto;\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.handle:focus-visible {\n outline: 2px solid var(--wzl-accent, #4a9eff);\n outline-offset: 2px;\n}\n","import { useRef, type KeyboardEvent as ReactKeyboardEvent, type ReactElement, type ReactNode } from 'react';\nimport { useHandleDrag, gradientGeometry, type GradientFill } from '@weasel-js/core';\nimport s from './GradientHandles.module.css';\n\n/** Structural, and deliberately not exported — `Plot2D` and `CurveEditor`\n * each publish their own `Point`, and a third would only be ambiguous at\n * the package barrel. Consumers pass any `{ x, y }`. */\ninterface Point {\n x: number;\n y: number;\n}\n\n/**\n * Props for {@link GradientHandles}. `onInput` fires throughout a drag and\n * `onChange` once at its end.\n */\nexport interface GradientHandlesProps {\n /**\n * The gradient whose geometry these handles move, in a **resolved,\n * isotropic** frame — one where `radius` is a length in the same units as\n * `center.x` and a right angle is a right angle.\n *\n * A `units: 'bounds'` gradient is not such a frame: `x` and `y` are\n * fractions of two different lengths, so a circle there is an ellipse on\n * screen and polar math silently mixes scales. Resolve it first with\n * `fillInPoseFrame(fill, box)` and convert edits back with\n * `fillToBoundsFrame(next, box)`.\n */\n value: GradientFill;\n /**\n * Gradient space → overlay pixels. For a `units: 'local'` gradient this\n * is the node's local-to-screen transform; for `'world'`, the view.\n */\n toScreen: (p: Point) => Point;\n /** Overlay pixels → gradient space. Must invert `toScreen`. */\n toLocal: (p: Point) => Point;\n /** Live during a drag — wire for preview, do not write to history. */\n onInput?: (next: GradientFill) => void;\n /** Committed at drag end: one call per gesture. */\n onChange: (next: GradientFill) => void;\n /** Overlay size in CSS pixels. */\n width: number;\n height: number;\n className?: string;\n}\n\n/**\n * Direct-manipulation handles for a gradient's geometry, drawn as an SVG\n * overlay above a canvas: endpoints for linear, center and radius for\n * radial, center and angle arm for conic.\n *\n * Positioning is entirely the consumer's `toScreen` / `toLocal` — this\n * component never sees a view or a scene node, so the same handles serve a\n * node-local gradient, a world-space one, and a plain unzoomed preview.\n *\n * The overlay ignores pointer events except on the handles themselves, so\n * it can sit over live canvas content without swallowing tool input.\n */\nexport function GradientHandles(props: GradientHandlesProps): ReactElement {\n const { value, toScreen, toLocal, onInput, onChange, width, height, className } = props;\n\n return (\n <svg\n className={[s.overlay, className].filter(Boolean).join(' ')}\n width={width}\n height={height}\n >\n {renderForKind(value, toScreen, toLocal, onInput, onChange)}\n </svg>\n );\n}\n\nfunction renderForKind(\n value: GradientFill,\n toScreen: (p: Point) => Point,\n toLocal: (p: Point) => Point,\n onInput: ((next: GradientFill) => void) | undefined,\n onChange: (next: GradientFill) => void,\n): ReactNode {\n const emit = (next: GradientFill, phase: 'input' | 'commit'): void => {\n if (phase === 'input') onInput?.(next);\n else onChange(next);\n };\n\n if (value.fill === 'linear-gradient') {\n const from = toScreen(value.from);\n const to = toScreen(value.to);\n return (\n <>\n <Guide x1={from.x} y1={from.y} x2={to.x} y2={to.y} />\n <DragPoint\n at={from}\n label=\"Gradient start\"\n onDrag={(p, phase) => emit({ ...value, from: toLocal(p) }, phase)}\n />\n <DragPoint\n at={to}\n label=\"Gradient end\"\n onDrag={(p, phase) => emit({ ...value, to: toLocal(p) }, phase)}\n />\n </>\n );\n }\n\n if (value.fill === 'radial-gradient') {\n const center = toScreen(value.center);\n // The radius handle rides the +x axis of gradient space, so it stays on\n // the drawn circle under a rotated or anisotropic transform.\n const edge = toScreen({ x: value.center.x + value.radius, y: value.center.y });\n const screenRadius = Math.hypot(edge.x - center.x, edge.y - center.y);\n return (\n <>\n <circle className={s.guide} cx={center.x} cy={center.y} r={screenRadius} />\n <DragPoint\n at={center}\n label=\"Gradient center\"\n onDrag={(p, phase) => emit({ ...value, center: toLocal(p) }, phase)}\n />\n <DragPoint\n at={edge}\n label=\"Gradient radius\"\n onDrag={(p, phase) => {\n const local = toLocal(p);\n const radius = Math.hypot(local.x - value.center.x, local.y - value.center.y);\n emit({ ...value, radius: Math.max(MIN_RADIUS, radius) }, phase);\n }}\n />\n </>\n );\n }\n\n const center = toScreen(value.center);\n const { radius } = gradientGeometry(value);\n const tip = toScreen({\n x: value.center.x + Math.cos(value.angle) * radius,\n y: value.center.y + Math.sin(value.angle) * radius,\n });\n return (\n <>\n <Guide x1={center.x} y1={center.y} x2={tip.x} y2={tip.y} />\n <DragPoint\n at={center}\n label=\"Gradient center\"\n onDrag={(p, phase) => emit({ ...value, center: toLocal(p) }, phase)}\n />\n <DragPoint\n at={tip}\n label=\"Gradient angle\"\n onDrag={(p, phase) => {\n const local = toLocal(p);\n const angle = Math.atan2(local.y - value.center.y, local.x - value.center.x);\n emit({ ...value, angle }, phase);\n }}\n />\n </>\n );\n}\n\n/** A radius of zero divides by zero in the shader's `t`; keep it off the floor. */\nconst MIN_RADIUS = 1;\n\nfunction Guide(props: { x1: number; y1: number; x2: number; y2: number }): ReactElement {\n return <line className={s.guide} {...props} />;\n}\n\nfunction DragPoint({\n at,\n label,\n onDrag,\n}: {\n at: Point;\n label: string;\n onDrag: (p: Point, phase: 'input' | 'commit') => void;\n}): ReactElement {\n const start = useRef<Point>(at);\n const drag = useHandleDrag<SVGCircleElement>({\n onStart: () => { start.current = at; },\n onMove: (p) => { onDrag(p, 'input'); },\n onEnd: ({ point, moved }) => {\n // A press that never moved is not an edit and must not write anything.\n if (moved) onDrag(point, 'commit');\n },\n // An abandoned gesture is not an edit either — put the live preview back\n // where it started rather than committing where it was left.\n onCancel: () => { onDrag(start.current, 'input'); },\n });\n const onKeyDown = (e: ReactKeyboardEvent<SVGCircleElement>): void => {\n const amount = e.shiftKey ? KEY_STEP * 10 : KEY_STEP;\n let dx = 0;\n let dy = 0;\n if (e.key === 'ArrowLeft') dx = -amount;\n else if (e.key === 'ArrowRight') dx = amount;\n else if (e.key === 'ArrowUp') dy = -amount;\n else if (e.key === 'ArrowDown') dy = amount;\n else return;\n e.preventDefault();\n const next = { x: at.x + dx, y: at.y + dy };\n onDrag(next, 'input');\n onDrag(next, 'commit');\n };\n return (\n <circle\n className={s.handle}\n cx={at.x}\n cy={at.y}\n r={HANDLE_RADIUS}\n // Not `slider`: the handle carries a 2-D position, not one value, so\n // it has no `aria-valuenow` to honor the role's contract with.\n role=\"button\"\n aria-label={label}\n tabIndex={0}\n onKeyDown={onKeyDown}\n {...drag}\n />\n );\n}\n\nconst HANDLE_RADIUS = 7;\n\n/** One arrow-key step, in overlay pixels. */\nconst KEY_STEP = 1;\n","import { useRef, type ReactElement, type RefObject } from 'react';\nimport {\n fillInPoseFrame,\n fillToBoundsFrame,\n isGradientFill,\n useActionsRegistry,\n useNodeOverlayFrame,\n type FillStyle,\n type GradientFill,\n type RectPose,\n type Scene,\n type Stroke,\n type UiOngoingControl,\n type View,\n} from '@weasel-js/core';\nimport { GradientHandles } from './GradientHandles';\n\n/** The paint slots a node carries. Either may hold a gradient, and each has\n * its own action, so the overlay is told which one it is editing. */\nexport type PaintSlot = 'fill' | 'stroke';\n\n/** What `SceneGradientHandles` reads off a node. Every field is optional, so\n * any richer app data shape satisfies it. */\nexport interface PaintedNodeData {\n fill?: FillStyle | null;\n stroke?: Stroke | null;\n}\n\nexport interface SceneGradientHandlesProps<\n TData extends PaintedNodeData,\n TLayer extends string,\n TPose extends RectPose,\n> {\n scene: Scene<TData, TLayer, TPose>;\n /** The element the overlay is positioned in. Must be the canvas's own box,\n * or the handles land somewhere other than the paint. */\n containerRef: RefObject<HTMLElement | null>;\n /** The node whose paint is being edited — usually the lone selected id. */\n nodeId: string | null | undefined;\n /** Which of the node's two paints these handles move. */\n slot: PaintSlot;\n /** Current viewport; a thunk is re-read per projection. See\n * `useNodeOverlayFrame`. */\n view?: View | (() => View);\n className?: string;\n}\n\n/**\n * Scene-aware `GradientHandles`: on-canvas geometry handles for the gradient\n * in one node's `fill` or `stroke`, committed through the `setFill` /\n * `setStroke` actions as a single undo entry per drag.\n *\n * Renders nothing unless the targeted slot holds a gradient. The node stores\n * its gradient in `units: 'bounds'` — fractions of its own box, so the paint\n * survives pan, zoom and resize — which is not a frame polar math can work\n * in; the handles get it resolved onto the box and every edit is normalized\n * back on the way out.\n *\n * `slot` is a prop rather than state the kit keeps: an app that edits fill\n * and stroke separately already knows which one has focus.\n */\nexport function SceneGradientHandles<\n TData extends PaintedNodeData,\n TLayer extends string,\n TPose extends RectPose,\n>(props: SceneGradientHandlesProps<TData, TLayer, TPose>): ReactElement | null {\n const { scene, containerRef, nodeId, slot, view, className } = props;\n const actions = useActionsRegistry();\n const frame = useNodeOverlayFrame(scene, containerRef, nodeId, { view });\n const ctrlRef = useRef<UiOngoingControl | null>(null);\n\n const gradient = frame ? gradientInSlot(paintOf(scene, nodeId, slot)) : null;\n if (!frame || !gradient) return null;\n\n const dispatch = (next: GradientFill, phase: 'input' | 'commit'): void => {\n const paint = fillToBoundsFrame(next, frame.box);\n const id = slot === 'fill' ? 'setFill' : 'setStroke';\n if (!ctrlRef.current) ctrlRef.current = actions?.begin(id, { paint }) ?? null;\n else ctrlRef.current.update({ paint });\n if (phase === 'commit' && ctrlRef.current) {\n ctrlRef.current.end('commit');\n ctrlRef.current = null;\n }\n };\n\n return (\n <GradientHandles\n value={fillInPoseFrame(gradient, frame.box) as GradientFill}\n toScreen={frame.toScreen}\n toLocal={frame.toLocal}\n width={frame.width}\n height={frame.height}\n className={className}\n onInput={(next) => dispatch(next, 'input')}\n onChange={(next) => dispatch(next, 'commit')}\n />\n );\n}\n\nfunction paintOf<TData extends PaintedNodeData, TLayer extends string, TPose extends RectPose>(\n scene: Scene<TData, TLayer, TPose>,\n nodeId: string | null | undefined,\n slot: PaintSlot,\n): FillStyle | null | undefined {\n if (nodeId == null) return undefined;\n const data = (scene.nodes as ReadonlyMap<string, { data: TData }>).get(nodeId)?.data;\n if (!data) return undefined;\n return slot === 'fill' ? data.fill : data.stroke?.paint;\n}\n\nfunction gradientInSlot(paint: FillStyle | null | undefined): GradientFill | null {\n return isGradientFill(paint) ? paint : null;\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as e } from "./range.module-CO59aXEH.js";
1
+ import { t as e } from "./range.module-CvSfABmu.js";
2
2
  import { jsx as t } from "react/jsx-runtime";
3
3
  import "react";
4
4
  var n = { range: "_range_1on6e_4" };
@@ -26,4 +26,4 @@ function r(r) {
26
26
  //#endregion
27
27
  export { r as t };
28
28
 
29
- //# sourceMappingURL=InlineRange-DG2dvtb4.js.map
29
+ //# sourceMappingURL=InlineRange-CcfzY80y.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InlineRange-DG2dvtb4.js","names":[],"sources":["../../src/components/InlineRange/InlineRange.module.css","../../src/components/InlineRange/InlineRange.tsx"],"sourcesContent":["/* Track and thumb come from the shared range skin. What is local to\n InlineRange is the filled-to-N% paint, which no other surface has:\n `--slider-fill` carries the value, since no static rule expresses it. */\n.range {\n flex: 1;\n --wzl-range-bg: linear-gradient(\n to right,\n var(--wzl-accent) 0 var(--slider-fill, 0%),\n var(--wzl-border-strong) var(--slider-fill, 0%) 100%\n );\n --wzl-range-box-h: var(--wzl-slider-track-h);\n /* The gradient above IS the track, so the shared track paint must not land on top of it. */\n --wzl-range-track-bg: transparent;\n border-radius: var(--wzl-radius-pill);\n}\n","import { type CSSProperties, type InputHTMLAttributes, type ReactElement } from 'react';\nimport shared from '../range.module.css';\nimport s from './InlineRange.module.css';\n\n/** Props for {@link InlineRange} — a range input's, with `value` required. */\nexport type InlineRangeProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'type' | 'value' | 'min' | 'max'\n> & {\n value: number;\n min?: number;\n max?: number;\n};\n\n/**\n * Compact range input for a property row: one track, no label, no output.\n *\n * Distinct from the kit's other two — `Slider` is shaped for canvas scrubbing,\n * and `RangeSlider` is a full-width labelled form control on React Aria. This\n * one exists to sit inline beside a swatch or a readout, and to own the track\n * painting both of those surfaces need.\n */\nexport function InlineRange(props: InlineRangeProps): ReactElement {\n const { value, min = 0, max = 100, className, style, ...rest } = props;\n const span = max - min;\n const filled = span > 0 ? ((Math.min(Math.max(value, min), max) - min) / span) * 100 : 0;\n return (\n <input\n {...rest}\n type=\"range\"\n value={value}\n min={min}\n max={max}\n className={[shared.range, s.range, className].filter(Boolean).join(' ')}\n style={{ ['--slider-fill' as string]: `${filled}%`, ...style } as CSSProperties}\n />\n );\n}\n"],"mappings":";;;;;;ACsBA,SAAgB,EAAY,GAAuC;CACjE,IAAM,EAAE,UAAO,SAAM,GAAG,SAAM,KAAK,cAAW,UAAO,GAAG,MAAS,GAC3D,IAAO,IAAM,GACb,IAAS,IAAO,KAAM,KAAK,IAAI,KAAK,IAAI,GAAO,EAAI,EAAE,EAAI,GAAG,KAAO,IAAQ,MAAM;AACvF,QACE,kBAAC,SAAD;EACE,GAAI;EACJ,MAAK;EACE;EACF;EACA;EACL,WAAW;GAAC,EAAO;GAAO,EAAE;GAAO;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EACvE,OAAO;GAAG,iBAA4B,GAAG,EAAO;GAAI,GAAG;GAAO;EAC9D,CAAA"}
1
+ {"version":3,"file":"InlineRange-CcfzY80y.js","names":[],"sources":["../../src/components/InlineRange/InlineRange.module.css","../../src/components/InlineRange/InlineRange.tsx"],"sourcesContent":["/* Track and thumb come from the shared range skin. What is local to\n InlineRange is the filled-to-N% paint, which no other surface has:\n `--slider-fill` carries the value, since no static rule expresses it. */\n.range {\n flex: 1;\n --wzl-range-bg: linear-gradient(\n to right,\n var(--wzl-accent) 0 var(--slider-fill, 0%),\n var(--wzl-border-strong) var(--slider-fill, 0%) 100%\n );\n --wzl-range-box-h: var(--wzl-slider-track-h);\n /* The gradient above IS the track, so the shared track paint must not land on top of it. */\n --wzl-range-track-bg: transparent;\n border-radius: var(--wzl-radius-pill);\n}\n","import { type CSSProperties, type InputHTMLAttributes, type ReactElement } from 'react';\nimport shared from '../range.module.css';\nimport s from './InlineRange.module.css';\n\n/** Props for {@link InlineRange} — a range input's, with `value` required. */\nexport type InlineRangeProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n 'type' | 'value' | 'min' | 'max'\n> & {\n value: number;\n min?: number;\n max?: number;\n};\n\n/**\n * Compact range input for a property row: one track, no label, no output.\n *\n * Distinct from the kit's other two — `Slider` is shaped for canvas scrubbing,\n * and `RangeSlider` is a full-width labelled form control on React Aria. This\n * one exists to sit inline beside a swatch or a readout, and to own the track\n * painting both of those surfaces need.\n */\nexport function InlineRange(props: InlineRangeProps): ReactElement {\n const { value, min = 0, max = 100, className, style, ...rest } = props;\n const span = max - min;\n const filled = span > 0 ? ((Math.min(Math.max(value, min), max) - min) / span) * 100 : 0;\n return (\n <input\n {...rest}\n type=\"range\"\n value={value}\n min={min}\n max={max}\n className={[shared.range, s.range, className].filter(Boolean).join(' ')}\n style={{ ['--slider-fill' as string]: `${filled}%`, ...style } as CSSProperties}\n />\n );\n}\n"],"mappings":";;;;;;ACsBA,SAAgB,EAAY,GAAuC;CACjE,IAAM,EAAE,UAAO,SAAM,GAAG,SAAM,KAAK,cAAW,UAAO,GAAG,MAAS,GAC3D,IAAO,IAAM,GACb,IAAS,IAAO,KAAM,KAAK,IAAI,KAAK,IAAI,GAAO,EAAI,EAAE,EAAI,GAAG,KAAO,IAAQ,MAAM;AACvF,QACE,kBAAC,SAAD;EACE,GAAI;EACJ,MAAK;EACE;EACF;EACA;EACL,WAAW;GAAC,EAAO;GAAO,EAAE;GAAO;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EACvE,OAAO;GAAG,iBAA4B,GAAG,EAAO;GAAI,GAAG;GAAO;EAC9D,CAAA"}
@@ -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 { FieldError as i, Input as a, Label as o, Text as s, TextField as c } from "react-aria-components";
@@ -52,4 +52,4 @@ var l = {
52
52
  //#endregion
53
53
  export { u as t };
54
54
 
55
- //# sourceMappingURL=Input-CxMANia3.js.map
55
+ //# sourceMappingURL=Input-CoKOtyD0.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Input-CxMANia3.js","names":[],"sources":["../../src/components/Input/Input.module.css","../../src/components/Input/Input.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: center;\n gap: 6px;\n height: var(--wzl-field-h, var(--wzl-control-h));\n padding: 0 var(--wzl-field-pad-x);\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 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;\n}\n\n.frame input::placeholder {\n color: var(--wzl-fg-subtle);\n}\n\n.adornment {\n display: inline-flex;\n align-items: center;\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size-sm);\n pointer-events: none;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n TextField,\n Label,\n Input as RACInput,\n Text,\n FieldError,\n type TextFieldProps as RACTextFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './Input.module.css';\n\n/** Props for {@link Input}, on top of React Aria's `TextField` props. */\nexport type InputProps = Omit<RACTextFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n placeholder?: string;\n leadingAdornment?: ReactNode;\n trailingAdornment?: ReactNode;\n className?: string;\n};\n\n/**\n * Single-line text input wrapping React Aria's TextField. Supplies a\n * default skin against the `--wzl-*` token system, and exposes the\n * familiar label / description / errorMessage / adornment slot shape.\n *\n * `ref` forwards to the underlying `<input>`.\n */\nexport const Input = forwardRef(function Input(\n props: InputProps,\n ref: Ref<HTMLInputElement>,\n) {\n const {\n label,\n description,\n errorMessage,\n placeholder,\n leadingAdornment,\n trailingAdornment,\n className,\n ...textFieldProps\n } = props;\n\n return (\n <TextField {...textFieldProps} className={[s.field, fieldClasses.root, className].filter(Boolean).join(' ')}>\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <div className={s.frame}>\n {leadingAdornment !== undefined && <span className={s.adornment}>{leadingAdornment}</span>}\n <RACInput ref={ref} placeholder={placeholder} />\n {trailingAdornment !== undefined && <span className={s.adornment}>{trailingAdornment}</span>}\n </div>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </TextField>\n );\n});\n"],"mappings":";;;;;;;;GC+Ba,IAAQ,EAAW,SAC9B,GACA,GACA;CACA,IAAM,EACJ,UACA,gBACA,iBACA,gBACA,qBACA,sBACA,cACA,GAAG,MACD;AAEJ,QACE,kBAAC,GAAD;EAAW,GAAI;EAAgB,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA3G;GACG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,OAAD;IAAK,WAAW,EAAE;cAAlB;KACG,MAAqB,KAAA,KAAa,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAY;MAAwB,CAAA;KAC1F,kBAAC,GAAD;MAAe;MAAkB;MAAe,CAAA;KAC/C,MAAsB,KAAA,KAAa,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAY;MAAyB,CAAA;KACxF;;GACL,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GAC5D;;EAEd"}
1
+ {"version":3,"file":"Input-CoKOtyD0.js","names":[],"sources":["../../src/components/Input/Input.module.css","../../src/components/Input/Input.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: center;\n gap: 6px;\n height: var(--wzl-field-h, var(--wzl-control-h));\n padding: 0 var(--wzl-field-pad-x);\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 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;\n}\n\n.frame input::placeholder {\n color: var(--wzl-fg-subtle);\n}\n\n.adornment {\n display: inline-flex;\n align-items: center;\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size-sm);\n pointer-events: none;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n TextField,\n Label,\n Input as RACInput,\n Text,\n FieldError,\n type TextFieldProps as RACTextFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './Input.module.css';\n\n/** Props for {@link Input}, on top of React Aria's `TextField` props. */\nexport type InputProps = Omit<RACTextFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n placeholder?: string;\n leadingAdornment?: ReactNode;\n trailingAdornment?: ReactNode;\n className?: string;\n};\n\n/**\n * Single-line text input wrapping React Aria's TextField. Supplies a\n * default skin against the `--wzl-*` token system, and exposes the\n * familiar label / description / errorMessage / adornment slot shape.\n *\n * `ref` forwards to the underlying `<input>`.\n */\nexport const Input = forwardRef(function Input(\n props: InputProps,\n ref: Ref<HTMLInputElement>,\n) {\n const {\n label,\n description,\n errorMessage,\n placeholder,\n leadingAdornment,\n trailingAdornment,\n className,\n ...textFieldProps\n } = props;\n\n return (\n <TextField {...textFieldProps} className={[s.field, fieldClasses.root, className].filter(Boolean).join(' ')}>\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <div className={s.frame}>\n {leadingAdornment !== undefined && <span className={s.adornment}>{leadingAdornment}</span>}\n <RACInput ref={ref} placeholder={placeholder} />\n {trailingAdornment !== undefined && <span className={s.adornment}>{trailingAdornment}</span>}\n </div>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </TextField>\n );\n});\n"],"mappings":";;;;;;;;GC+Ba,IAAQ,EAAW,SAC9B,GACA,GACA;CACA,IAAM,EACJ,UACA,gBACA,iBACA,gBACA,qBACA,sBACA,cACA,GAAG,MACD;AAEJ,QACE,kBAAC,GAAD;EAAW,GAAI;EAAgB,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA3G;GACG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,OAAD;IAAK,WAAW,EAAE;cAAlB;KACG,MAAqB,KAAA,KAAa,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAY;MAAwB,CAAA;KAC1F,kBAAC,GAAD;MAAe;MAAkB;MAAe,CAAA;KAC/C,MAAsB,KAAA,KAAa,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAY;MAAyB,CAAA;KACxF;;GACL,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GAC5D;;EAEd"}
@@ -39,4 +39,4 @@ var r = {
39
39
  //#endregion
40
40
  export { i as t };
41
41
 
42
- //# sourceMappingURL=ItemList-BcdHAah3.js.map
42
+ //# sourceMappingURL=ItemList-BZJQReXD.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ItemList-BcdHAah3.js","names":[],"sources":["../../src/components/ItemList/ItemList.module.css","../../src/components/ItemList/ItemList.tsx"],"sourcesContent":["/* One height and one inset for every sidebar list. A panel stacking two of\n them has to have their rows line up, which is what a private copy of this\n file per list stopped happening. */\n.list {\n display: flex;\n flex-direction: column;\n gap: 1px;\n user-select: none;\n -webkit-user-select: none;\n position: relative;\n width: 100%;\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0 8px;\n height: 24px;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg);\n background: transparent;\n border-radius: var(--wzl-radius-sm);\n cursor: default;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.row:hover {\n background: var(--wzl-surface);\n}\n\n.label {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.selected,\n.selected:hover {\n background: var(--wzl-border);\n color: var(--wzl-fg);\n}\n\n/* Present but not in effect. */\n.muted {\n color: var(--wzl-fg-subtle);\n}\n\n.empty {\n padding: 12px 8px;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n font-style: italic;\n}\n","/**\n * The row chrome behind a sidebar list — layers, history, anything that is a\n * flat column of selectable rows in a panel.\n *\n * It owns the container, the row box and the empty state, and nothing else:\n * what a row *means* stays with the consumer. History dims its redo entries\n * and rules a line under the current one; a layer list drags to reorder and\n * leads each row with a swatch. Both reach that through `className` and\n * `rowProps` rather than through options here, so this does not grow a flag\n * per consumer.\n *\n * Rows are one height and one inset for every list, which is the point: two\n * panels stacked in the same sidebar have to agree, and they did not while\n * each owned a private copy of the same CSS.\n */\nimport { forwardRef, type HTMLAttributes, type ReactNode, type Ref } from 'react';\nimport s from './ItemList.module.css';\n\n/** One row. `id` is the React key. */\nexport interface ItemListRow {\n id: string;\n label: ReactNode;\n /** Fixed-size content before the label — a color swatch, a checkbox. */\n leading?: ReactNode;\n /** The row the list considers current. */\n selected?: boolean;\n /** Present but not in effect — history's redo entries. */\n muted?: boolean;\n /** Consumer classes for states this component has no opinion about. */\n className?: string;\n /** Handlers and data attributes for this row's element. `data-*` is\n * spelled out because `HTMLAttributes` does not admit it, and every\n * consumer here marks its rows up with one. */\n rowProps?: HTMLAttributes<HTMLDivElement> & Record<`data-${string}`, unknown>;\n}\n\nexport interface ItemListProps {\n rows: readonly ItemListRow[];\n /** Shown in place of the rows when there are none. */\n empty?: ReactNode;\n className?: string;\n /** Rendered inside the container above the rows — a drop indicator. */\n overlay?: ReactNode;\n /** Spread onto the container: pointer handlers for a drag, `aria-label`. */\n containerProps?: HTMLAttributes<HTMLDivElement>;\n}\n\nexport const ItemList = forwardRef(function ItemList(\n { rows, empty, className, overlay, containerProps }: ItemListProps,\n ref: Ref<HTMLDivElement>,\n) {\n const cls = [s.list, className].filter(Boolean).join(' ');\n if (rows.length === 0) {\n return (\n <div className={cls} ref={ref} {...containerProps}>\n <div className={s.empty}>{empty ?? '—'}</div>\n </div>\n );\n }\n return (\n <div className={cls} ref={ref} {...containerProps}>\n {overlay}\n {rows.map((row) => (\n <div\n key={row.id}\n className={[\n s.row,\n row.selected && s.selected,\n row.muted && s.muted,\n row.className,\n ].filter(Boolean).join(' ')}\n {...row.rowProps}\n >\n {row.leading}\n <span className={s.label}>{row.label}</span>\n </div>\n ))}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;GC+Ca,IAAW,EAAW,SACjC,EAAE,SAAM,UAAO,cAAW,YAAS,qBACnC,GACA;CACA,IAAM,IAAM,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAQzD,QAPI,EAAK,WAAW,IAEhB,kBAAC,OAAD;EAAK,WAAW;EAAU;EAAK,GAAI;YACjC,kBAAC,OAAD;GAAK,WAAW,EAAE;aAAQ,KAAS;GAAU,CAAA;EACzC,CAAA,GAIR,kBAAC,OAAD;EAAK,WAAW;EAAU;EAAK,GAAI;YAAnC,CACG,GACA,EAAK,KAAK,MACT,kBAAC,OAAD;GAEE,WAAW;IACT,EAAE;IACF,EAAI,YAAY,EAAE;IAClB,EAAI,SAAS,EAAE;IACf,EAAI;IACL,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;GAC3B,GAAI,EAAI;aARV,CAUG,EAAI,SACL,kBAAC,QAAD;IAAM,WAAW,EAAE;cAAQ,EAAI;IAAa,CAAA,CACxC;KAXC,EAAI,GAWL,CACN,CACE;;EAER"}
1
+ {"version":3,"file":"ItemList-BZJQReXD.js","names":[],"sources":["../../src/components/ItemList/ItemList.module.css","../../src/components/ItemList/ItemList.tsx"],"sourcesContent":["/* One height and one inset for every sidebar list. A panel stacking two of\n them has to have their rows line up, which is what a private copy of this\n file per list stopped happening. */\n.list {\n display: flex;\n flex-direction: column;\n gap: 1px;\n user-select: none;\n -webkit-user-select: none;\n position: relative;\n width: 100%;\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0 8px;\n height: 24px;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg);\n background: transparent;\n border-radius: var(--wzl-radius-sm);\n cursor: default;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.row:hover {\n background: var(--wzl-surface);\n}\n\n.label {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.selected,\n.selected:hover {\n background: var(--wzl-border);\n color: var(--wzl-fg);\n}\n\n/* Present but not in effect. */\n.muted {\n color: var(--wzl-fg-subtle);\n}\n\n.empty {\n padding: 12px 8px;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n font-style: italic;\n}\n","/**\n * The row chrome behind a sidebar list — layers, history, anything that is a\n * flat column of selectable rows in a panel.\n *\n * It owns the container, the row box and the empty state, and nothing else:\n * what a row *means* stays with the consumer. History dims its redo entries\n * and rules a line under the current one; a layer list drags to reorder and\n * leads each row with a swatch. Both reach that through `className` and\n * `rowProps` rather than through options here, so this does not grow a flag\n * per consumer.\n *\n * Rows are one height and one inset for every list, which is the point: two\n * panels stacked in the same sidebar have to agree, and they did not while\n * each owned a private copy of the same CSS.\n */\nimport { forwardRef, type HTMLAttributes, type ReactNode, type Ref } from 'react';\nimport s from './ItemList.module.css';\n\n/** One row. `id` is the React key. */\nexport interface ItemListRow {\n id: string;\n label: ReactNode;\n /** Fixed-size content before the label — a color swatch, a checkbox. */\n leading?: ReactNode;\n /** The row the list considers current. */\n selected?: boolean;\n /** Present but not in effect — history's redo entries. */\n muted?: boolean;\n /** Consumer classes for states this component has no opinion about. */\n className?: string;\n /** Handlers and data attributes for this row's element. `data-*` is\n * spelled out because `HTMLAttributes` does not admit it, and every\n * consumer here marks its rows up with one. */\n rowProps?: HTMLAttributes<HTMLDivElement> & Record<`data-${string}`, unknown>;\n}\n\nexport interface ItemListProps {\n rows: readonly ItemListRow[];\n /** Shown in place of the rows when there are none. */\n empty?: ReactNode;\n className?: string;\n /** Rendered inside the container above the rows — a drop indicator. */\n overlay?: ReactNode;\n /** Spread onto the container: pointer handlers for a drag, `aria-label`. */\n containerProps?: HTMLAttributes<HTMLDivElement>;\n}\n\nexport const ItemList = forwardRef(function ItemList(\n { rows, empty, className, overlay, containerProps }: ItemListProps,\n ref: Ref<HTMLDivElement>,\n) {\n const cls = [s.list, className].filter(Boolean).join(' ');\n if (rows.length === 0) {\n return (\n <div className={cls} ref={ref} {...containerProps}>\n <div className={s.empty}>{empty ?? '—'}</div>\n </div>\n );\n }\n return (\n <div className={cls} ref={ref} {...containerProps}>\n {overlay}\n {rows.map((row) => (\n <div\n key={row.id}\n className={[\n s.row,\n row.selected && s.selected,\n row.muted && s.muted,\n row.className,\n ].filter(Boolean).join(' ')}\n {...row.rowProps}\n >\n {row.leading}\n <span className={s.label}>{row.label}</span>\n </div>\n ))}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;GC+Ca,IAAW,EAAW,SACjC,EAAE,SAAM,UAAO,cAAW,YAAS,qBACnC,GACA;CACA,IAAM,IAAM,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAQzD,QAPI,EAAK,WAAW,IAEhB,kBAAC,OAAD;EAAK,WAAW;EAAU;EAAK,GAAI;YACjC,kBAAC,OAAD;GAAK,WAAW,EAAE;aAAQ,KAAS;GAAU,CAAA;EACzC,CAAA,GAIR,kBAAC,OAAD;EAAK,WAAW;EAAU;EAAK,GAAI;YAAnC,CACG,GACA,EAAK,KAAK,MACT,kBAAC,OAAD;GAEE,WAAW;IACT,EAAE;IACF,EAAI,YAAY,EAAE;IAClB,EAAI,SAAS,EAAE;IACf,EAAI;IACL,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;GAC3B,GAAI,EAAI;aARV,CAUG,EAAI,SACL,kBAAC,QAAD;IAAM,WAAW,EAAE;cAAQ,EAAI;IAAa,CAAA,CACxC;KAXC,EAAI,GAWL,CACN,CACE;;EAER"}
@@ -394,4 +394,4 @@ function p(e, t = {}) {
394
394
  //#endregion
395
395
  export { s as a, c as i, p as n, o, d as r, u as t };
396
396
 
397
- //# sourceMappingURL=Keycaps-D-UGWDrt.js.map
397
+ //# sourceMappingURL=Keycaps-DSeq6QWH.js.map