@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 +0,0 @@
1
- {"version":3,"file":"Prefs-BqnwyjmH.js","names":[],"sources":["../../src/components/paintValue.ts","../../src/components/Prefs/schema.ts","../../src/components/Prefs/Prefs.module.css","../../src/components/Prefs/PrefsForm.tsx","../../src/components/Prefs/PrefsDialog.tsx"],"sourcesContent":["// Reading and writing the paint objects a property control may be pointed at.\n//\n// `data.fill` is a `FillStyle` and `data.stroke` a `Stroke`, so a control that\n// assumes either is a hex string reads `undefined` off it and writes a bare\n// string back over it. These helpers are what keep a control honest about the\n// shape it is holding.\n\n/** The color of a solid paint, or `undefined` for anything else — including a\n * value that isn't a paint at all. `fill` is optional on the solid member of\n * the union, so the tag alone can't decide it. */\nexport function solidColorOf(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const paint = value as { fill?: string; color?: unknown };\n if (paint.fill !== undefined && paint.fill !== 'solid') return undefined;\n return typeof paint.color === 'string' ? paint.color : undefined;\n}\n\n/** The single color a stroke shows, or `undefined` when it has none to show —\n * a gradient or pattern stroke, or no stroke at all. */\nexport function strokeColorOf(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null || !('paint' in value)) return undefined;\n return solidColorOf((value as { paint: unknown }).paint);\n}\n","// Preference-schema vocabulary for PrefsForm / PrefsDialog / SelectionPanel:\n// core's `ToolPref*` family under this package's names. Never re-declare them\n// here — this was a hand-kept structural copy until it drifted, and the\n// independence from `@weasel-js/core` it bought is long gone anyway.\n\nexport type {\n ToolPref as BuiltinPref,\n ToolPrefBoolean as PrefBoolean,\n ToolPrefBooleanControl as PrefBooleanControl,\n ToolPrefColor as PrefColor,\n ToolPrefCustom as PrefCustom,\n ToolPrefEnum as PrefEnum,\n ToolPrefEnumControl as PrefEnumControl,\n ToolPrefEnumEncoding as PrefEnumEncoding,\n ToolPrefGroup as PrefGroup,\n ToolPrefKind as PrefKind,\n ToolPrefLeaf as PrefLeaf,\n ToolPrefNumber as PrefNumber,\n ToolPrefNumberControl as PrefNumberControl,\n ToolPrefNumberUnit as PrefNumberUnit,\n ToolPrefObject as PrefObject,\n ToolPrefPaint as PrefPaint,\n ToolPrefString as PrefString,\n ToolPrefStringControl as PrefStringControl,\n} from '@weasel-js/core';\n\nimport type { ToolPrefGroup, ToolPrefLeaf } from '@weasel-js/core';\n\n/** Distinguishes a leaf from a group while walking a schema tree. */\nexport function isPrefLeaf(node: ToolPrefLeaf | ToolPrefGroup): node is ToolPrefLeaf {\n return 'kind' in node;\n}\n\n/** Get the value at a dotted path inside a nested value tree. Returns\n * `undefined` when a segment is missing or hits a non-object. */\nexport function prefValueAtPath(values: unknown, path: string): unknown {\n let cur: unknown = values;\n for (const seg of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[seg];\n }\n return cur;\n}\n\n/**\n * Recursively drop `hidden` leaves (unless `showHidden`), pruning groups\n * that end up empty. Returns null when the entire subtree is hidden.\n */\nexport function visiblePrefSubtree<T extends ToolPrefLeaf | ToolPrefGroup>(\n node: T,\n showHidden: boolean,\n): T | null {\n if (isPrefLeaf(node)) return node.hidden && !showHidden ? null : node;\n const group = node as ToolPrefGroup;\n const children: Record<string, ToolPrefLeaf | ToolPrefGroup> = {};\n for (const [key, child] of Object.entries(group.children)) {\n const kept = visiblePrefSubtree(child, showHidden);\n if (kept) children[key] = kept;\n }\n if (Object.keys(children).length === 0) return null;\n return { ...node, children };\n}\n","/* Ported from WeaselDraw's wd-prefs-* rules (apps/draw/src/app.css),\n * generalized onto theme tokens. Column width is overridable via\n * --wzl-prefs-column-width at any ancestor scope. */\n\n.columns {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: 12px;\n overflow-y: auto;\n padding-right: 4px;\n}\n\n.column {\n flex: 0 0 var(--wzl-prefs-column-width, 300px);\n width: var(--wzl-prefs-column-width, 300px);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.panel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n}\n\n.subpanel {\n display: flex;\n flex-direction: column;\n gap: 6px;\n margin-top: 6px;\n padding: 8px 8px 8px 10px;\n background: var(--wzl-surface-sunken);\n border-left: 2px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n}\n\n.groupHeader {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding-bottom: 4px;\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.subpanel .groupHeader {\n border-bottom: none;\n padding-bottom: 2px;\n}\n\n.groupTitle {\n margin: 0;\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-bold);\n color: var(--wzl-fg);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n\n.subpanel .groupTitle {\n font-size: var(--wzl-font-size-sm);\n text-transform: none;\n letter-spacing: 0;\n}\n\n.groupDesc {\n margin: 0;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n line-height: 1.35;\n}\n\n.rows {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg);\n cursor: default;\n}\n\n.rowLabel {\n flex: 1 1 auto;\n min-width: 0;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n/* Description affordance — quiet until hovered/focused. */\n.help {\n flex: 0 0 auto;\n padding: 0;\n border: none;\n background: none;\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size-sm);\n line-height: 1;\n cursor: help;\n border-radius: var(--wzl-radius-sm);\n}\n\n.help:hover {\n color: var(--wzl-fg);\n}\n\n.help:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 1px;\n color: var(--wzl-fg);\n}\n\n/* Control slot — fixed width so the label can take the rest of the row.\n * Kit form controls render as block-level flex columns that fill it. */\n.rowControl {\n flex: 0 0 110px;\n min-width: 0;\n display: flex;\n align-items: center;\n}\n\n.textarea {\n width: 100%;\n padding: 4px 8px;\n background: var(--wzl-surface-sunken);\n color: var(--wzl-fg);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n font: inherit;\n font-size: var(--wzl-font-size-sm);\n resize: vertical;\n}\n\n.unrenderable {\n font-family: var(--wzl-font-mono);\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n}\n\n/* PrefsDialog header row: title text + optional headerExtra chrome. */\n.titleRow {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n min-width: 0;\n}\n\n.titleRow > span:first-child {\n flex: 1;\n min-width: 0;\n}\n\n/* An object leaf renders its fields as its own rows. */\n.objectLeaf {\n display: flex;\n flex-direction: column;\n gap: 6px;\n inline-size: 100%;\n}\n\n.objectRow {\n display: grid;\n grid-template-columns: 96px 1fr;\n gap: 8px;\n align-items: center;\n}\n\n.objectLabel {\n font-size: var(--wzl-font-size-sm);\n opacity: 0.75;\n}\n\n.objectGroup {\n margin: 4px 0 0;\n font-size: var(--wzl-font-size-xs);\n font-weight: var(--wzl-font-weight-bold);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.55;\n}\n","import { useMemo, type ReactNode } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { Checkbox } from '../Checkbox';\nimport { ColorField } from '../ColorField';\nimport { solidColorOf } from '../paintValue';\nimport { Input } from '../Input';\nimport { NumberField } from '../NumberField';\nimport { RadioGroup, Radio } from '../RadioGroup';\nimport { RangeSlider } from '../RangeSlider';\nimport { Select } from '../Select';\nimport { Switch } from '../Switch';\nimport { Tooltip, TooltipTrigger } from '../Tooltip';\nimport { isBuiltinToolPref } from '@weasel-js/core';\nimport {\n isPrefLeaf,\n prefValueAtPath,\n visiblePrefSubtree,\n type PrefGroup,\n type PrefLeaf,\n} from './schema';\nimport s from './Prefs.module.css';\n\n/** What a {@link PrefRenderer} is given for the leaf it is rendering. */\nexport interface PrefRenderContext {\n /** Dotted path of the leaf within the schema root. */\n path: string;\n /** The schema node. App renderers narrow this to their own kind shape. */\n pref: PrefLeaf;\n /** Current value — `values` at `path`, falling back to `pref.default`. */\n value: unknown;\n setValue: (value: unknown) => void;\n}\n\n/**\n * Renders the control cell for one preference leaf. Returning `null`\n * collapses the row.\n */\nexport type PrefRenderer = (ctx: PrefRenderContext) => ReactNode;\n\n/** Props for {@link PrefsForm}. */\nexport interface PrefsFormProps {\n /** Root of the schema tree. Core `ToolPrefGroup`s assign structurally. */\n schema: PrefGroup;\n /** Nested value tree (shape mirrors the schema). Sparse is fine —\n * missing leaves fall back to their schema `default`. */\n values?: unknown;\n /** Change callback with the leaf's dotted path. Values apply live;\n * there is no dirty/commit state. */\n onChange: (path: string, value: unknown) => void;\n /**\n * Per-kind renderers for app-defined kinds. Entries also override the\n * built-in kinds when keys collide. A renderer owns the control cell\n * (the label/tooltip row chrome stays with the form, unless the leaf\n * sets `block`); returning `null` collapses the row entirely. Unknown\n * kinds with no renderer show a labeled placeholder instead of\n * crashing.\n */\n renderers?: Record<string, PrefRenderer>;\n /** Reveal `hidden` leaves (dev tooling). Default false. */\n showHidden?: boolean;\n className?: string;\n}\n\n/**\n * Schema-driven preferences form. Top-level groups render as columns,\n * nested groups as indented sub-panels, leaves as label + control rows.\n * Storage-agnostic: pair with `PrefsDialog` for the modal composition,\n * and persist however the app likes via `onChange`.\n */\nexport function PrefsForm(props: PrefsFormProps) {\n const { schema, values, onChange, renderers, showHidden = false, className } = props;\n const visibleRoot = useMemo(\n () => visiblePrefSubtree(schema, showHidden),\n [schema, showHidden],\n );\n const ctx: WalkCtx = { values, onChange, renderers };\n return (\n <div className={[s.columns, className].filter(Boolean).join(' ')}>\n {Object.entries(visibleRoot?.children ?? {}).map(([key, child]) => (\n <div key={key} className={s.column}>\n {isPrefLeaf(child) ? (\n // Top-level leaves are unusual but legal — give each its own\n // column for symmetry with grouped siblings.\n <PrefRow ctx={ctx} path={key} pref={child} />\n ) : (\n <GroupBody ctx={ctx} group={child} path={key} depth={0} />\n )}\n </div>\n ))}\n </div>\n );\n}\n\ninterface WalkCtx {\n values: unknown;\n onChange: (path: string, value: unknown) => void;\n renderers?: Record<string, PrefRenderer>;\n}\n\nfunction GroupBody({ ctx, group, path, depth }: {\n ctx: WalkCtx;\n group: PrefGroup;\n path: string;\n depth: number;\n}) {\n return (\n <div className={depth === 0 ? s.panel : s.subpanel}>\n <div className={s.groupHeader}>\n <h3 className={s.groupTitle}>{group.name}</h3>\n {group.description !== undefined && (\n <p className={s.groupDesc}>{group.description}</p>\n )}\n </div>\n <div className={s.rows}>\n {Object.entries(group.children).map(([key, child]) => {\n const childPath = `${path}.${key}`;\n return isPrefLeaf(child) ? (\n <PrefRow key={key} ctx={ctx} path={childPath} pref={child} />\n ) : (\n <GroupBody key={key} ctx={ctx} group={child} path={childPath} depth={depth + 1} />\n );\n })}\n </div>\n </div>\n );\n}\n\nfunction PrefRow({ ctx, path, pref }: { ctx: WalkCtx; path: string; pref: PrefLeaf }) {\n const stored = prefValueAtPath(ctx.values, path);\n const renderCtx: PrefRenderContext = {\n path,\n pref,\n value: stored !== undefined ? stored : pref.default,\n setValue: (v) => ctx.onChange(path, v),\n };\n\n const custom = ctx.renderers?.[pref.kind];\n const control = custom ? custom(renderCtx) : renderBuiltin(renderCtx);\n if (custom && control === null) return null;\n\n // `block` leaves own their chrome (embedded editors with their own\n // header) — no label/tooltip row.\n if (pref.block) return <>{control}</>;\n\n return (\n <label className={s.row}>\n <span className={s.rowLabel}>\n {pref.name}\n {pref.description ? (\n // Help affordance carries the description tooltip. A tooltip\n // trigger must be interactive (keyboard-reachable), so this is\n // a real button — a bare label span would be neither focusable\n // nor announced.\n <TooltipTrigger>\n <Focusable>\n <button type=\"button\" className={s.help} aria-label={`About ${pref.name}`}>\n ⓘ\n </button>\n </Focusable>\n <Tooltip>{pref.description}</Tooltip>\n </TooltipTrigger>\n ) : null}\n </span>\n <span className={s.rowControl}>{control}</span>\n </label>\n );\n}\n\nfunction renderBuiltin(\n ctx: PrefRenderContext,\n // The object a nested leaf is a field of — what an enum `encoding` reads\n // and writes against. Undefined for a top-level leaf, which has none.\n siblings?: Record<string, unknown>,\n): ReactNode {\n const { pref, value, setValue } = ctx;\n if (!isBuiltinToolPref(pref)) {\n // App-defined kind with no `renderers` entry: labeled placeholder, not a\n // crash — a missing wiring should be visible and recoverable.\n return <span className={s.unrenderable}>({pref.kind}: no renderer)</span>;\n }\n switch (pref.kind) {\n case 'boolean': {\n const checked = Boolean(value);\n return pref.control === 'switch' ? (\n <Switch isSelected={checked} onChange={setValue} aria-label={pref.name} />\n ) : (\n <Checkbox isSelected={checked} onChange={setValue} aria-label={pref.name} />\n );\n }\n case 'number': {\n const n = typeof value === 'number' && Number.isFinite(value) ? value : 0;\n if (pref.control === 'slider') {\n return (\n <RangeSlider\n value={n}\n onChange={(v) => setValue(typeof v === 'number' ? v : v[0])}\n minValue={pref.min}\n maxValue={pref.max}\n step={pref.step ?? 1}\n aria-label={pref.name}\n />\n );\n }\n return (\n <NumberField\n value={n}\n onChange={setValue}\n minValue={pref.min}\n maxValue={pref.max}\n step={pref.step ?? 1}\n aria-label={pref.name}\n />\n );\n }\n case 'string': {\n const text = typeof value === 'string' ? value : '';\n if (pref.control === 'textarea') {\n return (\n <textarea\n className={s.textarea}\n value={text}\n onChange={(e) => setValue(e.target.value)}\n aria-label={pref.name}\n rows={3}\n />\n );\n }\n return <Input value={text} onChange={setValue} aria-label={pref.name} />;\n }\n case 'enum': {\n const encoding = pref.encoding;\n // An encoded leaf stores something other than the option string (a dash\n // array), so reading the raw value selects nothing and writing one\n // replaces the stored form with the option string.\n const option = encoding\n ? encoding.read(value, siblings)\n : typeof value === 'string'\n ? value\n : pref.default;\n const choose = (next: string): void =>\n setValue(encoding ? encoding.write(next, siblings) : next);\n if (pref.control === 'radio') {\n return (\n <RadioGroup value={option ?? null} onChange={choose} aria-label={pref.name}>\n {pref.options.map((o) => (\n <Radio key={o.value} value={o.value} isDisabled={o.disabled}>\n {o.label}\n </Radio>\n ))}\n </RadioGroup>\n );\n }\n return (\n <Select<string>\n options={pref.options.map((o) => ({\n value: o.value,\n label: o.label,\n isDisabled: o.disabled,\n }))}\n selectedKey={option ?? null}\n onSelectionChange={choose}\n aria-label={pref.name}\n />\n );\n }\n case 'color': {\n const hex = typeof value === 'string' ? value : pref.default;\n return (\n <ColorField\n value={hex}\n alpha={pref.alpha}\n onChange={setValue}\n aria-label={pref.name}\n />\n );\n }\n case 'paint': {\n // A whole `FillStyle`. A gradient or pattern has no single color to\n // show, so the field goes blank rather than claiming one, and an edit\n // writes a whole solid union member rather than grafting a `color` key\n // onto the gradient.\n const solid = solidColorOf(value) ?? solidColorOf(pref.default);\n return (\n <ColorField\n value={solid}\n alpha={pref.alpha}\n onChange={(color) => setValue({ fill: 'solid', color })}\n aria-label={pref.name}\n />\n );\n }\n case 'object': {\n // One value with its fields hanging off it: each child renders its own\n // control and commits the parent object whole.\n const p = pref;\n const held = typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n const objectRows = (children: Record<string, PrefLeaf | PrefGroup>): ReactNode[] => {\n const out: ReactNode[] = [];\n for (const [key, child] of Object.entries(children)) {\n if (!isPrefLeaf(child)) {\n const inner = objectRows(child.children);\n if (inner.length === 0) continue;\n out.push(<h4 key={`group:${key}`} className={s.objectGroup}>{child.name}</h4>, ...inner);\n continue;\n }\n out.push(\n <label key={key} className={s.objectRow}>\n <span className={s.objectLabel}>{child.name}</span>\n {renderBuiltin({\n path: `${ctx.path}.${key}`,\n pref: child,\n value: held?.[key],\n setValue: (v) => {\n const base = held ?? p.fromScalar?.(value) ?? {};\n setValue({ ...base, [key]: v });\n },\n }, held)}\n </label>,\n );\n }\n return out;\n };\n return <div className={s.objectLeaf}>{objectRows(p.children)}</div>;\n }\n default: {\n // Not reachable while every built-in kind has an arm — and a new kind\n // that lacks one is a compile error here, never a blank row.\n const _exhaustive: never = pref;\n throw new Error(\n `PrefsForm: no control for built-in pref kind \"${(_exhaustive as { kind: string }).kind}\"`,\n );\n }\n }\n}\n","import type { ReactNode } from 'react';\nimport { Dialog } from '../Dialog';\nimport { PrefsForm, type PrefsFormProps } from './PrefsForm';\nimport s from './Prefs.module.css';\n\n/**\n * Props for {@link PrefsDialog} — everything {@link PrefsForm} takes, plus\n * the dialog's own open state and chrome.\n */\nexport interface PrefsDialogProps extends PrefsFormProps {\n isOpen: boolean;\n onOpenChange: (open: boolean) => void;\n /** Dialog heading. Defaults to the schema root's `name`. */\n title?: ReactNode;\n /** Extra chrome rendered inline with the title (e.g. a dev-mode\n * \"Show hidden\" switch). */\n headerExtra?: ReactNode;\n /** Class applied to the dialog's modal box. */\n dialogClassName?: string;\n}\n\n/**\n * The kit's preferences dialog: `Dialog` + `PrefsForm`. Changes apply\n * live through `onChange` — there is no OK/Cancel staging. For custom\n * placement (sidebar, popover, inline page), compose `PrefsForm`\n * directly; this wrapper is intentionally thin.\n */\nexport function PrefsDialog(props: PrefsDialogProps) {\n const { isOpen, onOpenChange, title, headerExtra, dialogClassName, ...form } = props;\n return (\n <Dialog\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n aria-label={typeof title === 'string' ? title : form.schema.name}\n className={dialogClassName}\n title={\n headerExtra !== undefined ? (\n <span className={s.titleRow}>\n <span>{title ?? form.schema.name}</span>\n {headerExtra}\n </span>\n ) : (\n title ?? form.schema.name\n )\n }\n >\n <PrefsForm {...form} />\n </Dialog>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"Properties-fLSEE65J.js","names":[],"sources":["../../src/components/Properties/CurveField.module.css","../../src/components/Properties/CurveField.tsx","../../src/components/Properties/Properties.module.css","../../src/components/Properties/PropertyPanel.tsx","../../src/components/Properties/EffectCard.tsx","../../src/components/Properties/PropertyGroup.tsx"],"sourcesContent":[".field {\n display: grid;\n gap: 6px;\n grid-column: 1 / -1;\n box-sizing: border-box;\n}\n\n.field * {\n box-sizing: border-box;\n}\n\n.plot {\n position: relative;\n width: 100%;\n}\n\n.marks {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.actions {\n display: flex;\n gap: 6px;\n justify-self: start;\n}\n\n.action {\n background: transparent;\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n padding: 4px 10px;\n font-size: var(--wzl-font-size-sm);\n cursor: pointer;\n}\n\n.action:hover {\n background: var(--wzl-surface-raised);\n}\n","import { useCallback, useMemo } from 'react';\nimport { dlog } from '../../dlog';\nimport { type ControlPoint, CurveEditor } from '../CurveEditor';\nimport s from './CurveField.module.css';\n\n/** An annotation drawn over a curve plot: a shaded band across a range of x,\n * or a vertical line at one x. */\nexport type CurveMark =\n | { kind: 'band'; x: [number, number]; color?: string }\n | { kind: 'line'; x: number; color?: string };\n\n/** Props for `<CurveField>`. */\nexport interface CurveFieldProps {\n /** Flat [x0, y0, x1, y1, …] — matches how curve-as-array configs\n * serialize in JSON snapshots. */\n values: number[];\n min: number;\n max: number;\n step: number;\n /** Plot width in CSS px. Caller sizes (typically uses a ResizeObserver). */\n width: number;\n /** Plot height in CSS px. Default 110. */\n height?: number;\n /** Flat default curve. When provided, a Reset button restores it. */\n defaults?: readonly number[];\n onChange: (next: number[]) => void;\n /** Optional vertical marks overlaid on the plot (e.g. landmarks). */\n marks?: readonly CurveMark[];\n}\n\n/**\n * A function-domain (y = f(x), x ∈ [0,1]) curve editor with per-stop\n * numeric readouts and flip buttons. Wraps weasel-ui's\n * `CurveEditor`; consumers wanting the raw editor (2D paths,\n * custom anchors) should import that instead.\n */\nexport function CurveField({\n values,\n min,\n max,\n step,\n width,\n height = 110,\n defaults,\n onChange,\n marks,\n}: CurveFieldProps) {\n const points: ControlPoint[] = useMemo(() => {\n const out: ControlPoint[] = [];\n for (let i = 0; i + 1 < values.length; i += 2) out.push({ x: values[i], y: values[i + 1] });\n return out;\n }, [values]);\n\n const handleChange = useCallback(\n (next: ControlPoint[]) => {\n const flat: number[] = new Array(next.length * 2);\n for (let i = 0; i < next.length; i++) {\n const ySnap = Math.round(next[i].y / step) * step;\n const y = Math.max(min, Math.min(max, ySnap));\n flat[i * 2] = next[i].x;\n flat[i * 2 + 1] = y;\n }\n dlog('curve-field', 'handleChange', {\n nLen: next.length,\n flatLen: flat.length,\n sample: flat.slice(0, 4),\n });\n onChange(flat);\n },\n [onChange, min, max, step],\n );\n\n const handleFlip = useCallback(() => {\n const flipped: Array<{ x: number; y: number }> = [];\n for (let i = 0; i + 1 < values.length; i += 2) {\n flipped.push({ x: 1 - values[i], y: values[i + 1] });\n }\n flipped.sort((a, b) => a.x - b.x);\n const out: number[] = [];\n for (const p of flipped) out.push(p.x, p.y);\n onChange(out);\n }, [values, onChange]);\n\n const handleFlipVertical = useCallback(() => {\n const mid = (min + max) / 2;\n const flipped: number[] = new Array(values.length);\n for (let i = 0; i + 1 < values.length; i += 2) {\n flipped[i] = values[i];\n flipped[i + 1] = 2 * mid - values[i + 1];\n }\n onChange(flipped);\n }, [values, min, max, onChange]);\n\n return (\n <div className={s.field}>\n <div className={s.plot}>\n <CurveEditor\n value={points}\n onInput={handleChange}\n domain=\"1d\"\n constrain=\"function\"\n xRange={[0, 1]}\n yRange={[min, max]}\n width={width}\n height={height}\n endpoints=\"pinned-x\"\n addPointMode=\"click-curve\"\n minPoints={2}\n grid={{}}\n history={false}\n />\n {marks && marks.length > 0 && (\n <svg\n className={s.marks}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n aria-hidden=\"true\"\n >\n {marks.map((m) => {\n const color = m.color ?? '#ffcc00';\n if (m.kind === 'band') {\n const x0 = Math.max(0, Math.min(1, m.x[0])) * width;\n const x1 = Math.max(0, Math.min(1, m.x[1])) * width;\n const left = Math.min(x0, x1);\n const w = Math.abs(x1 - x0);\n return (\n <g key={`band-${m.x[0]}-${m.x[1]}-${color}`}>\n <rect x={left} y={0} width={w} height={height} fill={color} fillOpacity={0.4} />\n <line\n x1={left}\n y1={0}\n x2={left}\n y2={height}\n stroke={color}\n strokeOpacity={0.85}\n strokeWidth={1}\n />\n <line\n x1={left + w}\n y1={0}\n x2={left + w}\n y2={height}\n stroke={color}\n strokeOpacity={0.85}\n strokeWidth={1}\n />\n </g>\n );\n }\n const x = Math.max(0, Math.min(1, m.x)) * width;\n return (\n <line\n key={`mark-${m.x}-${color}`}\n x1={x}\n y1={0}\n x2={x}\n y2={height}\n stroke={color}\n strokeWidth={1}\n />\n );\n })}\n </svg>\n )}\n </div>\n <div className={s.actions}>\n <button type=\"button\" className={s.action} onClick={handleFlip}>\n Flip horizontally\n </button>\n <button type=\"button\" className={s.action} onClick={handleFlipVertical}>\n Flip vertically\n </button>\n {defaults && (\n <button\n type=\"button\"\n className={s.action}\n onClick={() => onChange([...defaults])}\n >\n Reset\n </button>\n )}\n </div>\n </div>\n );\n}\n","/* One stylesheet for the whole property-panel family. PropertyList, PropertyRow,\n PropertyGroup and Subpanel/EffectCard are styled together because the grid\n rules genuinely cross components — `.listPairs > .subpanel` and\n `.groupBody > :not(.rowColor)` name two components in one selector, and a CSS\n Module can only compose local names within its own file. */\n\n/* Authored against a border-box reset (labkit's `.lk-root *`). weasel-ui has no\n global reset, so the family restates it over its own subtrees. */\n.panel,\n.list,\n.row,\n.group,\n.subpanel,\n.card,\n.cardList,\n.panel *,\n.list *,\n.row *,\n.group *,\n.subpanel *,\n.card *,\n.cardList * {\n box-sizing: border-box;\n}\n\n/* ── Container metrics ──────────────────────────────────────────────\n Every spacing number the family lays out with is read from a `--wzl-prop-*`\n custom property with its own value as the fallback, so a consumer can move\n any of them from an ancestor without selector surgery. The `density` classes\n below set them in preset bundles; nothing in this file declares one, so an\n inherited value always reaches the use site. */\n\n.panel {\n background: var(--wzl-surface-raised);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-lg);\n padding: var(--wzl-prop-panel-pad, 14px 16px);\n backdrop-filter: blur(var(--wzl-glass-blur));\n -webkit-backdrop-filter: blur(var(--wzl-glass-blur));\n}\n\n.panelTitle {\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-lg);\n line-height: var(--wzl-leading-snug);\n letter-spacing: 0.01em;\n color: var(--wzl-fg);\n margin: 0 0 var(--wzl-prop-panel-title-gap, 12px);\n}\n\n.list {\n display: grid;\n grid-template-columns: 1fr 1fr;\n column-gap: var(--wzl-prop-column-gap, 10px);\n row-gap: var(--wzl-prop-row-gap, 4px);\n align-items: stretch;\n /* A property panel is dense: its fields are shorter and tighter than a\n standalone Input. Mapped here, on the container, so one declaration moves\n every field in the panel — and read from the container-level name so a\n density set further up is not shadowed by this declaration. */\n --wzl-field-h: var(--wzl-prop-field-h, 20px);\n --wzl-field-pad-x: var(--wzl-prop-field-pad-x, 6px);\n}\n\n/* Default packing: only color rows pair, everything else spans full width.\n Scoped to NOT apply in pairs mode (otherwise this 0,2,0 selector beats the\n pairs override below, which is only 0,1,0). */\n.list:not(.listPairs) > :not(.rowColor) {\n grid-column: 1 / -1;\n}\n\n/* `pack=\"pairs\"` flips that: every row auto-places into the 2-col grid two\n per row, and explicit full-width children opt in via the span helper class. */\n.listPairs > * {\n min-width: 0;\n}\n\n.listPairs > .span,\n.listPairs > .subpanel,\n.listPairs > h3 {\n grid-column: 1 / -1;\n}\n\n/* `pack=\"one-up\"` gives every row the full width, color rows included — the\n one packing a palette can be built from. */\n.listOneUp > * {\n grid-column: 1 / -1;\n}\n\n/* ── density ────────────────────────────────────────────────────────\n Preset bundles for the container metrics above. Set on whichever container\n the consumer puts `density` on; every metric inherits down from there, so a\n panel sets the whole family and a single group can still differ. `normal`\n states its values rather than omitting them, so a group can go back to the\n default inside a tighter panel. */\n\n.densityTight {\n --wzl-prop-row-gap: 2px;\n --wzl-prop-subpanel-row-gap: 2px;\n --wzl-prop-column-gap: 8px;\n --wzl-prop-panel-pad: 10px 12px;\n --wzl-prop-panel-title-gap: 8px;\n --wzl-prop-group-pad: 4px 8px 6px;\n --wzl-prop-group-title-gap: 2px;\n --wzl-prop-field-h: 18px;\n --wzl-prop-field-pad-x: 5px;\n}\n\n.densityNormal {\n --wzl-prop-row-gap: 4px;\n --wzl-prop-subpanel-row-gap: 3px;\n --wzl-prop-column-gap: 10px;\n --wzl-prop-panel-pad: 14px 16px;\n --wzl-prop-panel-title-gap: 12px;\n --wzl-prop-group-pad: 6px 10px 8px;\n --wzl-prop-group-title-gap: 4px;\n --wzl-prop-field-h: 20px;\n --wzl-prop-field-pad-x: 6px;\n}\n\n.densityRoomy {\n --wzl-prop-row-gap: 8px;\n --wzl-prop-subpanel-row-gap: 7px;\n --wzl-prop-column-gap: 14px;\n --wzl-prop-panel-pad: 18px 20px;\n --wzl-prop-panel-title-gap: 16px;\n --wzl-prop-group-pad: 10px 14px 12px;\n --wzl-prop-group-title-gap: 6px;\n --wzl-prop-field-h: 24px;\n --wzl-prop-field-pad-x: 8px;\n}\n\n/* ── align ──────────────────────────────────────────────────────────\n Where an inline row's label and control sit on the row's cross axis.\n Inherits like density does, so a column of color rows lines its swatches up\n on one edge from a single declaration on the list.\n\n Two names, because a color row aligns twice: its items within their grid row,\n and the whole grid within the row's box. They agree except at `baseline`,\n which `align-content` does not take — there the grid goes to the top and the\n items find the label's baseline inside it. */\n\n.alignStart {\n --wzl-prop-row-align: start;\n --wzl-prop-row-align-content: start;\n}\n\n.alignCenter {\n --wzl-prop-row-align: center;\n --wzl-prop-row-align-content: center;\n}\n\n.alignEnd {\n --wzl-prop-row-align: end;\n --wzl-prop-row-align-content: end;\n}\n\n.alignBaseline {\n --wzl-prop-row-align: baseline;\n --wzl-prop-row-align-content: start;\n}\n\n/* ── Row ────────────────────────────────────────────────────────\n Default shape: label on top (with optional right-aligned readout),\n control below. */\n\n.row {\n display: flex;\n flex-direction: column;\n /* No gap by default — controls sit flush under their label, matching\n speech-balloons `.field { gap: 0 }`. Slider rows pull tighter still\n via a negative margin on the label below. */\n gap: 0;\n min-width: 0;\n}\n\n/* Slider rows: pull the track up under the label so the row reads as one\n tight block rather than a label + a separate horizontal strip. Also\n bottom-align the track within the row so paired rows in a 2-col grid\n keep their tracks lined up even when one label wraps to two lines. */\n.row:has(> input[type='range']) {\n justify-content: flex-end;\n}\n\n.row:has(> input[type='range']) > .rowLabel {\n /* Baseline-align (not center) so the readout input doesn't inflate the\n label-group height past the text baseline. */\n align-items: baseline;\n justify-content: space-between;\n gap: 4px;\n margin-bottom: -4px;\n}\n\n.rowLabel {\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-sm);\n line-height: var(--wzl-leading-snug);\n color: var(--wzl-fg-muted);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.readout {\n font-style: normal;\n font-weight: var(--wzl-font-weight-medium);\n color: var(--wzl-accent);\n margin-left: auto;\n}\n\n/* ⓘ help affordance beside a row label. Every dimension is stated because a\n host app's bare-element defaults can be authored at zero specificity —\n labkit's `:where(.lk-root) :where(button)` sets `height: var(--wzl-control-h)`\n and horizontal padding, which would otherwise stretch a one-glyph button into\n a full-height control. */\n.help {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.15em;\n height: 1.15em;\n padding: 0;\n border: 0;\n background: none;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n color: var(--wzl-fg-subtle);\n font-size: var(--wzl-font-size-sm);\n line-height: 1;\n letter-spacing: normal;\n cursor: help;\n border-radius: var(--wzl-radius-sm);\n}\n\n.help:hover {\n color: var(--wzl-fg);\n border-color: transparent;\n}\n\n.help:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 1px;\n color: var(--wzl-fg);\n}\n\n/* Single always-rendered <input> styled to read as a text label until focused. */\n.readoutInput {\n font: inherit;\n font-weight: var(--wzl-font-weight-medium);\n /* Force leading-tight — the label parent inherits 1.2, which would make the\n input box ~3px taller than the speech-balloons reference. */\n line-height: var(--wzl-leading-tight);\n color: var(--wzl-accent);\n background: transparent;\n border: 1px solid transparent;\n border-radius: var(--wzl-radius-sm);\n padding: 1px 0;\n margin: 0;\n width: calc(5em - 24px);\n text-align: right;\n cursor: text;\n outline: none;\n transition: background 80ms ease, border-color 80ms ease;\n -moz-appearance: textfield;\n}\n\n.readoutInput:hover {\n background: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.readoutInput:focus {\n background: var(--wzl-surface-sunken);\n border-color: var(--wzl-focus-ring);\n}\n\n.readoutInput::-webkit-inner-spin-button,\n.readoutInput::-webkit-outer-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n/* Inline-block (not flex) so the browser's native vertical-align for\n <sub>/<sup> still works inside — flex collapses baseline semantics\n and sinks superscripts onto the baseline. */\n.readoutGroup {\n display: inline-block;\n white-space: nowrap;\n}\n\n/* Word-style units (e.g. \"px\") read as a small dim suffix next to the digits. */\n.readoutUnit {\n color: var(--wzl-fg-muted);\n font-size: var(--wzl-font-size-2xs);\n line-height: var(--wzl-leading-tight);\n pointer-events: none;\n}\n\n.fieldUnitGroup {\n display: inline-block;\n white-space: nowrap;\n min-width: 0;\n}\n\n.fieldUnitGroup .readoutUnit {\n margin-left: 4px;\n}\n\n/* Symbol units (e.g. `<sup>°</sup>`) lean on the browser's native super\n positioning. Just dim them to match — do NOT set line-height: 0, that\n would re-collapse the glyph onto the baseline. */\n.readoutGroup sup,\n.fieldUnitGroup sup {\n color: var(--wzl-fg-muted);\n font-size: var(--wzl-font-size-2xs);\n pointer-events: none;\n}\n\n/* ── Inline layout: label sits to the left of the control. ────── */\n.rowInline {\n flex-direction: row;\n align-items: var(--wzl-prop-row-align, center);\n /* Label on the left edge, field on the right: fields of different widths\n still line up as one rail down the column. */\n justify-content: space-between;\n gap: 6px;\n}\n\n.rowInline .rowLabel {\n /* The label never gives: shrinking it breaks a name like `silhouette_width`\n mid-word, since it carries no break opportunity of its own. The field is\n what yields as the column narrows. */\n flex: 0 0 auto;\n}\n\n.rowInline > input,\n.rowInline > select,\n.rowInline > .toggle {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Fields size to their content, not to the row they happen to sit in: a number\n is a handful of digits and a short string is a word. Stated as a width and\n not as a `min(100%, …)`, because an auto-width sidebar sizes to what its\n fields ask for and a percentage asks for an input's 20-character default. */\n.row input[type='number']:not(.readoutInput) {\n width: var(--wzl-prop-number-width, 9ch);\n max-width: 100%;\n -moz-appearance: textfield;\n appearance: textfield;\n}\n\n.row input[type='text']:not(.readoutInput) {\n width: var(--wzl-prop-text-width, 16ch);\n max-width: 100%;\n}\n\n/* An inline row stays on one line: the field gives up width to the label as the\n column narrows, down to a floor that still shows a value. */\n.rowInline > input:not(.readoutInput),\n.rowInline > select {\n flex: 0 1 auto;\n min-width: 4ch;\n}\n\n.rowInline input[type='range'] {\n width: auto;\n}\n\n.row input[type='text']:not(.readoutInput),\n.row input[type='number'] {\n background: var(--wzl-surface-sunken);\n color: var(--wzl-fg);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n /* Height is stated rather than padded to, because the display face's line box\n is half again its font size — padding a field to size makes it 27px tall\n around 13px of text and no amount of trimming the padding fixes that. */\n height: var(--wzl-field-h, var(--wzl-control-h));\n padding: 0 var(--wzl-field-pad-x);\n font-family: var(--wzl-font-ui);\n font-weight: var(--wzl-font-weight-medium);\n font-size: var(--wzl-font-size);\n line-height: var(--wzl-leading-tight);\n}\n\n/* Numbers right-align so a column of them shares a decimal position; text does\n not, so this cannot live in the rule the two share. */\n.row input[type='number'] {\n text-align: right;\n}\n\n/* Chrome reserves the spin-button gutter at the right edge whether or not the\n arrows are painted, so a right-aligned value stops ~20px short of its own\n border and the row reads as if something is missing from that space. The\n panel drives numbers by typing and dragging, never by the steppers.\n `.readoutInput` has always dropped them; this is the same removal for the\n typed field beside it. */\n.row input[type='number']::-webkit-inner-spin-button,\n.row input[type='number']::-webkit-outer-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.row input[type='text']:not(.readoutInput):focus,\n.row input[type='number']:focus {\n outline: none;\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n/* Selects in property rows render borderless — chevron only, transparent\n background, no chrome. Keep the native arrow via appearance: auto so users\n still see something clickable. */\n.row select {\n background: transparent;\n color: var(--wzl-fg);\n border: 0;\n border-radius: 0;\n height: var(--wzl-field-h, var(--wzl-control-h));\n padding: 0;\n font-family: var(--wzl-font-ui);\n font-weight: var(--wzl-font-weight-medium);\n font-size: var(--wzl-font-size);\n line-height: var(--wzl-leading-tight);\n cursor: pointer;\n}\n\n/* An outline rather than the boxed family's inset ring: a property-row select\n is borderless, so there is no border to recolour. */\n.row select:focus {\n outline: 1px solid var(--wzl-focus-ring);\n outline-offset: 2px;\n}\n\n/* ── Color variant: label and swatch side-by-side, two per row in the grid.\n Switches to a 2-row grid when an alpha slider is rendered beneath. */\n/* `align-content: end` sinks the whole grid so a color row paired with a\n taller sibling keeps its alpha track on the neighbour's bottom edge. An\n explicit `align` overrides both that and the per-item alignment below, which\n is what a column of swatches read as a group needs. */\n.rowColor {\n display: grid;\n grid-template-columns: 1fr auto;\n grid-template-rows: auto;\n align-content: var(--wzl-prop-row-align-content, end);\n column-gap: 8px;\n row-gap: 0;\n min-width: 0;\n}\n\n.rowColor .rowLabel {\n grid-column: 1;\n grid-row: 1;\n align-self: var(--wzl-prop-row-align, center);\n flex: initial;\n min-width: 0;\n}\n\n/* 36×24, hairline border, no padding. Padding: 0 is important — the native\n <input type=\"color\"> in some browsers (esp. Chrome) inflates beyond its\n declared box when given padding, which expands the grid row and pushes\n the paired slider track out of bottom alignment. */\n.rowColor input[type='color'] {\n grid-column: 2;\n grid-row: 1;\n align-self: var(--wzl-prop-row-align, center);\n box-sizing: border-box;\n width: 36px;\n height: 18px;\n background: transparent;\n border: 0;\n border-radius: var(--wzl-radius-sm);\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n}\n\n/* Alpha slider: full-width second row beneath the color swatch. */\n.rowColor input[type='range'].alpha {\n grid-column: 1 / -1;\n grid-row: 2;\n height: 12px;\n width: 100%;\n margin: 0;\n}\n\n/* `layout=\"block\"` stacks the color row: label, then swatch, then alpha. */\n.rowColor.rowBlock {\n grid-template-columns: 1fr;\n}\n\n.rowColor.rowBlock .rowLabel {\n grid-column: 1;\n grid-row: 1;\n}\n\n.rowColor.rowBlock input[type='color'] {\n grid-column: 1;\n grid-row: 2;\n justify-self: start;\n}\n\n.rowColor.rowBlock input[type='range'].alpha {\n grid-row: 3;\n}\n\n/* ── Checkbox variant: control-first inline layout, no uppercase label. */\n.rowCheckbox {\n flex-direction: row;\n align-items: var(--wzl-prop-row-align, center);\n gap: 8px;\n cursor: pointer;\n}\n\n.rowCheckbox .rowLabel {\n text-transform: none;\n letter-spacing: normal;\n font-size: var(--wzl-font-size);\n color: var(--wzl-fg);\n /* Place the label after the checkbox visually. */\n order: 1;\n}\n\n.rowCheckbox input[type='checkbox'] {\n accent-color: var(--wzl-accent);\n width: 14px;\n height: 14px;\n cursor: pointer;\n}\n\n/* `layout=\"block\"` stacks the checkbox row: label above, box beneath, both on\n the leading edge — a block row's cross axis is the horizontal one. */\n.rowCheckbox.rowBlock {\n flex-direction: column;\n align-items: var(--wzl-prop-row-align, start);\n}\n\n.rowCheckbox.rowBlock .rowLabel {\n order: 0;\n}\n\n/* ── Segmented toggle group ───────────────────────────────────── */\n.toggle {\n display: flex;\n width: 100%;\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n overflow: hidden;\n}\n\n.toggleButton {\n flex: 1;\n height: 26px;\n padding: 0 8px;\n background: rgba(0, 0, 0, 0.25);\n color: var(--wzl-fg-muted);\n border: 0;\n border-radius: 0;\n font-family: var(--wzl-font-ui);\n font-weight: var(--wzl-font-weight-medium);\n font-size: var(--wzl-font-size-sm);\n line-height: var(--wzl-leading-tight);\n cursor: pointer;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n}\n\n.toggleButton + .toggleButton {\n border-left: 1px solid var(--wzl-border);\n}\n\n.toggleButton:hover {\n color: var(--wzl-fg);\n border-color: var(--wzl-border);\n}\n\n.toggleButtonSelected {\n background: var(--wzl-accent);\n color: var(--wzl-fg-on-accent);\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.toggleButtonSelected:hover {\n color: var(--wzl-fg-on-accent);\n}\n\n/* ── PropertyGroup ──────────────────────────────────────────────────\n Bordered, sunken section inside a list. */\n.group {\n grid-column: 1 / -1;\n background: var(--wzl-surface-sunken);\n border-radius: var(--wzl-radius-md);\n padding: var(--wzl-prop-group-pad, 6px 10px 8px);\n}\n\n.groupTitle {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n align-items: center;\n gap: 8px;\n margin: 0 0 var(--wzl-prop-group-title-gap, 4px);\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-sm);\n letter-spacing: var(--wzl-tracking-wider);\n text-transform: uppercase;\n color: var(--wzl-fg-subtle);\n}\n\n.groupTitle hr {\n border: 0;\n border-top: 1px solid var(--wzl-border);\n margin: 0;\n}\n\n/* A twisty leads the title, so the head takes over the title's bottom gap and\n the two sit on one line. */\n.groupHead {\n margin-bottom: var(--wzl-prop-group-title-gap, 4px);\n}\n\n.groupHead .groupTitle {\n margin-bottom: 0;\n}\n\n/* `hidden` on its own loses to the body's `display: grid` below. */\n.groupBody[hidden] {\n display: none;\n}\n\n.groupBody {\n display: grid;\n grid-template-columns: 1fr 1fr;\n column-gap: var(--wzl-prop-column-gap, 10px);\n row-gap: var(--wzl-prop-row-gap, 4px);\n /* Stretch so paired rows match in height — slider rows then bottom-align\n their tracks (via `margin-top: auto`) and stay lined up even when one\n sibling's label wraps to two lines. */\n align-items: stretch;\n}\n\n.group:not(.groupPairs) .groupBody > :not(.rowColor) {\n grid-column: 1 / -1;\n}\n\n.groupPairs .groupBody > * {\n grid-column: auto;\n}\n\n.groupPairs .groupBody > h3,\n.groupPairs .groupBody > .span {\n grid-column: 1 / -1;\n}\n\n.groupOneUp .groupBody > * {\n grid-column: 1 / -1;\n}\n\n/* ── Subpanel ────────────────────────────────────────────────────────\n Headered group of controls inside a list. The header is a typographic\n label flanked by a horizontal rule — no card chrome, just the divider. */\n.subpanel {\n display: grid;\n grid-template-columns: 1fr 1fr;\n column-gap: var(--wzl-prop-column-gap, 10px);\n row-gap: var(--wzl-prop-subpanel-row-gap, 3px);\n align-items: stretch;\n background: transparent;\n border: 0;\n padding: 0;\n margin: 0;\n}\n\n.subpanel > * {\n min-width: 0;\n}\n\n.subpanel > .subpanelTitle,\n.subpanel > .span {\n grid-column: 1 / -1;\n}\n\n.subpanelTitle {\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-medium);\n font-size: var(--wzl-font-size-sm);\n line-height: var(--wzl-leading-tight);\n color: var(--wzl-fg);\n text-transform: uppercase;\n letter-spacing: var(--wzl-tracking-wider);\n margin: 0;\n padding: 6px 0 4px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.subpanelTitle > span {\n flex: 0 0 auto;\n}\n\n.subpanelTitle > hr {\n flex: 1 1 auto;\n border: 0;\n border-top: 1px solid rgba(255, 255, 255, 0.12);\n margin: 0;\n}\n\n/* ── EffectCard ──────────────────────────────────────────────────────\n Draggable, collapsible card. Optional --wzl-effect-card-accent re-binds the\n local --wzl-accent so every descendant control auto-tints. */\n.card {\n background: rgba(0, 0, 0, 0.25);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n.cardDragging {\n opacity: 0.45;\n}\n\n.cardExpanded {\n background: rgba(0, 0, 0, 0.3);\n}\n\n.cardAccented {\n /* Re-bind --wzl-accent inside the card so every descendant style that paints\n with var(--wzl-accent) (readouts, slider thumb, etc.) picks up the\n per-card tint automatically. */\n --wzl-accent: var(--wzl-effect-card-accent);\n background: color-mix(in srgb, var(--wzl-effect-card-accent) 4%, rgba(0, 0, 0, 0.28));\n border-color: color-mix(in srgb, var(--wzl-effect-card-accent) 28%, var(--wzl-border));\n border-left-width: 4px;\n border-left-color: var(--wzl-effect-card-accent);\n}\n\n.cardAccented.cardExpanded {\n background: color-mix(in srgb, var(--wzl-effect-card-accent) 5%, rgba(0, 0, 0, 0.35));\n}\n\n.cardAccented .cardHead {\n background: color-mix(in srgb, var(--wzl-effect-card-accent) 38%, rgba(0, 0, 0, 0.4));\n}\n\n.cardAccented .cardHead:hover {\n background: color-mix(in srgb, var(--wzl-effect-card-accent) 48%, rgba(0, 0, 0, 0.35));\n}\n\n.cardHead {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 2px 10px;\n cursor: pointer;\n user-select: none;\n}\n\n.cardHead:hover {\n background: rgba(255, 255, 255, 0.03);\n}\n\n.cardHandle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--wzl-fg-muted);\n cursor: grab;\n padding: 0 2px;\n}\n\n.cardHandle:active {\n cursor: grabbing;\n}\n\n.cardIndexBadge {\n /* Rendered as a <button> (keyboard-operable drag/toggle handle); strip the\n native button chrome so it matches the surrounding badge styling. */\n appearance: none;\n border: 0;\n background: none;\n display: inline-flex;\n align-items: baseline;\n justify-content: center;\n min-width: 14px;\n padding: 0 2px;\n color: var(--wzl-effect-card-accent, var(--wzl-fg-muted));\n font-size: var(--wzl-font-size);\n font-weight: var(--wzl-font-weight-bold);\n font-family: var(--wzl-font-mono);\n line-height: 1;\n}\n\n.cardTitle {\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-medium);\n font-size: var(--wzl-font-size);\n line-height: var(--wzl-leading-tight);\n color: var(--wzl-fg);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.cardPrimary {\n font-family: var(--wzl-font-ui);\n font-weight: var(--wzl-font-weight-medium);\n font-size: var(--wzl-font-size-sm);\n line-height: var(--wzl-leading-tight);\n color: var(--wzl-fg);\n background: rgba(0, 0, 0, 0.3);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n padding: 2px 6px;\n margin-left: auto;\n}\n\n/* Push the remove button to the right edge — whether it follows a primary\n chip or sits alone after the title. */\n.cardHead .cardRemove,\n.cardHead .cardPrimary + .cardRemove {\n margin-left: auto;\n}\n\n.cardHead .cardPrimary ~ .cardRemove {\n margin-left: 4px;\n}\n\n.cardRemove {\n background: transparent;\n color: var(--wzl-fg-muted);\n border: 1px solid transparent;\n border-radius: var(--wzl-radius-sm);\n width: 22px;\n height: 22px;\n font-size: var(--wzl-font-size);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.cardRemove:hover {\n color: var(--wzl-danger-fg, var(--wzl-danger));\n border-color: color-mix(in srgb, var(--wzl-danger) 40%, transparent);\n background: color-mix(in srgb, var(--wzl-danger) 8%, transparent);\n}\n\n.cardBody {\n padding: 4px 12px 12px;\n border-top: 1px solid var(--wzl-border);\n}\n\n/* ── EffectCardList ────────────────────────────────────────────────── */\n.cardList {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.cardListWrap {\n position: relative;\n}\n\n.cardDropHint {\n height: 0;\n border-top: 2px solid var(--wzl-accent);\n border-radius: var(--wzl-radius-sm);\n margin: 2px 0;\n box-shadow: 0 0 6px color-mix(in srgb, var(--wzl-accent) 60%, transparent);\n}\n\n.cardListEmpty {\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-sm);\n line-height: var(--wzl-leading);\n color: var(--wzl-fg-muted);\n text-align: center;\n padding: 16px 12px;\n border: 1px dashed var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n}\n","import { type ReactNode, useEffect, useRef, useState } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { dlog } from '../../dlog';\nimport { formatNumber, parseSignedNumber } from '../../format/number';\nimport { Tooltip, TooltipTrigger } from '../Tooltip';\nimport shared from '../range.module.css';\nimport s from './Properties.module.css';\n\n/**\n * How much room a container gives its rows — gaps, padding, and field height,\n * moved together. Set on any container in the family; it reaches every\n * descendant, so an inner group can differ from the panel around it.\n */\nexport type PropertyDensity = 'tight' | 'normal' | 'roomy';\n\n/** Where an inline row's label and control sit on the row's cross axis. Set on\n * a container to line a whole column of them up. */\nexport type PropertyAlign = 'start' | 'center' | 'end' | 'baseline';\n\n/** Metric props every container in the family takes. Both reach descendants,\n * so the nearest container that states one wins. */\nexport interface PropertyMetricProps {\n /** Room the rows get. Unset inherits from an outer container, else `normal`. */\n density?: PropertyDensity;\n /**\n * Cross-axis alignment of an inline row's label and control. `baseline` sits\n * the control on the label's first-line baseline, which is what lines a\n * column of swatches up against labels of different heights. Unset keeps each\n * variant's own alignment — a color row centers its label and swatch and\n * sinks the pair to the row's bottom edge, which keeps a paired alpha track\n * level with its neighbour.\n */\n align?: PropertyAlign;\n}\n\nconst DENSITY_CLASS: Record<PropertyDensity, string> = {\n tight: s.densityTight,\n normal: s.densityNormal,\n roomy: s.densityRoomy,\n};\n\nconst ALIGN_CLASS: Record<PropertyAlign, string> = {\n start: s.alignStart,\n center: s.alignCenter,\n end: s.alignEnd,\n baseline: s.alignBaseline,\n};\n\n/** Joins a container's base class with its metric classes and the consumer's.\n * Exported for the family's other files, which share the same two props. */\nexport function propertyMetricClass(\n base: string,\n { density, align }: PropertyMetricProps,\n className?: string,\n): string {\n return [base, density && DENSITY_CLASS[density], align && ALIGN_CLASS[align], className]\n .filter(Boolean)\n .join(' ');\n}\n\n/** Props for `<PropertyPanel>`. */\nexport interface PropertyPanelProps extends PropertyMetricProps {\n title?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\n/** A titled panel holding property rows — the sidebar container the rest of\n * this module's components fill. */\nexport function PropertyPanel({\n title,\n children,\n className,\n density,\n align,\n}: PropertyPanelProps) {\n return (\n <div className={propertyMetricClass(s.panel, { density, align }, className)}>\n {title != null && <h2 className={s.panelTitle}>{title}</h2>}\n {children}\n </div>\n );\n}\n\n/** How a property list packs its rows into two columns. */\nexport type PropertyListPack = 'auto-color' | 'pairs' | 'one-up';\n\n/** Props for `<PropertyList>`. */\nexport interface PropertyListProps extends PropertyMetricProps {\n children: ReactNode;\n className?: string;\n /**\n * How rows pack into the 2-column grid.\n * - `'auto-color'` (default): only color rows pair side-by-side; everything\n * else spans the full width. Right for sparse top-level panels.\n * - `'pairs'`: every row auto-places into the 2-column grid two-per-row.\n * Headers and subpanels still span the full width; wrap any other\n * full-width child in `<PropertySpan>`. Right for dense effect bodies.\n * - `'one-up'`: every row spans the full width, color rows included. Right\n * for a palette — a column of swatches read as a group.\n */\n pack?: PropertyListPack;\n}\n\n/**\n * Grid container for PropertyRows. Use standalone for chrome-less layouts, or\n * nest inside <PropertyPanel/> for the standard glass card.\n */\nexport function PropertyList({\n children,\n className,\n pack = 'auto-color',\n density,\n align,\n}: PropertyListProps) {\n const base = `${s.list}${pack === 'pairs' ? ` ${s.listPairs}` : pack === 'one-up' ? ` ${s.listOneUp}` : ''}`;\n return <div className={propertyMetricClass(base, { density, align }, className)}>{children}</div>;\n}\n\n/** Props for `<PropertySpan>`. */\nexport interface PropertySpanProps {\n children: ReactNode;\n className?: string;\n}\n\n/**\n * Wrapper that makes an arbitrary child span both columns of a\n * `<PropertyList pack=\"pairs\">`, a `<PropertyGroup pack=\"pairs\">` body, or a\n * `<Subpanel>`. `<PropertyRow span>` covers the row case; this covers\n * everything else a consumer puts in the grid.\n */\nexport function PropertySpan({ children, className }: PropertySpanProps) {\n return <div className={className ? `${s.span} ${className}` : s.span}>{children}</div>;\n}\n\n/** Which control shape a row holds, which decides its intrinsic layout. */\nexport type PropertyRowVariant = 'default' | 'color' | 'checkbox';\n/** Whether a row's label sits above its control or beside it. */\nexport type PropertyRowLayout = 'block' | 'inline';\n\n/** Props for `<PropertyRow>`. */\nexport interface PropertyRowProps extends PropertyMetricProps {\n label: ReactNode;\n /** Right-aligned readout shown next to the label (e.g. current value). */\n readout?: ReactNode;\n /**\n * Help text for the row, shown in a tooltip off an ⓘ affordance beside the\n * label. Empty or absent renders no affordance.\n */\n description?: string;\n variant?: PropertyRowVariant;\n /**\n * Label position relative to the control. Unset takes the variant's own\n * orientation: `block` — label above control — for the default variant, and\n * `inline` for the color and checkbox variants, which read as a row.\n */\n layout?: PropertyRowLayout;\n /**\n * Take the full width of the enclosing grid. Only has an effect inside a\n * `<PropertyList pack=\"pairs\">` or a `<Subpanel>`; elsewhere rows are already\n * full width.\n */\n span?: boolean;\n children: ReactNode;\n htmlFor?: string;\n className?: string;\n}\n\n/** The label-plus-control frame every typed row below is built from. Use it\n * directly for a control this module does not cover. */\nexport function PropertyRow({\n label,\n readout,\n description,\n variant = 'default',\n layout,\n span,\n children,\n htmlFor,\n className,\n density,\n align,\n}: PropertyRowProps) {\n const variantClass = variant === 'color' ? s.rowColor : variant === 'checkbox' ? s.rowCheckbox : '';\n // Each variant already lays out one way; a class is only needed for the\n // other one. The default variant stacks, so it needs `.rowInline`; color and\n // checkbox read as a row, so they need `.rowBlock`.\n const intrinsic: PropertyRowLayout = variant === 'default' ? 'block' : 'inline';\n const resolved = layout ?? intrinsic;\n const layoutClass =\n resolved === intrinsic ? '' : resolved === 'inline' ? s.rowInline : s.rowBlock;\n const cls = propertyMetricClass(\n [s.row, variantClass, layoutClass, span && s.span].filter(Boolean).join(' '),\n { density, align },\n className,\n );\n return (\n <label className={cls} htmlFor={htmlFor}>\n <span className={s.rowLabel}>\n {label}\n {description ? <PropertyRowHelp label={label} description={description} /> : null}\n {readout != null && <em className={s.readout}>{readout}</em>}\n </span>\n {children}\n </label>\n );\n}\n\n/** Tooltip trigger for a row's `description`. A tooltip trigger has to be\n * interactive to be keyboard-reachable, so this is a real button. */\nfunction PropertyRowHelp({ label, description }: { label: ReactNode; description: string }) {\n const name = typeof label === 'string' ? label : 'this setting';\n return (\n <TooltipTrigger>\n <Focusable>\n <button\n type=\"button\"\n className={s.help}\n aria-label={`About ${name}`}\n onClick={(e) => {\n // The wrapping <label> would otherwise actuate the row's control.\n e.preventDefault();\n e.stopPropagation();\n }}\n onMouseDown={(e) => e.stopPropagation()}\n >\n ⓘ\n </button>\n </Focusable>\n <Tooltip>{description}</Tooltip>\n </TooltipTrigger>\n );\n}\n\n// ── Row implementations ──────────────────────────────────────────────\n\n/** Props for `<SliderRow>`. */\nexport interface SliderRowProps extends PropertyMetricProps {\n label: ReactNode;\n value: number;\n min: number;\n max: number;\n step?: number;\n /**\n * The committed value. Given `onInput` as well, it fires once a drag ends\n * or a typed readout is accepted; on its own it fires on every move, which\n * is what a row with one callback has always done.\n */\n onChange: (next: number) => void;\n /**\n * The live value, fired continuously through a drag. Pass it alongside\n * `onChange` when the write is expensive — cheap state here, the costly\n * work there. Mirrors `<Slider>`'s `onInput` / `onChange` pair.\n */\n onInput?: (next: number) => void;\n /** Override how the value is rendered next to the label. Defaults to `value.toString()`. */\n format?: (value: number) => ReactNode;\n /**\n * Optional suffix rendered next to the readout. A string becomes a\n * baseline-aligned dim \"word\" unit (e.g. \"px\"); pass JSX like `<sup>°</sup>`\n * to get the browser's native super positioning for symbol units.\n */\n unit?: ReactNode;\n layout?: PropertyRowLayout;\n description?: string;\n /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */\n span?: boolean;\n}\n\n/** A bounded number edited by dragging, with a live readout whose precision\n * follows `step`. */\nexport function SliderRow({\n label,\n value,\n min,\n max,\n step = 1,\n onChange,\n onInput,\n format,\n unit,\n layout,\n description,\n span,\n density,\n align,\n}: SliderRowProps) {\n // Default readout precision tracks `step`: integer steps → 0 decimals,\n // 0.1 → 1 decimal, 0.05/0.02/0.01 → 2 decimals, 0.005 → 3, etc. Callers\n // can still pass an explicit `format` to override (e.g. for a custom\n // unit string or a non-decimal display like fractions).\n const decimals = step >= 1 ? 0 : Math.min(6, Math.max(0, Math.ceil(-Math.log10(step))));\n const live = onInput ?? onChange;\n const commit = onInput ? onChange : undefined;\n const range = useRef<HTMLInputElement>(null);\n const latestCommit = useRef(commit);\n useEffect(() => {\n latestCommit.current = commit;\n });\n useEffect(() => {\n const el = range.current;\n if (!el) return;\n // The commit half is the platform's: a range input fires `input` through\n // the drag and `change` once, on release. React's synthetic onChange sees\n // only the first, since its value tracker drops the unchanged second.\n const onCommit = () => latestCommit.current?.(Number(el.value));\n el.addEventListener('change', onCommit);\n return () => el.removeEventListener('change', onCommit);\n }, []);\n const effectiveFormat =\n format ??\n ((n: number) =>\n formatNumber(n, {\n useGrouping: false,\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }));\n return (\n <PropertyRow\n span={span}\n label={label}\n readout={\n <EditableReadout\n value={value}\n min={min}\n max={max}\n format={effectiveFormat}\n unit={unit}\n onCommit={(next) => {\n live(next);\n commit?.(next);\n }}\n />\n }\n layout={layout}\n description={description}\n density={density}\n align={align}\n >\n <input\n ref={range}\n type=\"range\"\n className={shared.range}\n tabIndex={-1}\n min={min}\n max={max}\n step={step}\n value={value}\n onChange={(e) => {\n const v = Number(e.target.value);\n dlog('property-panel', 'slider', { label, value: v });\n live(v);\n }}\n />\n </PropertyRow>\n );\n}\n\ninterface EditableReadoutProps {\n value: number;\n min: number;\n max: number;\n format?: (value: number) => ReactNode;\n unit?: ReactNode;\n onCommit: (next: number) => void;\n}\n\n/**\n * Readout that swaps to a number input on click, commits on Enter/blur,\n * cancels on Escape. Clicks are stopped so the wrapping <label> doesn't\n * forward focus to the slider thumb.\n */\nfunction EditableReadout({ value, min, max, format, unit, onCommit }: EditableReadoutProps) {\n // Draft is non-null only while the input is focused. Live value mirrors\n // into the input otherwise. Pattern mirrors speech-balloons Lab.tsx:893-942.\n const [draft, setDraft] = useState<string | null>(null);\n const fmt = (n: number) =>\n format ? format(n) : formatNumber(n, { useGrouping: false, maximumFractionDigits: 20 });\n const displayValue =\n draft !== null\n ? draft\n : (() => {\n const formatted = fmt(value);\n return typeof formatted === 'string' ? formatted : String(formatted);\n })();\n\n const suffix =\n unit == null ? null : typeof unit === 'string' ? (\n <span className={s.readoutUnit}>{unit}</span>\n ) : (\n unit\n );\n\n const commit = () => {\n if (draft !== null) {\n const n = parseSignedNumber(draft);\n if (Number.isFinite(n)) onCommit(Math.min(max, Math.max(min, n)));\n }\n setDraft(null);\n };\n\n return (\n <span className={s.readoutGroup}>\n <input\n type=\"text\"\n inputMode=\"decimal\"\n className={s.readoutInput}\n value={displayValue}\n onFocus={(e) => {\n const formatted = fmt(value);\n setDraft(typeof formatted === 'string' ? formatted : String(formatted));\n e.currentTarget.select();\n }}\n onChange={(e) => setDraft(e.target.value.replace(/-/g, '−'))}\n onBlur={commit}\n onClick={(e) => {\n // Stop the click from reaching the wrapping <label> (which would\n // forward focus to the slider thumb).\n e.preventDefault();\n e.stopPropagation();\n e.currentTarget.focus();\n }}\n onMouseDown={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n commit();\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n setDraft(null);\n e.currentTarget.blur();\n }\n }}\n />\n {suffix}\n </span>\n );\n}\n\n/** Props for `<ColorRow>`. */\nexport interface ColorRowProps extends PropertyMetricProps {\n label: ReactNode;\n value: string;\n onChange: (next: string) => void;\n /** 0..1 alpha. When provided, a translucent slider renders beneath the swatch. */\n alpha?: number;\n onAlphaChange?: (next: number) => void;\n /**\n * Render the alpha track as inert (dimmed, no thumb, not-allowed cursor).\n * Use when the color's consumer drops alpha so the affordance reads dead.\n */\n alphaDisabled?: boolean;\n /** Label beside the swatch (default) or above it. */\n layout?: PropertyRowLayout;\n description?: string;\n /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */\n span?: boolean;\n}\n\n/** A color swatch, optionally with an alpha slider beneath it. */\nexport function ColorRow({\n label,\n value,\n onChange,\n alpha,\n onAlphaChange,\n alphaDisabled,\n layout,\n description,\n span,\n density,\n align,\n}: ColorRowProps) {\n const showAlpha = alpha != null;\n return (\n <PropertyRow\n span={span}\n label={label}\n variant=\"color\"\n layout={layout}\n description={description}\n density={density}\n align={align}\n >\n <input type=\"color\" value={value} onChange={(e) => onChange(e.target.value)} />\n {showAlpha && (\n <input\n type=\"range\"\n className={`${shared.range} ${shared.alpha} ${s.alpha}`}\n min={0}\n max={1}\n step={0.01}\n value={alpha}\n disabled={alphaDisabled}\n onChange={(e) => onAlphaChange?.(Number(e.target.value))}\n />\n )}\n </PropertyRow>\n );\n}\n\n/** Props for `<CheckboxRow>`. */\nexport interface CheckboxRowProps extends PropertyMetricProps {\n label: ReactNode;\n value: boolean;\n onChange: (next: boolean) => void;\n /** Label beside the box (default) or above it. */\n layout?: PropertyRowLayout;\n description?: string;\n /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */\n span?: boolean;\n}\n\n/** A boolean checkbox. */\nexport function CheckboxRow({\n label,\n value,\n onChange,\n layout,\n span,\n description,\n density,\n align,\n}: CheckboxRowProps) {\n return (\n <PropertyRow\n span={span}\n label={label}\n variant=\"checkbox\"\n layout={layout}\n description={description}\n density={density}\n align={align}\n >\n <input type=\"checkbox\" checked={value} onChange={(e) => onChange(e.target.checked)} />\n </PropertyRow>\n );\n}\n\n/** Props for `<TextRow>`. */\nexport interface TextRowProps extends PropertyMetricProps {\n label: ReactNode;\n value: string;\n onChange: (next: string) => void;\n placeholder?: string;\n maxLength?: number;\n layout?: PropertyRowLayout;\n description?: string;\n /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */\n span?: boolean;\n}\n\n/** A single-line text input. */\nexport function TextRow({\n label,\n value,\n onChange,\n placeholder,\n maxLength,\n layout,\n description,\n span,\n density,\n align,\n}: TextRowProps) {\n return (\n <PropertyRow\n span={span}\n label={label}\n layout={layout}\n description={description}\n density={density}\n align={align}\n >\n <input\n type=\"text\"\n value={value}\n placeholder={placeholder}\n maxLength={maxLength}\n onChange={(e) => onChange(e.target.value)}\n />\n </PropertyRow>\n );\n}\n\n/** Props for `<NumberRow>`. */\nexport interface NumberRowProps extends PropertyMetricProps {\n label: ReactNode;\n value: number;\n onChange: (next: number) => void;\n min?: number;\n max?: number;\n step?: number;\n placeholder?: string;\n /**\n * Optional suffix rendered after the field. A string becomes a dim \"word\"\n * unit (e.g. \"px\"); pass JSX like `<sup>°</sup>` for symbol units. Display\n * only — it never participates in parsing, and the value stays a number.\n */\n unit?: ReactNode;\n layout?: PropertyRowLayout;\n description?: string;\n /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */\n span?: boolean;\n}\n\n/** A number typed directly. Reach for `<SliderRow>` when the range matters\n * more than the exact value. */\nexport function NumberRow({\n label,\n value,\n onChange,\n min,\n max,\n step,\n placeholder,\n unit,\n layout,\n description,\n span,\n density,\n align,\n}: NumberRowProps) {\n const input = (\n <input\n type=\"number\"\n value={value}\n min={min}\n max={max}\n step={step}\n placeholder={placeholder}\n onChange={(e) => {\n const raw = e.target.value;\n if (raw === '') return;\n const n = Number(raw);\n if (Number.isFinite(n)) onChange(n);\n }}\n />\n );\n return (\n <PropertyRow\n span={span}\n label={label}\n layout={layout}\n description={description}\n density={density}\n align={align}\n >\n {unit == null ? (\n input\n ) : (\n <span className={s.fieldUnitGroup}>\n {input}\n {typeof unit === 'string' ? <span className={s.readoutUnit}>{unit}</span> : unit}\n </span>\n )}\n </PropertyRow>\n );\n}\n\nexport interface PropertyOption<T extends string> {\n value: T;\n label: ReactNode;\n}\n\n/** Props for `<SelectRow>`. */\nexport interface SelectRowProps<T extends string> extends PropertyMetricProps {\n label: ReactNode;\n value: T;\n options: ReadonlyArray<PropertyOption<T>>;\n onChange: (next: T) => void;\n layout?: PropertyRowLayout;\n description?: string;\n /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */\n span?: boolean;\n}\n\n/** A dropdown over a fixed set of choices. Prefer `<ToggleRow>` when there\n * are only two or three and they should all stay visible. */\nexport function SelectRow<T extends string>({\n label,\n value,\n options,\n onChange,\n layout,\n description,\n span,\n density,\n align,\n}: SelectRowProps<T>) {\n return (\n <PropertyRow\n span={span}\n label={label}\n layout={layout}\n description={description}\n density={density}\n align={align}\n >\n <select\n value={value}\n onChange={(e) => {\n const v = e.target.value as T;\n dlog('property-panel', 'select', { label, value: v });\n onChange(v);\n }}\n >\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {/* HTML <option> only renders text; ReactNode → string coerce */}\n {opt.label as string}\n </option>\n ))}\n </select>\n </PropertyRow>\n );\n}\n\n/** Props for `<ToggleRow>`. */\nexport interface ToggleRowProps<T extends string> extends PropertyMetricProps {\n label: ReactNode;\n value: T;\n options: ReadonlyArray<PropertyOption<T>>;\n onChange: (next: T) => void;\n layout?: PropertyRowLayout;\n description?: string;\n /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */\n span?: boolean;\n}\n\n/** A segmented control: the same choice as a select, with every option\n * visible at once. */\nexport function ToggleRow<T extends string>({\n label,\n value,\n options,\n onChange,\n layout,\n description,\n span,\n density,\n align,\n}: ToggleRowProps<T>) {\n return (\n <PropertyRow\n span={span}\n label={label}\n layout={layout}\n description={description}\n density={density}\n align={align}\n >\n <div className={s.toggle}>\n {options.map((opt) => {\n const selected = opt.value === value;\n return (\n <button\n key={opt.value}\n type=\"button\"\n aria-pressed={selected}\n className={\n selected ? `${s.toggleButton} ${s.toggleButtonSelected}` : s.toggleButton\n }\n onClick={() => onChange(opt.value)}\n >\n {opt.label}\n </button>\n );\n })}\n </div>\n </PropertyRow>\n );\n}\n","import { type CSSProperties, type ReactNode, useState } from 'react';\nimport s from './Properties.module.css';\nimport { type PropertyMetricProps, propertyMetricClass } from './PropertyPanel';\n\n// ── Subpanel ─────────────────────────────────────────────────────────\n// Headered group of controls inside a wider panel/list. The header is a\n// label flanked by a horizontal rule — no background, padding, or border;\n// purely a typographic divider. Ported from speech-balloons styles.css:277-313.\n\n/** Props for `<Subpanel>`. */\nexport interface SubpanelProps extends PropertyMetricProps {\n title: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\n/** A typographic divider inside a property list: a small title flanked by a\n * rule. Purely a heading — use `<PropertyGroup>` for a bordered section. */\nexport function Subpanel({ title, children, className, density, align }: SubpanelProps) {\n const cls = propertyMetricClass(s.subpanel, { density, align }, className);\n return (\n <div className={cls}>\n <h4 className={s.subpanelTitle}>\n <span>{title}</span>\n <hr />\n </h4>\n {children}\n </div>\n );\n}\n\n// ── EffectCard ───────────────────────────────────────────────────────\n// Draggable, collapsible card for stacked effect editors. Optional\n// --wzl-effect-card-accent per-instance recolors the title bar, border-left, and\n// (via re-binding --wzl-accent inside the card) every descendant control that\n// reads from the accent token.\n\n/** Props for `<EffectCard>`. */\nexport interface EffectCardProps {\n /** Card title rendered in the title bar (e.g. effect kind label or a select). */\n title: ReactNode;\n /** Optional badge/chip displayed before the remove button (e.g. primary value summary). */\n primary?: ReactNode;\n /** Optional accent color in any CSS color form. Sets --wzl-effect-card-accent on the card. */\n accent?: string;\n /** Optional ordinal badge that doubles as the drag handle (used by tail cards in SB). */\n index?: number;\n expanded?: boolean;\n onToggleExpanded?: () => void;\n onRemove?: () => void;\n /**\n * Drag-handle wiring. When provided, an inline handle (⋮⋮ icon or `index`)\n * activates HTML5 drag on press. Consumer owns the drag/drop coordination —\n * see EffectCardList for the batteries-included wrapper.\n */\n draggable?: boolean;\n onPressHandle?: () => void;\n onReleaseHandle?: () => void;\n onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void;\n onDragEnd?: (e: React.DragEvent<HTMLDivElement>) => void;\n onDragOver?: (e: React.DragEvent<HTMLDivElement>) => void;\n onDragLeave?: (e: React.DragEvent<HTMLDivElement>) => void;\n onDrop?: (e: React.DragEvent<HTMLDivElement>) => void;\n dragging?: boolean;\n children?: ReactNode;\n className?: string;\n}\n\n/** An accented, collapsible card for one item in a list of like things —\n * effects, layers, tails. The accent color rebinds the theme accent token\n * within the card, so its controls pick it up too. */\nexport function EffectCard({\n title,\n primary,\n accent,\n index,\n expanded = true,\n onToggleExpanded,\n onRemove,\n draggable,\n onPressHandle,\n onReleaseHandle,\n onDragStart,\n onDragEnd,\n onDragOver,\n onDragLeave,\n onDrop,\n dragging,\n children,\n className,\n}: EffectCardProps) {\n const stateClass = `${expanded ? ` ${s.cardExpanded}` : ''}${dragging ? ` ${s.cardDragging}` : ''}${accent ? ` ${s.cardAccented}` : ''}`;\n const cls = `${s.card}${stateClass}${className ? ` ${className}` : ''}`;\n // Per-instance dynamic color → CSS custom property requires inline style.\n // The card's CSS re-binds --wzl-accent to it so descendants tint.\n const style = accent ? ({ '--wzl-effect-card-accent': accent } as CSSProperties) : undefined;\n\n // When a toggle handler is supplied the header acts as a disclosure button:\n // role + tab stop + Enter/Space. Bundled together so the interactive props are\n // applied as a set (no half-interactive element when onToggleExpanded is absent).\n const headProps = onToggleExpanded\n ? {\n role: 'button' as const,\n tabIndex: 0,\n onClick: onToggleExpanded,\n onKeyDown: (e: React.KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onToggleExpanded();\n }\n },\n }\n : {};\n\n return (\n // biome-ignore lint/a11y/noStaticElementInteractions: native HTML5 drag-and-drop container — drag events are not click/keyboard interactions and have no semantic element; the in-card handle and header carry the a11y affordances.\n <div\n className={cls}\n style={style}\n draggable={draggable}\n onDragStart={onDragStart}\n onDragEnd={onDragEnd}\n onDragOver={onDragOver}\n onDragLeave={onDragLeave}\n onDrop={onDrop}\n >\n <div className={s.cardHead} {...headProps}>\n {index != null ? (\n <button\n type=\"button\"\n className={`${s.cardIndexBadge} ${s.cardHandle}`}\n title=\"Drag to reorder · click to toggle\"\n aria-label={`Item ${index + 1} — drag to reorder, click to toggle`}\n onMouseDown={onPressHandle}\n onMouseUp={onReleaseHandle}\n onClick={(e) => {\n e.stopPropagation();\n onToggleExpanded?.();\n }}\n >\n {index + 1}\n </button>\n ) : (\n <span\n className={s.cardHandle}\n aria-hidden=\"true\"\n title=\"Drag to reorder\"\n onMouseDown={onPressHandle}\n onMouseUp={onReleaseHandle}\n >\n <DragHandleIcon />\n </span>\n )}\n <span className={s.cardTitle}>{title}</span>\n {primary != null && <span className={s.cardPrimary}>{primary}</span>}\n {onRemove && (\n <button\n type=\"button\"\n className={s.cardRemove}\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n title=\"Remove\"\n aria-label=\"Remove\"\n >\n ✕\n </button>\n )}\n </div>\n {expanded && <div className={s.cardBody}>{children}</div>}\n </div>\n );\n}\n\nfunction DragHandleIcon() {\n return (\n <svg width=\"10\" height=\"14\" viewBox=\"0 0 10 14\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.3\" fill=\"currentColor\" />\n <circle cx=\"7\" cy=\"3\" r=\"1.3\" fill=\"currentColor\" />\n <circle cx=\"3\" cy=\"7\" r=\"1.3\" fill=\"currentColor\" />\n <circle cx=\"7\" cy=\"7\" r=\"1.3\" fill=\"currentColor\" />\n <circle cx=\"3\" cy=\"11\" r=\"1.3\" fill=\"currentColor\" />\n <circle cx=\"7\" cy=\"11\" r=\"1.3\" fill=\"currentColor\" />\n </svg>\n );\n}\n\n// ── EffectCardList ───────────────────────────────────────────────────\n// Batteries-included list wrapper that coordinates HTML5 drag-and-drop across\n// a list of EffectCards. Consumer supplies items + a renderItem callback;\n// onReorder fires with (sourceId, targetId, position) on drop. Mirrors the\n// LayerStack pattern from speech-balloons Lab.tsx:630-707.\n\n/** The minimum an item must carry to appear in an `<EffectCardList>`. */\nexport interface EffectCardListItem {\n id: string | number;\n}\n\n/** Props for `<EffectCardList>`. */\nexport interface EffectCardListProps<T extends EffectCardListItem> {\n items: ReadonlyArray<T>;\n renderItem: (\n item: T,\n helpers: {\n isExpanded: boolean;\n isDragging: boolean;\n toggleExpanded: () => void;\n cardProps: Pick<\n EffectCardProps,\n | 'expanded'\n | 'dragging'\n | 'draggable'\n | 'onToggleExpanded'\n | 'onPressHandle'\n | 'onReleaseHandle'\n | 'onDragStart'\n | 'onDragEnd'\n | 'onDragOver'\n | 'onDragLeave'\n | 'onDrop'\n >;\n },\n ) => ReactNode;\n onReorder: (sourceId: T['id'], targetId: T['id'], position: 'before' | 'after') => void;\n /** Optional rendered when the list is empty. */\n empty?: ReactNode;\n /** IDs to render expanded on first mount. Defaults to all collapsed. */\n defaultExpandedIds?: ReadonlyArray<T['id']>;\n}\n\n/** A drag-reorderable list of `<EffectCard>`s. Owns the expanded and dragging\n * state and hands it back to `renderItem`, so the caller only supplies each\n * card's contents. */\nexport function EffectCardList<T extends EffectCardListItem>({\n items,\n renderItem,\n onReorder,\n empty,\n defaultExpandedIds,\n}: EffectCardListProps<T>) {\n const [expanded, setExpanded] = useState<Set<T['id']>>(() => new Set(defaultExpandedIds ?? []));\n const [draggingId, setDraggingId] = useState<T['id'] | null>(null);\n const [pressedId, setPressedId] = useState<T['id'] | null>(null);\n const [dropHint, setDropHint] = useState<{ id: T['id']; position: 'before' | 'after' } | null>(\n null,\n );\n\n if (items.length === 0) {\n return <>{empty ?? <div className={s.cardListEmpty}>No items.</div>}</>;\n }\n\n return (\n <div className={s.cardList}>\n {items.map((item) => {\n const isExpanded = expanded.has(item.id);\n const isDragging = draggingId === item.id;\n const isPressed = pressedId === item.id;\n const showHintBefore =\n dropHint && dropHint.id === item.id && dropHint.position === 'before';\n const showHintAfter = dropHint && dropHint.id === item.id && dropHint.position === 'after';\n\n const toggleExpanded = () =>\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(item.id)) next.delete(item.id);\n else next.add(item.id);\n return next;\n });\n\n return (\n <div key={String(item.id)} className={s.cardListWrap}>\n {showHintBefore && <div className={s.cardDropHint} />}\n {renderItem(item, {\n isExpanded,\n isDragging,\n toggleExpanded,\n cardProps: {\n expanded: isExpanded,\n dragging: isDragging,\n draggable: isPressed,\n onToggleExpanded: toggleExpanded,\n onPressHandle: () => setPressedId(item.id),\n onReleaseHandle: () => setPressedId(null),\n onDragStart: (e) => {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(item.id));\n setDraggingId(item.id);\n },\n onDragEnd: () => {\n setDraggingId(null);\n setPressedId(null);\n setDropHint(null);\n },\n onDragOver: (e) => {\n if (draggingId === null || draggingId === item.id) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const position: 'before' | 'after' =\n e.clientY < rect.top + rect.height / 2 ? 'before' : 'after';\n setDropHint({ id: item.id, position });\n },\n onDragLeave: () => {\n setDropHint((h) => (h && h.id === item.id ? null : h));\n },\n onDrop: (e) => {\n e.preventDefault();\n if (draggingId === null || draggingId === item.id) {\n setDropHint(null);\n return;\n }\n const rect = (e.currentTarget as HTMLDivElement).getBoundingClientRect();\n const position: 'before' | 'after' =\n e.clientY < rect.top + rect.height / 2 ? 'before' : 'after';\n onReorder(draggingId, item.id, position);\n setDropHint(null);\n },\n },\n })}\n {showHintAfter && <div className={s.cardDropHint} />}\n </div>\n );\n })}\n </div>\n );\n}\n","import { type ReactNode, useId, useState } from 'react';\nimport { DisclosureRow } from '../Disclosure';\nimport s from './Properties.module.css';\nimport {\n type PropertyListPack,\n type PropertyMetricProps,\n propertyMetricClass,\n} from './PropertyPanel';\n\n/** Props for `<PropertyGroup>`. */\nexport interface PropertyGroupProps extends PropertyMetricProps {\n /** Title rendered between two rules at the top of the group. */\n title: ReactNode;\n /** When true the group renders nothing — useful for conditional sections. */\n hidden?: boolean;\n /**\n * Give the title a twisty that folds the rows away. Implied by any of the\n * three props below, so it is only needed for a group that starts open and\n * keeps its own state.\n */\n collapsible?: boolean;\n /** Start folded. Read once; the group owns the state from then on. */\n defaultCollapsed?: boolean;\n /**\n * Folded or not, as the consumer holds it. Given, the group keeps no state\n * of its own and every toggle arrives at `onCollapsedChange` instead — which\n * is what a panel that remembers its sections past a reload needs.\n */\n collapsed?: boolean;\n onCollapsedChange?: (next: boolean) => void;\n children: ReactNode;\n className?: string;\n /** How rows pack into the 2-column grid — see `<PropertyList pack>`. */\n pack?: PropertyListPack;\n}\n\n/**\n * Visually-bordered group inside a PropertyList. Use to scope a set of\n * related rows under a heading (e.g. \"Aqua\", \"Bevel\", \"Dome\" sections\n * inside a fill effect's controls).\n *\n * A collapsible group keeps its rows mounted and hides them, so a control's\n * local state survives being folded away.\n */\nexport function PropertyGroup({\n title,\n hidden,\n collapsible,\n defaultCollapsed,\n collapsed,\n onCollapsedChange,\n children,\n className,\n pack = 'auto-color',\n density,\n align,\n}: PropertyGroupProps) {\n const bodyId = useId();\n const [own, setOwn] = useState(defaultCollapsed ?? false);\n if (hidden) return null;\n\n const folds =\n collapsible ??\n (defaultCollapsed !== undefined || collapsed !== undefined || onCollapsedChange !== undefined);\n const folded = folds && (collapsed ?? own);\n const toggle = () => {\n if (collapsed === undefined) setOwn(!folded);\n onCollapsedChange?.(!folded);\n };\n\n const base = `${s.group}${pack === 'pairs' ? ` ${s.groupPairs}` : pack === 'one-up' ? ` ${s.groupOneUp}` : ''}`;\n const cls = propertyMetricClass(base, { density, align }, className);\n const heading = (\n <h3 className={s.groupTitle}>\n <hr />\n <span>{title}</span>\n <hr />\n </h3>\n );\n return (\n <div className={cls}>\n {folds ? (\n <DisclosureRow\n className={s.groupHead}\n open={!folded}\n onToggle={toggle}\n label={typeof title === 'string' ? title : 'this section'}\n controls={bodyId}\n >\n {heading}\n </DisclosureRow>\n ) : (\n heading\n )}\n <div id={bodyId} className={s.groupBody} hidden={folded}>\n {children}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"RangeSlider-DefoAOsh.js","names":[],"sources":["../../src/components/RangeSlider/RangeSlider.module.css","../../src/components/RangeSlider/RangeSlider.tsx"],"sourcesContent":[".slider {\n display: flex;\n flex-direction: column;\n width: 100%;\n gap: 4px;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.slider[data-orientation=\"vertical\"] {\n flex-direction: row;\n align-items: stretch;\n width: 28px;\n height: 160px;\n}\n\n.head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\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}\n\n.output {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n font-variant-numeric: tabular-nums;\n}\n\n.track {\n position: relative;\n height: 6px;\n min-width: 80px;\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n}\n\n.slider[data-orientation=\"vertical\"] .track {\n width: 6px;\n height: 100%;\n}\n\n.fill {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n background: var(--wzl-accent-strong);\n border-radius: var(--wzl-radius-sm);\n}\n\n.thumb {\n width: 14px;\n height: 14px;\n background: var(--wzl-fg-muted);\n border: 1px solid var(--wzl-border-strong);\n border-radius: 50%;\n cursor: ew-resize;\n outline: none;\n transition: box-shadow var(--wzl-motion-fast);\n}\n\n.slider[data-orientation=\"vertical\"] .thumb {\n cursor: ns-resize;\n}\n\n.thumb[data-focus-visible] {\n box-shadow: 0 0 0 2px var(--wzl-focus-ring);\n}\n\n.thumb[data-dragging] {\n background: var(--wzl-accent-strong);\n}\n\n.slider[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n","import type { ReactNode } from 'react';\nimport {\n Slider as RACSlider,\n SliderOutput,\n SliderTrack,\n SliderThumb,\n Label,\n type SliderProps as RACSliderProps,\n} from 'react-aria-components';\nimport s from './RangeSlider.module.css';\n\n/** Props for {@link RangeSlider}, on top of React Aria's `Slider` props. */\nexport type RangeSliderProps = Omit<RACSliderProps, 'children' | 'className'> & {\n label?: ReactNode;\n /** Show the current value next to the label. Defaults to true when a\n * label is supplied. */\n showOutput?: boolean;\n /** Format the rendered value(s). Useful for units (`v => `${v}%``). */\n formatOutput?: (v: number | number[]) => ReactNode;\n className?: string;\n};\n\n/**\n * Linear range slider on React Aria. Single-value by default; pass an array\n * to `value` / `defaultValue` for multi-thumb. Distinct from `Slider`, which\n * is fully controlled and gives each thumb its own bounds and readout.\n */\nexport function RangeSlider(props: RangeSliderProps) {\n const { label, showOutput, formatOutput, className, ...rest } = props;\n const wantOutput = showOutput ?? label !== undefined;\n return (\n <RACSlider {...rest} className={[s.slider, className].filter(Boolean).join(' ')}>\n {(label !== undefined || wantOutput) && (\n <div className={s.head}>\n {label !== undefined && <Label className={s.label}>{label}</Label>}\n {wantOutput && (\n <SliderOutput className={s.output}>\n {({ state }) => {\n const v = state.values.length === 1 ? state.values[0] : state.values;\n return formatOutput ? formatOutput(v) : Array.isArray(v) ? v.join(' – ') : String(v);\n }}\n </SliderOutput>\n )}\n </div>\n )}\n <SliderTrack className={s.track}>\n {({ state }) => {\n // Single-thumb: fill from start to thumb. Multi-thumb: fill\n // between adjacent thumbs (simplest = between first and last).\n const fillStart = state.values.length > 1 ? state.getThumbPercent(0) * 100 : 0;\n const fillEnd = state.getThumbPercent(state.values.length - 1) * 100;\n return (\n <>\n <div\n className={s.fill}\n style={{ left: `${fillStart}%`, right: `${100 - fillEnd}%` }}\n />\n {state.values.map((_, i) => (\n <SliderThumb key={i} index={i} className={s.thumb} />\n ))}\n </>\n );\n }}\n </SliderTrack>\n </RACSlider>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AC2BA,SAAgB,EAAY,GAAyB;CACnD,IAAM,EAAE,UAAO,eAAY,iBAAc,cAAW,GAAG,MAAS,GAC1D,IAAa,KAAc,MAAU,KAAA;AAC3C,QACE,kBAAC,GAAD;EAAW,GAAI;EAAM,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA/E,EACI,MAAU,KAAA,KAAa,MACvB,kBAAC,OAAD;GAAK,WAAW,EAAE;aAAlB,CACG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAE;cAAQ;IAAc,CAAA,EACjE,KACC,kBAAC,GAAD;IAAc,WAAW,EAAE;eACvB,EAAE,eAAY;KACd,IAAM,IAAI,EAAM,OAAO,WAAW,IAAI,EAAM,OAAO,KAAK,EAAM;AAC9D,YAAO,IAAe,EAAa,EAAE,GAAG,MAAM,QAAQ,EAAE,GAAG,EAAE,KAAK,MAAM,GAAG,OAAO,EAAE;;IAEzE,CAAA,CAEb;MAER,kBAAC,GAAD;GAAa,WAAW,EAAE;cACtB,EAAE,eAAY;IAGd,IAAM,IAAY,EAAM,OAAO,SAAS,IAAI,EAAM,gBAAgB,EAAE,GAAG,MAAM,GACvE,IAAU,EAAM,gBAAgB,EAAM,OAAO,SAAS,EAAE,GAAG;AACjE,WACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;KACE,WAAW,EAAE;KACb,OAAO;MAAE,MAAM,GAAG,EAAU;MAAI,OAAO,GAAG,MAAM,EAAQ;MAAI;KAC5D,CAAA,EACD,EAAM,OAAO,KAAK,GAAG,MACpB,kBAAC,GAAD;KAAqB,OAAO;KAAG,WAAW,EAAE;KAAS,EAAnC,EAAmC,CACrD,CACD,EAAA,CAAA;;GAGK,CAAA,CACJ"}
@@ -1,121 +0,0 @@
1
- import { n as e } from "./Field-BUroh92b.js";
2
- import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
- import { Children as r, isValidElement as i } from "react";
4
- import { Button as a, FieldError as o, Label as s, ListBox as c, ListBoxItem as l, Popover as u, Select as d, SelectValue as f, Text as p } from "react-aria-components";
5
- var m = {
6
- field: "_field_1wahg_1",
7
- trigger: "_trigger_1wahg_9",
8
- value: "_value_1wahg_41",
9
- chevron: "_chevron_1wahg_54",
10
- popover: "_popover_1wahg_61",
11
- listbox: "_listbox_1wahg_67",
12
- option: "_option_1wahg_80",
13
- check: "_check_1wahg_105",
14
- fit: "_fit_1wahg_118",
15
- sizer: "_sizer_1wahg_129"
16
- };
17
- //#endregion
18
- //#region src/components/Select/Select.tsx
19
- function h(r) {
20
- let { label: i, description: l, errorMessage: h, placeholder: v, options: b, children: x, selectedKey: S, defaultSelectedKey: C, onSelectionChange: w, width: T = "fill", className: E, ...D } = r;
21
- return /* @__PURE__ */ n(d, {
22
- ...D,
23
- selectedKey: S,
24
- defaultSelectedKey: C,
25
- onSelectionChange: w ? (e) => w(e) : void 0,
26
- className: [
27
- m.field,
28
- T === "fit" && m.fit,
29
- e.root,
30
- E
31
- ].filter(Boolean).join(" "),
32
- children: [
33
- i !== void 0 && /* @__PURE__ */ t(s, {
34
- className: e.label,
35
- children: i
36
- }),
37
- /* @__PURE__ */ n(a, {
38
- className: m.trigger,
39
- children: [
40
- /* @__PURE__ */ t(f, {
41
- className: m.value,
42
- children: ({ defaultChildren: e, isPlaceholder: t }) => t ? v ?? e : e
43
- }),
44
- T === "fit" && /* @__PURE__ */ n("span", {
45
- className: m.sizer,
46
- "aria-hidden": "true",
47
- children: [v !== void 0 && /* @__PURE__ */ t("span", { children: v }), _(b, x).map((e, r) => /* @__PURE__ */ n("span", { children: [/* @__PURE__ */ t(g, {}), e] }, r))]
48
- }),
49
- /* @__PURE__ */ t("svg", {
50
- className: m.chevron,
51
- viewBox: "0 0 10 10",
52
- "aria-hidden": "true",
53
- children: /* @__PURE__ */ t("path", {
54
- d: "M2 4 L5 7 L8 4",
55
- fill: "none",
56
- stroke: "currentColor",
57
- strokeWidth: "1.5",
58
- strokeLinecap: "round",
59
- strokeLinejoin: "round"
60
- })
61
- })
62
- ]
63
- }),
64
- l !== void 0 && /* @__PURE__ */ t(p, {
65
- slot: "description",
66
- className: e.hint,
67
- children: l
68
- }),
69
- /* @__PURE__ */ t(o, {
70
- className: e.error,
71
- children: h
72
- }),
73
- /* @__PURE__ */ t(u, {
74
- className: m.popover,
75
- "data-weasel-overlay": "",
76
- children: /* @__PURE__ */ t(c, {
77
- className: m.listbox,
78
- children: b === void 0 ? x : b.map((e) => /* @__PURE__ */ t(y, {
79
- id: e.value,
80
- isDisabled: e.isDisabled,
81
- textValue: e.textValue,
82
- children: e.label
83
- }, String(e.value)))
84
- })
85
- })
86
- ]
87
- });
88
- }
89
- function g() {
90
- return /* @__PURE__ */ t("svg", {
91
- className: m.check,
92
- viewBox: "0 0 10 10",
93
- "aria-hidden": "true",
94
- children: /* @__PURE__ */ t("polyline", {
95
- points: "1.5,5 4,7.5 8.5,3",
96
- fill: "none",
97
- stroke: "currentColor",
98
- strokeWidth: "1.6",
99
- strokeLinecap: "round",
100
- strokeLinejoin: "round"
101
- })
102
- });
103
- }
104
- function _(e, t) {
105
- return e === void 0 ? r.toArray(t).map((e) => i(e) ? e.props.children : e) : e.map((e) => e.label);
106
- }
107
- function v(e, t) {
108
- return t === void 0 ? typeof e == "string" || typeof e == "number" ? String(e) : void 0 : t;
109
- }
110
- function y({ children: e, className: r, textValue: i, ...a }) {
111
- return /* @__PURE__ */ n(l, {
112
- ...a,
113
- textValue: v(e, i),
114
- className: [m.option, r].filter(Boolean).join(" "),
115
- children: [/* @__PURE__ */ t(g, {}), e]
116
- });
117
- }
118
- //#endregion
119
- export { y as n, h as t };
120
-
121
- //# sourceMappingURL=Select-DGafwTF0.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Select-DGafwTF0.js","names":[],"sources":["../../src/components/Select/Select.module.css","../../src/components/Select/Select.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: 4px;\n}\n\n.trigger {\n display: flex;\n width: 100%;\n align-items: center;\n gap: 6px;\n height: var(--wzl-control-h);\n padding: 0 6px 0 10px;\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 cursor: pointer;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.trigger[data-focus-visible] {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .trigger {\n border-color: var(--wzl-danger);\n}\n\n.trigger[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.value {\n flex: 1;\n min-width: 0;\n text-align: left;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.value[data-placeholder] {\n color: var(--wzl-fg-subtle);\n}\n\n.chevron {\n width: 10px;\n height: 10px;\n color: var(--wzl-fg-muted);\n flex: 0 0 auto;\n}\n\n.popover {\n min-width: var(--trigger-width);\n max-height: inherit;\n outline: none;\n}\n\n.listbox {\n background: var(--wzl-surface-raised);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);\n padding: 4px;\n max-height: 280px;\n overflow: auto;\n outline: none;\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 8px;\n border-radius: var(--wzl-radius-sm);\n cursor: pointer;\n color: var(--wzl-fg);\n outline: none;\n}\n\n.option[data-focused] {\n background: var(--wzl-surface-hover);\n}\n\n.option[data-selected] {\n color: var(--wzl-fg);\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.option[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.check {\n width: 10px;\n height: 10px;\n flex: 0 0 auto;\n opacity: 0;\n}\n\n.option[data-selected] .check {\n opacity: 1;\n}\n\n/* `width='fit'`: a hidden stack of every option label shares the value's grid\n cell, so the trigger sizes to its widest option and never resizes on select. */\n.fit {\n width: auto;\n max-width: 100%;\n}\n\n.fit .trigger {\n display: grid;\n grid-template-columns: auto auto;\n}\n\n.fit .value,\n.fit .sizer {\n grid-area: 1 / 1;\n}\n\n.fit .chevron {\n grid-area: 1 / 2;\n}\n\n.sizer {\n display: grid;\n height: 0;\n overflow: hidden;\n visibility: hidden;\n white-space: nowrap;\n}\n\n.sizer > * {\n grid-area: 1 / 1;\n}\n","import { Children, isValidElement, type ReactNode } from 'react';\nimport {\n Select as RACSelect,\n Label,\n Button as RACButton,\n SelectValue,\n Popover as RACPopover,\n ListBox as RACListBox,\n ListBoxItem as RACListBoxItem,\n Text,\n FieldError,\n type SelectProps as RACSelectProps,\n type ListBoxItemProps as RACListBoxItemProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './Select.module.css';\n\n/** One option in a {@link Select}'s `options` list. */\nexport type SelectOption = {\n value: string;\n label: ReactNode;\n isDisabled?: boolean;\n /**\n * Plain-text form of `label`, for type-to-select and screen readers.\n * Only needed when `label` isn't a bare string — a label built from\n * elements has no text React Aria can read off it. A string label\n * supplies this itself.\n */\n textValue?: string;\n};\n\ntype Key = string | number;\n\n/**\n * Props for {@link Select}, on top of React Aria's `Select` props, with the\n * selection key narrowed to the option value type.\n */\nexport type SelectProps<T extends Key = string> = Omit<RACSelectProps<object>, 'children' | 'className' | 'selectedKey' | 'defaultSelectedKey' | 'onSelectionChange'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n placeholder?: string;\n /** Either pass `options` for a quick render, or `children` for full control. */\n options?: ReadonlyArray<SelectOption & { value: T }>;\n children?: ReactNode;\n selectedKey?: T | null;\n defaultSelectedKey?: T;\n onSelectionChange?: (key: T) => void;\n /**\n * `'fill'` (the default) takes the width of whatever row the select sits in.\n * `'fit'` sizes the trigger to its widest option, so it neither swallows a\n * toolbar's slack nor changes width as the selection moves.\n */\n width?: 'fill' | 'fit';\n className?: string;\n};\n\n/**\n * Form select wrapping React Aria's Select. Pass either `options` for a\n * quick declarative render or `children` of `<SelectItem>` for control over\n * each row.\n *\n * The selection key type is parameterized so consumers with a string-literal\n * union for `value` (e.g. `'r' | 'g' | 'b'`) get a typed `onSelectionChange`.\n */\nexport function Select<T extends Key = string>(props: SelectProps<T>) {\n const {\n label,\n description,\n errorMessage,\n placeholder,\n options,\n children,\n selectedKey,\n defaultSelectedKey,\n onSelectionChange,\n width = 'fill',\n className,\n ...rest\n } = props;\n\n return (\n <RACSelect\n {...rest}\n selectedKey={selectedKey}\n defaultSelectedKey={defaultSelectedKey}\n onSelectionChange={onSelectionChange ? (k) => onSelectionChange(k as T) : undefined}\n className={[s.field, width === 'fit' && s.fit, fieldClasses.root, className]\n .filter(Boolean)\n .join(' ')}\n >\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <RACButton className={s.trigger}>\n <SelectValue className={s.value}>\n {({ defaultChildren, isPlaceholder }) =>\n isPlaceholder ? (placeholder ?? defaultChildren) : defaultChildren\n }\n </SelectValue>\n {width === 'fit' && (\n <span className={s.sizer} aria-hidden=\"true\">\n {placeholder !== undefined && <span>{placeholder}</span>}\n {optionLabels(options, children).map((l, i) => (\n <span key={i}>\n <CheckMark />\n {l}\n </span>\n ))}\n </span>\n )}\n <svg className={s.chevron} viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M2 4 L5 7 L8 4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </RACButton>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n {/* `data-weasel-overlay` marks DOM that belongs to this control but\n renders in a portal, outside the subtree the trigger sits in. Any\n consumer reasoning about \"did focus leave my component?\" via\n `closest()` gets the wrong answer without it — a text editor whose\n font menu lives here would end its session the moment the menu is\n clicked. */}\n <RACPopover className={s.popover} data-weasel-overlay=\"\">\n <RACListBox className={s.listbox}>\n {options !== undefined\n ? options.map((o) => (\n <SelectItem\n key={String(o.value)}\n id={o.value}\n isDisabled={o.isDisabled}\n textValue={o.textValue}\n >\n {o.label}\n </SelectItem>\n ))\n : children}\n </RACListBox>\n </RACPopover>\n </RACSelect>\n );\n}\n\n/* A selected row's mark travels into the trigger with its label, so a\n `width='fit'` sizer has to allow for it too. */\nfunction CheckMark() {\n return (\n <svg className={s.check} viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <polyline points=\"1.5,5 4,7.5 8.5,3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\n/**\n * The labels a `width='fit'` trigger measures itself against. In the children\n * form the label sits one element deep, inside a `SelectItem`.\n */\nfunction optionLabels(\n options: ReadonlyArray<SelectOption> | undefined,\n children: ReactNode,\n): ReactNode[] {\n if (options !== undefined) return options.map((o) => o.label);\n return Children.toArray(children).map((c) =>\n isValidElement<{ children?: ReactNode }>(c) ? c.props.children : c,\n );\n}\n\n/** Props for {@link SelectItem}, on top of React Aria's `ListBoxItem` props. */\nexport type SelectItemProps = Omit<RACListBoxItemProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * Every row renders a check mark beside its label, so from React Aria's\n * side the children are never plain text and it can't derive the string\n * type-to-select and screen readers need — it warns once per row. A string\n * label already *is* that string, so derive it rather than making every\n * call site restate it; anything richer has to say what it reads as.\n */\nfunction textValueOf(children: ReactNode, explicit: string | undefined): string | undefined {\n if (explicit !== undefined) return explicit;\n return typeof children === 'string' || typeof children === 'number'\n ? String(children)\n : undefined;\n}\n\n/**\n * One row in a {@link Select}'s list, with a leading check mark when\n * selected. A string child supplies its own `textValue`; anything richer must\n * pass one so type-to-select and screen readers have something to read.\n */\nexport function SelectItem({ children, className, textValue, ...rest }: SelectItemProps) {\n return (\n <RACListBoxItem\n {...rest}\n textValue={textValueOf(children, textValue)}\n className={[s.option, className].filter(Boolean).join(' ')}\n >\n <CheckMark />\n {children}\n </RACListBoxItem>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;ACkEA,SAAgB,EAA+B,GAAuB;CACpE,IAAM,EACJ,UACA,gBACA,iBACA,gBACA,YACA,aACA,gBACA,uBACA,sBACA,WAAQ,QACR,cACA,GAAG,MACD;AAEJ,QACE,kBAAC,GAAD;EACE,GAAI;EACS;EACO;EACpB,mBAAmB,KAAqB,MAAM,EAAkB,EAAO,GAAG,KAAA;EAC1E,WAAW;GAAC,EAAE;GAAO,MAAU,SAAS,EAAE;GAAK,EAAa;GAAM;GAAU,CACzE,OAAO,QAAQ,CACf,KAAK,IAAI;YAPd;GASG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,GAAD;IAAW,WAAW,EAAE;cAAxB;KACE,kBAAC,GAAD;MAAa,WAAW,EAAE;iBACtB,EAAE,oBAAiB,uBACnB,IAAiB,KAAe,IAAmB;MAEzC,CAAA;KACb,MAAU,SACT,kBAAC,QAAD;MAAM,WAAW,EAAE;MAAO,eAAY;gBAAtC,CACG,MAAgB,KAAA,KAAa,kBAAC,QAAD,EAAA,UAAO,GAAmB,CAAA,EACvD,EAAa,GAAS,EAAS,CAAC,KAAK,GAAG,MACvC,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAa,CAAA,EACZ,EACI,EAAA,EAHI,EAGJ,CACP,CACG;;KAET,kBAAC,OAAD;MAAK,WAAW,EAAE;MAAS,SAAQ;MAAY,eAAY;gBACzD,kBAAC,QAAD;OAAM,GAAE;OAAiB,MAAK;OAAO,QAAO;OAAe,aAAY;OAAM,eAAc;OAAQ,gBAAe;OAAU,CAAA;MACxH,CAAA;KACI;;GACX,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GAOtE,kBAAC,GAAD;IAAY,WAAW,EAAE;IAAS,uBAAoB;cACpD,kBAAC,GAAD;KAAY,WAAW,EAAE;eACtB,MAAY,KAAA,IAWT,IAVA,EAAQ,KAAK,MACX,kBAAC,GAAD;MAEE,IAAI,EAAE;MACN,YAAY,EAAE;MACd,WAAW,EAAE;gBAEZ,EAAE;MACQ,EANN,OAAO,EAAE,MAAM,CAMT,CACb;KAEK,CAAA;IACF,CAAA;GACH;;;AAMhB,SAAS,IAAY;AACnB,QACE,kBAAC,OAAD;EAAK,WAAW,EAAE;EAAO,SAAQ;EAAY,eAAY;YACvD,kBAAC,YAAD;GAAU,QAAO;GAAoB,MAAK;GAAO,QAAO;GAAe,aAAY;GAAM,eAAc;GAAQ,gBAAe;GAAU,CAAA;EACpI,CAAA;;AAQV,SAAS,EACP,GACA,GACa;AAEb,QADI,MAAY,KAAA,IACT,EAAS,QAAQ,EAAS,CAAC,KAAK,MACrC,EAAyC,EAAE,GAAG,EAAE,MAAM,WAAW,EAClE,GAHiC,EAAQ,KAAK,MAAM,EAAE,MAAM;;AAmB/D,SAAS,EAAY,GAAqB,GAAkD;AAE1F,QADI,MAAa,KAAA,IACV,OAAO,KAAa,YAAY,OAAO,KAAa,WACvD,OAAO,EAAS,GAChB,KAAA,IAH+B;;AAWrC,SAAgB,EAAW,EAAE,aAAU,cAAW,cAAW,GAAG,KAAyB;AACvF,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,WAAW,EAAY,GAAU,EAAU;EAC3C,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAH5D,CAKE,kBAAC,GAAD,EAAa,CAAA,EACZ,EACc"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"SelectionPanel-BwBevj5F.js","names":[],"sources":["../../src/components/SelectionPanel/model.ts","../../src/components/SelectionPanel/SelectionPanel.module.css","../../src/components/SelectionPanel/SelectionPanel.tsx"],"sourcesContent":["// Pure selection→panel derivations for SelectionPanel. Kept free of\n// React so intersection/aggregation semantics are unit-testable.\n//\n// Path convention (see core `NodePropertiesEntry`): a leaf's OWN KEY in\n// the schema is its node path — a dotted path of any depth rooted at\n// `pose` or `data` (`pose.x`, `data.fill`, `data.style.fontSize`). Group\n// keys are organizational only.\n\nimport {\n MIXED,\n type Mixed,\n type NodePropertiesEntry,\n type NodeRoutingEntry,\n type SceneNode,\n type ToolPrefGroup,\n type ToolPrefLeaf,\n} from '@weasel-js/core';\n\n// Re-exported under its existing public name here: the kit-wide \"these\n// values disagree\" sentinel (`@weasel-js/core`'s `MIXED`) also covers\n// multi-node aggregation, so this panel doesn't get its own symbol.\nexport { MIXED };\nexport type { Mixed };\n\nexport type AnyNode = SceneNode<unknown, string, unknown>;\n\n/**\n * One editable property in a {@link SelectionPanel}, paired with the dotted\n * node path it reads and writes.\n */\nexport interface PanelLeaf {\n /** Dotted node path — the leaf's key in the schema. */\n path: string;\n leaf: ToolPrefLeaf;\n}\n\n/**\n * One labeled row of a panel section. Holds several leaves when the schema\n * paired them.\n */\nexport interface PanelRow {\n label: string;\n leaves: PanelLeaf[];\n}\n\n/**\n * A titled block of rows, derived from one top-level group of a properties\n * schema. `key` is that group's schema key.\n */\nexport interface PanelSection {\n key: string;\n name: string;\n rows: PanelRow[];\n}\n\n/** Derive a node's kind: containers are `'group'`; leaves classify their\n * `data` through the routing entries (first match wins, `'unknown'`\n * otherwise) — same semantics as core's `NodeRouting.classify`. */\nexport function classifyKind(\n node: AnyNode,\n routing: readonly NodeRoutingEntry[],\n): string {\n if (node.kind === 'container') return 'group';\n for (const entry of routing) {\n if (entry.matches(node.data)) return entry.name;\n }\n return 'unknown';\n}\n\nfunction isGroup(n: ToolPrefLeaf | ToolPrefGroup): n is ToolPrefGroup {\n return !('kind' in n);\n}\n\n/** Flatten a schema to sections of leaves. Nested groups fold into their\n * top-level section; top-level leaves get an untitled leading section. */\nfunction flatten(schema: ToolPrefGroup): PanelSection[] {\n const sections: PanelSection[] = [];\n const untitled: PanelLeaf[] = [];\n\n const collect = (group: ToolPrefGroup, into: PanelLeaf[]): void => {\n for (const [key, child] of Object.entries(group.children)) {\n if (isGroup(child)) collect(child, into);\n else into.push({ path: key, leaf: child });\n }\n };\n\n for (const [key, child] of Object.entries(schema.children)) {\n if (isGroup(child)) {\n const leaves: PanelLeaf[] = [];\n collect(child, leaves);\n sections.push({ key, name: child.name, rows: pairRows(leaves) });\n } else {\n untitled.push({ path: key, leaf: child });\n }\n }\n if (untitled.length > 0) {\n sections.unshift({ key: '', name: '', rows: pairRows(untitled) });\n }\n return sections;\n}\n\n/** Merge consecutive leaves sharing a `pair` id into one labeled row. */\nfunction pairRows(leaves: readonly PanelLeaf[]): PanelRow[] {\n const rows: PanelRow[] = [];\n for (const item of leaves) {\n const pair = item.leaf.pair;\n const prev = rows[rows.length - 1];\n if (pair !== undefined && prev !== undefined && prev.label === pair) {\n prev.leaves.push(item);\n } else {\n rows.push({ label: pair ?? item.leaf.name, leaves: [item] });\n }\n }\n return rows;\n}\n\n/**\n * The schema the panel shows for a set of kinds: one kind → its full\n * schema; several → the intersection by (path, leaf kind), shaped by the\n * first kind's section/row layout. Kinds without a registered schema\n * contribute nothing, so their presence collapses the intersection.\n */\nexport function effectiveSections(\n kinds: readonly string[],\n entries: readonly NodePropertiesEntry[],\n): PanelSection[] {\n const uniq = [...new Set(kinds)];\n if (uniq.length === 0) return [];\n const byName = new Map(entries.map((e) => [e.name, e]));\n const schemas = uniq.map((k) => byName.get(k)?.schema);\n if (schemas.some((s) => s === undefined)) return [];\n const [first, ...rest] = schemas as ToolPrefGroup[];\n\n const flatFirst = flatten(first);\n if (rest.length === 0) return flatFirst;\n\n // '\\0' delimiter: path and kind strings are unconstrained, so a\n // printable separator could collide with real content.\n const leafKey = (l: PanelLeaf): string => `${l.path}\\0${l.leaf.kind}`;\n const restKeys = rest.map(\n (schema) =>\n new Set(\n flatten(schema)\n .flatMap((s) => s.rows)\n .flatMap((r) => r.leaves)\n .map(leafKey),\n ),\n );\n const keep = (l: PanelLeaf): boolean =>\n restKeys.every((set) => set.has(leafKey(l)));\n\n return flatFirst\n .map((section) => ({\n ...section,\n rows: section.rows\n .map((row) => ({ ...row, leaves: row.leaves.filter(keep) }))\n .filter((row) => row.leaves.length > 0),\n }))\n .filter((section) => section.rows.length > 0);\n}\n\n/** Read a node value at a dotted path of any depth (`pose.x`,\n * `data.style.fontSize`, `data.style.fill.color`). Returns `undefined` if\n * the path is dotless (mirrors `commit`'s no-op on a malformed schema\n * key) or if any intermediate segment is missing or not an object. */\nexport function nodeValueAt(node: AnyNode, path: string): unknown {\n const segments = path.split('.');\n if (segments.length < 2) return undefined;\n const head = segments[0];\n let cursor: unknown = head === 'pose' ? node.pose : head === 'data' ? node.data : undefined;\n for (let i = 1; i < segments.length; i++) {\n if (cursor == null || typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[segments[i]];\n }\n return cursor;\n}\n\n/** Immutably set `value` at a dotted path within `root`, cloning each level\n * on the way down so React sees new object identities. Arrays stay arrays\n * (an array intermediate is shallow-copied, not flattened into `{0: ...}`)\n * — this is the write-side twin of `nodeValueAt`, so a schema author who\n * gets a working read gets a working write. */\nexport function setAtPath(root: object, segments: readonly string[], value: unknown): object {\n const [head, ...rest] = segments;\n if (rest.length === 0) {\n return Array.isArray(root)\n ? Object.assign([...root], { [head]: value })\n : { ...root, [head]: value };\n }\n const child = (root as Record<string, unknown>)[head];\n const childObj = Array.isArray(child)\n ? [...child]\n : child != null && typeof child === 'object'\n ? { ...child }\n : {};\n const nested = setAtPath(childObj, rest, value);\n return Array.isArray(root)\n ? Object.assign([...root], { [head]: nested })\n : { ...root, [head]: nested };\n}\n\n/** Aggregate a path across nodes: the shared value, or `MIXED`. */\nexport function aggregateValue(\n nodes: readonly AnyNode[],\n path: string,\n): unknown | Mixed {\n let value: unknown;\n let first = true;\n for (const node of nodes) {\n const v = nodeValueAt(node, path);\n if (first) {\n value = v;\n first = false;\n } else if (!Object.is(v, value)) {\n return MIXED;\n }\n }\n return value;\n}\n\n/** `'rect ×2 · text'` — kinds in first-seen order with counts. */\nexport function kindBreakdown(kinds: readonly string[]): string {\n const counts = new Map<string, number>();\n for (const k of kinds) counts.set(k, (counts.get(k) ?? 0) + 1);\n return [...counts.entries()]\n .map(([k, n]) => (n > 1 ? `${k} ×${n}` : k))\n .join(' · ');\n}\n",".root {\n display: flex;\n flex-direction: column;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg);\n}\n\n.header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 0;\n border-block-end: 1px solid var(--wzl-border);\n}\n\n.kind {\n font-weight: var(--wzl-font-weight-bold);\n font-size: var(--wzl-font-size);\n}\n\n.breakdown {\n font-size: var(--wzl-font-size-sm);\n opacity: 0.6;\n}\n\n.section {\n padding: 6px 0 8px;\n border-block-end: 1px solid var(--wzl-border);\n}\n\n.section:last-child {\n border-block-end: none;\n}\n\n.sectionTitle {\n margin: 0 0 6px;\n font-size: var(--wzl-font-size-xs);\n font-weight: var(--wzl-font-weight-bold);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.55;\n}\n\n.row {\n display: grid;\n grid-template-columns: 64px 1fr;\n gap: 6px;\n align-items: center;\n margin-block-end: 5px;\n}\n\n.rowLabel {\n opacity: 0.75;\n}\n\n.rowControls {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-inline-size: 0;\n}\n\n/* Glyph plus its control, held together so the pair row wraps between\n fields rather than between a field and the icon naming it. */\n.namedField {\n display: flex;\n align-items: center;\n gap: 4px;\n min-inline-size: 0;\n}\n\n.namedField > svg {\n flex: none;\n opacity: 0.75;\n}\n\n.blockRow {\n margin-block-end: 5px;\n}\n\n/* Fields paired into one row split it evenly, the way Position's x/y do. */\n.rowControls > .namedField {\n flex: 1 1 0;\n}\n\n/* Except around a segmented bar, which is sized by its segments: an equal\n share of the row is wider than the buttons want to be. */\n.rowControls > .namedField:has(> .toggle) {\n flex: 0 1 auto;\n}\n\n.rowControls > * {\n flex: 1;\n min-inline-size: 0;\n}\n\n/* Bars sharing a row split the leftover between them, so the gap stays fixed\n and the width lands on the segments. A bar with the row to itself is sized\n by its segments instead — a third of 240px is a plate, not a button. */\n.rowControls > .toggle {\n flex: 1 1 auto;\n}\n\n.rowControls > .toggle:only-child {\n flex: 0 1 auto;\n}\n\n/* A flag bar is sized by its glyphs rather than by a share of the row: an\n even split would fling one-letter segments to the margins. */\n.rowControls > .flagToggle {\n flex: 0 0 auto;\n}\n\n.number :global(input),\n.select {\n font-size: var(--wzl-font-size-sm);\n}\n\n.unitSuffix {\n flex: none;\n font-size: var(--wzl-font-size-sm);\n opacity: 0.6;\n}\n\n.mixedSwitch {\n opacity: 0.55;\n}\n\n.unrenderable {\n font-size: var(--wzl-font-size-sm);\n opacity: 0.5;\n font-style: italic;\n}\n\n/* A `block` leaf that renders its own rows — an object leaf's fields. */\n.objectLeaf {\n margin-block: 8px 4px;\n}\n\n.objectLeaf .sectionTitle {\n margin-block: 7px 5px;\n}\n\n/* Fields under a heading inside an object read as belonging to it. */\n.objectGroup {\n padding-inline-start: 8px;\n border-inline-start: 1px solid var(--wzl-border);\n margin-block-end: 4px;\n}\n\n/* `InlineRange` owns the track; the row only says how it shares the space. */\n.slider {\n flex: 1;\n}\n\n.sliderReadout {\n flex: none;\n min-inline-size: 3ch;\n text-align: end;\n font-size: var(--wzl-font-size-sm);\n opacity: 0.7;\n}\n","import { Fragment, useState, useSyncExternalStore, type ReactNode } from 'react';\nimport {\n asNodeId,\n isBuiltinToolPref,\n type FillStyle,\n type NodePropertiesEntry,\n type NodeRoutingEntry,\n type Scene,\n type SelectionApi,\n type ToolPrefBoolean,\n type ToolPrefColor,\n type ToolPrefEnum,\n type ToolPrefLeaf,\n type ToolPrefNumber,\n type ToolPrefPaint,\n type ToolPrefGroup,\n type ToolPrefObject,\n} from '@weasel-js/core';\nimport { ColorField } from '../ColorField';\nimport { FontFamilySelect } from '../FontFamilySelect';\nimport { PaintInput } from '../PaintInput';\nimport { InlineRange } from '../InlineRange';\nimport { Input } from '../Input';\nimport { NumberField } from '../NumberField';\nimport { Select } from '../Select';\nimport { Switch } from '../Switch';\nimport { ToggleBar } from '../ToggleBar';\nimport { Icon } from '../../icons/Icon';\nimport { ICON_PATHS, type IconName } from '../../icons/paths';\nimport {\n MIXED,\n aggregateValue,\n classifyKind,\n effectiveSections,\n kindBreakdown,\n setAtPath,\n type AnyNode,\n type PanelLeaf,\n} from './model';\nimport s from './SelectionPanel.module.css';\n\n/** What a {@link PropertyRenderer} is given for the property it is rendering. */\nexport interface PropertyRenderContext {\n /** Dotted node path of the leaf (`pose.x`, `data.fill`). */\n path: string;\n /** The schema leaf. App renderers narrow it to their own kind shape. */\n pref: ToolPrefLeaf;\n /** Aggregated value across the selection; `undefined` when mixed or unset. */\n value: unknown;\n /** True when selected nodes disagree at this path. */\n mixed: boolean;\n /**\n * True when the nodes agree and what they agree on is nothing — the field\n * is absent, and whatever paints comes from a fallback further down.\n *\n * Distinct from `mixed`, and both leave `value` undefined. A control must\n * not substitute its schema default here: doing so shows a value the node\n * does not hold, and the next edit writes that invention back.\n */\n unset: boolean;\n /**\n * The fields of the object this leaf is a field of — `undefined` for a\n * top-level leaf, and for a field of an object the node does not hold.\n *\n * What a control needs when its own field doesn't carry the whole answer:\n * a stroke's dash lengths are multiples of the stroke's width, so reading\n * the array as a style takes both.\n */\n siblings?: Record<string, unknown>;\n /** Commit a value — fans out to every selected node in one undo step. */\n setValue: (value: unknown) => void;\n /**\n * The aggregated value at another node path — what `value` and `mixed` are\n * for this leaf's own path, for any path.\n *\n * A control whose subject spans more than one field needs it: a font picker\n * reporting which variant will actually paint has to read the node's weight\n * and style. Reading it off `value` is impossible — a leaf is handed one\n * field.\n */\n valueAt: (path: string) => { value: unknown; mixed: boolean };\n}\n\n/**\n * Renders the control for one property across the whole selection.\n * Returning `null` collapses the leaf.\n */\nexport type PropertyRenderer = (ctx: PropertyRenderContext) => ReactNode;\n\n/** Props for {@link SelectionPanel}. */\nexport interface SelectionPanelProps<TData, TLayer extends string, TPose> {\n /** The scene handle (`useScene`). The panel subscribes itself, so it\n * re-renders on scene mutations regardless of parent renders. */\n scene: Scene<TData, TLayer, TPose>;\n /** Selection handle (`useSelection`). Only `current` is read. */\n selection: Pick<SelectionApi, 'current'>;\n /** Properties-trait entries, e.g. core's `defaultNodeProperties` /\n * `inferredNodeProperties` (+ consumer extras). Memoize or hoist. */\n properties: readonly NodePropertiesEntry[];\n /** Routing-trait classifiers used to derive each node's kind — pass\n * the same list the canvas uses. Memoize or hoist. */\n routing: readonly NodeRoutingEntry[];\n /** Control overrides / app-defined kinds (PrefsForm-style). Keys are\n * leaf paths (`data.fill`, checked first) or leaf kinds (`color`).\n * A renderer returning `null` collapses its leaf (and the row, when\n * every leaf in it collapses). */\n renderers?: Record<string, PropertyRenderer>;\n /** Kind → header label. Default: capitalized kind name. */\n kindLabel?: (kind: string) => string;\n /** Rendered when the selection is empty. */\n emptyState?: ReactNode;\n className?: string;\n}\n\nconst defaultKindLabel = (kind: string): string =>\n kind.length === 0 ? kind : kind[0].toUpperCase() + kind.slice(1);\n\n/**\n * Pre-baked selection properties panel. Shows the selected nodes' kind\n * and the properties-trait schema for that kind; multi-selections show\n * the intersection of the kinds' schemas with per-field Mixed state.\n * Edits commit as one labeled `scene.batch` fan-out per gesture.\n */\nexport function SelectionPanel<TData, TLayer extends string, TPose>(\n props: SelectionPanelProps<TData, TLayer, TPose>,\n) {\n const {\n scene,\n selection,\n properties,\n routing,\n renderers,\n kindLabel = defaultKindLabel,\n emptyState = null,\n className,\n } = props;\n\n useSyncExternalStore(scene.subscribe, scene.getVersion, scene.getVersion);\n\n const nodes = selection.current\n .map((id) => scene.get(asNodeId(id)))\n .filter((n): n is NonNullable<typeof n> => n != null) as readonly AnyNode[];\n\n const kinds = nodes.map((n) => classifyKind(n, routing));\n const selectionKey = nodes.map((n) => n.id).join(',');\n const sections = effectiveSections(kinds, properties);\n\n if (nodes.length === 0) {\n return <div className={[s.root, className].filter(Boolean).join(' ')}>{emptyState}</div>;\n }\n\n const commit = (leaf: PanelLeaf, value: unknown): void => {\n const [head, ...rest] = leaf.path.split('.');\n if (rest.length === 0) return;\n const ids = selection.current.map(asNodeId);\n scene.batch(`Edit ${leaf.leaf.name}`, () => {\n for (const id of ids) {\n const node = scene.get(id);\n if (!node) continue;\n // `setAtPath` spreads plain objects/arrays down the schema path;\n // consumer `pose`/`data` are assumed plain-object-shaped along\n // that path (a class instance's prototype would be dropped by\n // the spread) — the `as TPose`/`as TData` casts below rely on it.\n if (head === 'pose') {\n scene.setPose(id, setAtPath(node.pose as object, rest, value) as TPose);\n } else if (head === 'data') {\n scene.update(id, { data: setAtPath(node.data as object, rest, value) as TData });\n }\n }\n });\n };\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n <header className={s.header}>\n {nodes.length === 1 ? (\n <span className={s.kind}>{kindLabel(kinds[0])}</span>\n ) : (\n <>\n <span className={s.kind}>{nodes.length} selected</span>\n <span className={s.breakdown}>{kindBreakdown(kinds)}</span>\n </>\n )}\n </header>\n {sections.map((section) => {\n // Render controls before emitting row chrome so a null-rendering\n // leaf (custom renderer opting out) collapses its cell — and the\n // whole row / section when nothing survives. PrefsForm precedent.\n const rows = section.rows\n .map((row) => {\n const controls = renderCells(\n row.leaves.map((panelLeaf) =>\n leafCell(\n panelLeaf,\n row.leaves.length > 1\n ? `${row.label} ${panelLeaf.leaf.name}`\n : panelLeaf.leaf.name,\n nodes,\n renderers,\n commit,\n selectionKey,\n ),\n ),\n renderers,\n );\n return { row, controls };\n })\n .filter(({ controls }) => controls.length > 0);\n if (rows.length === 0) return null;\n return (\n <section key={section.key} className={s.section}>\n {section.name !== '' && <h3 className={s.sectionTitle}>{section.name}</h3>}\n {rows.map(({ row, controls }) => (\n // A `block` leaf brings its own chrome — it spans the section\n // instead of sitting in a labeled row's control cell. Under a\n // headless section nothing else names it, so it heads itself;\n // an object leaf already renders its own heading.\n row.leaves.length === 1 && row.leaves[0].leaf.block ? (\n <div key={row.leaves[0].path}>\n {section.name === '' && row.leaves[0].leaf.kind !== 'object' && (\n <h4 className={s.sectionTitle} title={row.leaves[0].leaf.description}>\n {row.leaves[0].leaf.name}\n </h4>\n )}\n {controls[0].content}\n </div>\n ) : (\n <div key={row.leaves[0].path} className={s.row}>\n <span className={s.rowLabel} title={row.leaves[0].leaf.description}>\n {row.label}\n </span>\n <span className={s.rowControls}>\n {controls.map(({ key, content }) => (\n <Fragment key={key}>{content}</Fragment>\n ))}\n </span>\n </div>\n )\n ))}\n </section>\n );\n })}\n </div>\n );\n}\n\n/**\n * One leaf with its context built but nothing rendered yet.\n *\n * Deferring the render is what lets a run of sibling flags become one bar:\n * whether a leaf joins its neighbours is a fact about the run, which no leaf\n * rendering itself can see.\n */\ninterface LeafCell {\n key: string;\n leaf: ToolPrefLeaf;\n ctx: PropertyRenderContext;\n /** The accessible name this leaf's control carries — qualified by the row's\n * pair where the row holds more than one leaf. */\n ariaLabel: string;\n /** Renders the leaf on its own, chrome and all. */\n render: () => ReactNode;\n}\n\n/** A cell's content, once the run it belongs to has been decided. */\ninterface RenderedCell {\n key: string;\n /** The leaf that names the cell — the run's first, for a flag bar. */\n leaf: ToolPrefLeaf;\n block: boolean;\n content: ReactNode;\n}\n\nfunction leafCell(\n panelLeaf: PanelLeaf,\n ariaLabel: string,\n nodes: readonly AnyNode[],\n renderers: Record<string, PropertyRenderer> | undefined,\n commit: (leaf: PanelLeaf, value: unknown) => void,\n selectionKey: string,\n): LeafCell {\n const { path, leaf } = panelLeaf;\n const aggregated = aggregateValue(nodes, path);\n const mixed = aggregated === MIXED;\n const value = mixed ? undefined : aggregated;\n\n const ctx: PropertyRenderContext = {\n path,\n pref: leaf,\n value,\n mixed,\n unset: !mixed && aggregated === undefined,\n setValue: (v) => commit(panelLeaf, v),\n valueAt: (p) => {\n const at = aggregateValue(nodes, p);\n return at === MIXED ? { value: undefined, mixed: true } : { value: at, mixed: false };\n },\n };\n\n return {\n key: path,\n leaf,\n ctx,\n ariaLabel,\n render: () => {\n const custom = renderers?.[path] ?? renderers?.[leaf.kind];\n if (custom) return custom(ctx);\n return renderBuiltin(ctx, ariaLabel, renderers, selectionKey);\n },\n };\n}\n\n/**\n * True for a leaf that can join a flag bar: a paired toggle boolean the\n * consumer has not claimed with a renderer of its own.\n */\nfunction isFlagCell(cell: LeafCell, renderers?: Record<string, PropertyRenderer>): boolean {\n const { leaf } = cell;\n return (\n leaf.kind === 'boolean' &&\n (leaf as ToolPrefBoolean).control === 'toggle' &&\n leaf.pair !== undefined &&\n renderers?.[cell.ctx.path] === undefined &&\n renderers?.[leaf.kind] === undefined\n );\n}\n\n/**\n * Renders a row's cells, collapsing each run of adjacent same-`pair` toggle\n * booleans into one segmented bar — the idiom the enum toggle a row away\n * already uses, rather than one detached pill per flag.\n */\nfunction renderCells(\n cells: readonly LeafCell[],\n renderers?: Record<string, PropertyRenderer>,\n): RenderedCell[] {\n const out: RenderedCell[] = [];\n for (let i = 0; i < cells.length; ) {\n const cell = cells[i];\n if (!isFlagCell(cell, renderers)) {\n const content = cell.render();\n if (content != null) {\n out.push({ key: cell.key, leaf: cell.leaf, block: cell.leaf.block === true, content });\n }\n i += 1;\n continue;\n }\n let j = i + 1;\n while (\n j < cells.length &&\n isFlagCell(cells[j], renderers) &&\n cells[j].leaf.pair === cell.leaf.pair\n ) {\n j += 1;\n }\n const run = cells.slice(i, j);\n out.push({\n key: cell.key,\n leaf: cell.leaf,\n block: run.every((c) => c.leaf.block === true),\n content: <FlagBar run={run} ariaLabel={cell.leaf.pair as string} />,\n });\n i = j;\n }\n return out;\n}\n\n/**\n * A run of paired boolean flags as one multi-select bar — U / S / O, the way\n * every text editor draws them.\n */\nfunction FlagBar({ run, ariaLabel }: { run: readonly LeafCell[]; ariaLabel: string }): ReactNode {\n const on = run.filter((c) => c.ctx.value === true).map((c) => c.key);\n return (\n <ToggleBar<string>\n mode=\"multiple\"\n size=\"sm\"\n variant=\"flat\"\n className={s.flagToggle}\n ariaLabel={ariaLabel}\n items={run.map((c) => {\n const p = c.leaf as ToolPrefBoolean;\n return {\n value: c.key,\n label:\n p.icon && p.icon in ICON_PATHS ? (\n <Icon name={p.icon as IconName} size={14} />\n ) : (\n (p.short ?? p.name.slice(0, 1))\n ),\n ariaLabel: c.ariaLabel,\n };\n })}\n value={on}\n // Each flag owns its own path, so only the segment that moved is\n // written — committing the run would write two fields nobody touched,\n // and inside an object leaf would fabricate values for the other two.\n mixedValues={run.filter((c) => c.ctx.mixed).map((c) => c.key)}\n onChange={(next) => {\n for (const c of run) {\n const now = next.includes(c.key);\n if (now !== on.includes(c.key)) c.ctx.setValue(now);\n }\n }}\n />\n );\n}\n\nfunction renderBuiltin(\n ctx: PropertyRenderContext,\n ariaLabel: string,\n renderers?: Record<string, PropertyRenderer>,\n /** Identifies the current selection, so a control holding per-selection\n * scratch (the paint kind memory) is remounted rather than carried over. */\n selectionKey = '',\n /** True for a field of an object leaf. Such a field writes itself into its\n * parent, so it can only hold values its parent's type permits — which is\n * what rules \"none\" out for a nested paint. */\n nested = false,\n): ReactNode {\n const { pref, value, mixed, unset, setValue } = ctx;\n if (pref.kind === 'font-family') {\n // Not a `ToolPref` kind: its options are the live font registry, which no\n // static schema can carry. Core's own text schema still declares it, so\n // the panel ships the control rather than leaving every consumer to.\n //\n // The substitution probe runs at the node's own weight and style, so the\n // label names the variant that will actually paint. A mixed selection has\n // no single one; the probe falls back to 400/normal there.\n const weight = ctx.valueAt('data.style.fontWeight');\n const style = ctx.valueAt('data.style.fontStyle');\n return (\n <FontFamilySelect\n className={s.select}\n value={mixed || typeof value !== 'string' ? undefined : value}\n mixed={mixed}\n onChange={setValue}\n weight={typeof weight.value === 'number' ? weight.value : undefined}\n fontStyle={style.value === 'italic' ? 'italic' : undefined}\n aria-label={ariaLabel}\n />\n );\n }\n if (!isBuiltinToolPref(pref)) {\n return <span className={s.unrenderable}>({pref.kind}: no renderer)</span>;\n }\n switch (pref.kind) {\n case 'number': {\n const p = pref as ToolPrefNumber;\n const stored = typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n const display = stored !== undefined ? (p.unit ? p.unit.toDisplay(stored) : stored) : NaN;\n if (p.control === 'slider') {\n const min = p.min ?? 0;\n const max = p.max ?? 100;\n const known = !mixed && stored !== undefined;\n return (\n <>\n <InlineRange\n min={min}\n max={max}\n step={p.step ?? 1}\n // The thumb clamps to the track; the readout beside it does not,\n // so a value past `max` is still reported as what it is.\n value={known ? Math.min(Math.max(display, min), max) : min}\n disabled={mixed}\n className={s.slider}\n aria-label={ariaLabel}\n onChange={(e) => {\n const next = Number(e.target.value);\n setValue(p.unit ? p.unit.fromDisplay(next) : next);\n }}\n />\n <span className={s.sliderReadout} aria-hidden=\"true\">\n {known ? `${display}${p.unit?.suffix ?? ''}` : '—'}\n </span>\n </>\n );\n }\n const field = (\n <NumberField\n className={s.number}\n value={mixed || stored === undefined ? NaN : display}\n placeholder={mixed ? 'Mixed' : undefined}\n minValue={p.min}\n maxValue={p.max}\n step={p.step ?? 1}\n hideSteppers\n aria-label={ariaLabel}\n onChange={(n) => {\n if (Number.isNaN(n)) return;\n setValue(p.unit ? p.unit.fromDisplay(n) : n);\n }}\n />\n );\n if (p.unit?.suffix === undefined) return field;\n return (\n <>\n {field}\n <span className={s.unitSuffix} aria-hidden=\"true\">\n {p.unit.suffix}\n </span>\n </>\n );\n }\n case 'string': {\n return (\n <DraftInput\n text={mixed ? undefined : typeof value === 'string' ? value : ''}\n placeholder={mixed ? 'Mixed' : undefined}\n ariaLabel={ariaLabel}\n onCommit={setValue}\n />\n );\n }\n case 'boolean': {\n const p = pref as ToolPrefBoolean;\n if (p.control === 'toggle') {\n // One segment, so the bar is the flag: `short` (else the initial of\n // `name`) is all a paired row has room for, and `name` stays the\n // accessible name.\n //\n // No dimming wrapper here. Unselected already *is* how a toggle button\n // says \"not set\", and dimming it reads as disabled; mixed has an ARIA\n // form on a toggle — `mixedValues` — which the `Switch` below has to\n // fake for want of one.\n return (\n <ToggleBar<string>\n mode=\"multiple\"\n size=\"sm\"\n variant=\"flat\"\n className={s.flagToggle}\n ariaLabel={ariaLabel}\n items={[{\n value: ctx.path,\n label:\n p.icon && p.icon in ICON_PATHS ? (\n <Icon name={p.icon as IconName} size={14} />\n ) : (\n (p.short ?? p.name.slice(0, 1))\n ),\n ariaLabel,\n }]}\n value={value === true ? [ctx.path] : []}\n mixedValues={mixed ? [ctx.path] : []}\n onChange={(next) => setValue(next.includes(ctx.path))}\n />\n );\n }\n const control = <Switch isSelected={Boolean(value)} onChange={setValue} aria-label={ariaLabel} />;\n // Neither `Switch`'s indeterminate gap nor \"unset\" has an ARIA form, so\n // a reduced-opacity wrapper with a title is the cue for both — a mixed\n // selection, and a field the node leaves to its fallback.\n if (!mixed && !unset) return control;\n return (\n <span className={s.mixedSwitch} title={mixed ? 'Mixed' : 'Not set'}>\n {control}\n </span>\n );\n }\n case 'enum': {\n const p = pref as ToolPrefEnum;\n // An encoded leaf stores something other than the option string, so the\n // option comes from the encoding rather than from the value — and an\n // absent field is one of the things it reads (no dash is `solid`), which\n // is why `unset` doesn't blank the control here.\n const option = p.encoding\n ? (mixed ? undefined : p.encoding.read(value, ctx.siblings))\n : mixed || unset\n ? undefined\n : typeof value === 'string'\n ? value\n : p.default;\n const choose = (next: string): void =>\n setValue(p.encoding ? p.encoding.write(next, ctx.siblings) : next);\n if (p.control === 'toggle') {\n // Every option visible at once, which is the point of a segmented\n // control: a glyph, else `short`, keeps it to the width a property row\n // has, and the full label stays the accessible name. A mixed selection\n // selects nothing rather than picking a winner.\n return (\n <ToggleBar<string>\n size=\"sm\"\n variant=\"flat\"\n className={s.toggle}\n ariaLabel={ariaLabel}\n items={p.options.map((o) => ({\n value: o.value,\n label:\n o.icon && o.icon in ICON_PATHS ? (\n <Icon name={o.icon as IconName} size={14} />\n ) : (\n (o.short ?? o.label)\n ),\n ariaLabel: o.label,\n disabled: o.disabled,\n }))}\n value={option ?? null}\n onChange={(next) => { if (next !== null) choose(next); }}\n />\n );\n }\n return (\n <Select<string>\n className={s.select}\n options={p.options.map((o) => ({ value: o.value, label: o.label, isDisabled: o.disabled }))}\n selectedKey={option ?? null}\n placeholder={mixed ? 'Mixed' : unset ? '—' : undefined}\n onSelectionChange={choose}\n aria-label={ariaLabel}\n />\n );\n }\n case 'color': {\n const p = pref as ToolPrefColor;\n return (\n <ColorField\n value={mixed ? undefined : typeof value === 'string' ? value : p.default}\n mixed={mixed}\n alpha={p.alpha}\n onChange={setValue}\n aria-label={ariaLabel}\n />\n );\n }\n case 'paint': {\n // The value is a whole `FillStyle`, and `PaintInput` edits it as one —\n // previewing a gradient as a gradient rather than degrading it to the\n // indeterminate chip. That leaves the checkerboard meaning `mixed` and\n // nothing else.\n const p = pref as ToolPrefPaint;\n // `??` would read an explicit `null` — \"no paint\" — as absent and show\n // the schema default over it, so the None segment could never stay lit.\n // Unset shows the fallback that is actually on the canvas, dimmed: true,\n // but not chosen, which is what the dimming says.\n const paint = (value === null\n ? null\n : (value ?? (mixed ? undefined : p.default))) as FillStyle | null | undefined;\n return (\n <PaintInput\n // Per-kind switch memory is scratch for one selection; carrying it\n // across would recall the previous node's gradient.\n key={selectionKey}\n value={paint}\n mixed={mixed}\n unset={unset}\n // A nested paint writes one field of its parent, and no kit paint\n // field is optional — `Stroke.paint` is required. Removing the\n // whole parent is a different edit than repainting it, so the\n // control must not offer one as the other. A consumer whose\n // renderer owns the parent key (WeaselDraw's `setStroke`) can.\n allowNone={!nested}\n onChange={setValue}\n aria-label={ariaLabel}\n />\n );\n }\n case 'object': {\n // One value with fields hanging off it. Each child writes the parent\n // whole, so a field is never set on a half-built object.\n return <ObjectLeaf ctx={ctx} renderers={renderers} selectionKey={selectionKey} />;\n }\n default: {\n // Not reachable while every built-in kind has an arm — and a new kind\n // that lacks one is a compile error here, never a blank cell.\n const _exhaustive: never = pref;\n throw new Error(\n `SelectionPanel: no control for built-in pref kind \"${(_exhaustive as { kind: string }).kind}\"`,\n );\n }\n }\n}\n\n/**\n * Renders an object leaf: a titled block whose rows are the object's own\n * fields. A child's edit commits the parent object, never the child's path —\n * writing into a path whose value is not an object yet would corrupt it.\n */\n/** One labeled row inside an object leaf; `pair` merges adjacent fields into\n * it, exactly as the section rows merge theirs. */\ninterface ObjectRow {\n key: string;\n pair?: string;\n label: string;\n title?: string;\n /** Every field in the row brought its own chrome, so the row drops the\n * label column and spans the block. One unlabeled field in an otherwise\n * labeled pair would leave the row named after half of itself. */\n block: boolean;\n controls: ReactNode[];\n}\n\n/**\n * Renders an object leaf: the object's own fields, as rows. A child's edit\n * commits the parent object, never the child's path — writing into a path\n * whose value is not an object yet would corrupt it.\n */\nfunction ObjectLeaf({\n ctx,\n renderers,\n selectionKey = '',\n}: {\n ctx: PropertyRenderContext;\n renderers?: Record<string, PropertyRenderer>;\n selectionKey?: string;\n}): ReactNode {\n const pref = ctx.pref as ToolPrefObject;\n const held = typeof ctx.value === 'object' && ctx.value !== null\n ? (ctx.value as Record<string, unknown>)\n : undefined;\n\n // A value whose fields are entirely grouped is titled by those groups — its\n // own heading would stack onto the first one and name nothing new.\n const allGrouped = Object.values(pref.children).every((child) => !('kind' in child));\n\n // `indent` is false when nothing visible sits above these rows: depth is\n // drawn only where a label marks it.\n const rowsOf = (\n children: Record<string, ToolPrefLeaf | ToolPrefGroup>,\n indent: boolean,\n ): ReactNode[] => {\n const out: (ReactNode | ObjectRow)[] = [];\n const isRow = (v: ReactNode | ObjectRow): v is ObjectRow =>\n typeof v === 'object' && v !== null && 'controls' in v;\n\n // Cells accumulate unrendered so a run of adjacent same-`pair` flags can\n // be seen as a run before any of them draws; a group heading between two\n // fields ends the run, exactly as it ends a paired row.\n let pending: LeafCell[] = [];\n const flush = (): void => {\n for (const { key, leaf, block, content } of renderCells(pending, renderers)) {\n const prev = out[out.length - 1];\n if (leaf.pair !== undefined && isRow(prev) && prev.pair === leaf.pair) {\n prev.controls.push(<Fragment key={key}>{content}</Fragment>);\n prev.block &&= block;\n continue;\n }\n out.push({\n key,\n pair: leaf.pair,\n label: leaf.pair ?? leaf.name,\n title: leaf.description,\n block,\n controls: [<Fragment key={key}>{content}</Fragment>],\n });\n }\n pending = [];\n };\n\n for (const [key, child] of Object.entries(children)) {\n // A group among the children organises the fields under a heading\n // without contributing to the path — the rule group keys follow at the\n // top level.\n if (!('kind' in child)) {\n const labeled = child.name !== '';\n const inner = rowsOf(child.children, labeled);\n if (inner.length === 0) continue;\n flush();\n out.push(\n <div key={`group:${key}`} className={labeled && indent ? s.objectGroup : undefined}>\n {labeled && <h5 className={s.sectionTitle}>{child.name}</h5>}\n {inner}\n </div>,\n );\n continue;\n }\n const childPath = `${ctx.path}.${key}`;\n const childCtx: PropertyRenderContext = {\n path: childPath,\n pref: child,\n value: held?.[key],\n mixed: ctx.mixed,\n // A field of an object the node does not hold is unset, and so is one\n // the object omits — `data.stroke` absent leaves every stroke field\n // with nothing behind it, not with the schema's defaults.\n unset: !ctx.mixed && held?.[key] === undefined,\n siblings: held,\n valueAt: ctx.valueAt,\n setValue: (v) => {\n // The node holds no object yet, so writing one field has to\n // materialize the rest: the leaf's `default` is what a complete\n // value looks like. Starting from `{}` instead committed the one\n // field on its own — a `data.stroke` of `{ width: 2 }` with no\n // `paint`, which the type forbids and the painter threw on, taking\n // the whole frame and the visible document with it.\n const base = held\n ?? pref.fromScalar?.(ctx.value)\n ?? (typeof pref.default === 'object' && pref.default !== null\n ? { ...(pref.default as Record<string, unknown>) }\n : {});\n if (v === undefined) {\n // A field written as absent is absent — leaving the key holding\n // `undefined` says the object has a dash of nothing.\n const { [key]: _dropped, ...rest } = base;\n ctx.setValue(rest);\n return;\n }\n ctx.setValue({ ...base, [key]: v });\n },\n };\n pending.push({\n key: childPath,\n leaf: child,\n ctx: childCtx,\n ariaLabel: child.name,\n render: () => {\n const custom = renderers?.[childPath] ?? renderers?.[child.kind];\n const rendered = custom\n ? custom(childCtx)\n : renderBuiltin(childCtx, child.name, renderers, selectionKey, true);\n if (rendered == null) return null;\n // Neither a paired row nor a block one gives a field its own label\n // column, so the glyph is all that names it on screen. The control\n // already carries `name` as its accessible name, which leaves the\n // glyph decorative.\n const unlabeled = child.pair !== undefined || child.block === true;\n const glyph = unlabeled && child.icon && child.icon in ICON_PATHS;\n if (!glyph) return rendered;\n return (\n <span className={s.namedField}>\n <Icon name={child.icon as IconName} size={14} />\n {rendered}\n </span>\n );\n },\n });\n }\n flush();\n return out.map((entry) =>\n typeof entry === 'object' && entry !== null && 'controls' in entry ? (\n entry.block ? (\n <div key={entry.key} className={`${s.rowControls} ${s.blockRow}`} title={entry.title}>\n {entry.controls}\n </div>\n ) : (\n <div key={entry.key} className={s.row}>\n <span className={s.rowLabel} title={entry.title}>{entry.label}</span>\n <span className={s.rowControls}>{entry.controls}</span>\n </div>\n )\n ) : (\n entry\n ),\n );\n };\n\n const rows = rowsOf(pref.children, !allGrouped);\n if (rows.length === 0) return null;\n return (\n <div className={s.objectLeaf}>\n {!allGrouped && <h4 className={s.sectionTitle}>{pref.name}</h4>}\n {rows}\n </div>\n );\n}\n\n/** Text input with commit-on-blur/Enter semantics — live-per-keystroke\n * writes would emit one undo step per character. */\nfunction DraftInput({\n text,\n placeholder,\n ariaLabel,\n onCommit,\n}: {\n text: string | undefined;\n placeholder?: string;\n ariaLabel: string;\n onCommit: (v: string) => void;\n}) {\n const [draft, setDraft] = useState<string | null>(null);\n return (\n <Input\n value={draft ?? text ?? ''}\n placeholder={placeholder}\n aria-label={ariaLabel}\n onChange={setDraft}\n onBlur={() => {\n if (draft !== null && draft !== text) onCommit(draft);\n setDraft(null);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter') (e.target as HTMLInputElement).blur();\n }}\n />\n );\n}\n"],"mappings":"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"}