@weasel-js/ui 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/ActionBar-GThu74LC.js.map +1 -1
- package/dist/chunks/{ActionsBar-CAjrj_Pt.js → ActionsBar-9aR5N3lU.js} +10 -17
- package/dist/chunks/ActionsBar-9aR5N3lU.js.map +1 -0
- package/dist/chunks/Badge-DZQqAV4d.js.map +1 -1
- package/dist/chunks/BandEditor-13K91O9M.js +228 -0
- package/dist/chunks/BandEditor-13K91O9M.js.map +1 -0
- package/dist/chunks/Button-9QNIYugK.js.map +1 -1
- package/dist/chunks/{Callout-DlirfLJl.js → Callout-CFqG5xho.js} +1 -1
- package/dist/chunks/Callout-CFqG5xho.js.map +1 -0
- package/dist/chunks/{Checkbox-B3Z_u7k0.js → Checkbox-KQ5L8APP.js} +1 -1
- package/dist/chunks/Checkbox-KQ5L8APP.js.map +1 -0
- package/dist/chunks/{ColorField-B4Hd2qqS.js → ColorField-ykpiD3XU.js} +1 -1
- package/dist/chunks/ColorField-ykpiD3XU.js.map +1 -0
- package/dist/chunks/{ComboBox-tbmgND-F.js → ComboBox-DpxsFVBy.js} +2 -2
- package/dist/chunks/ComboBox-DpxsFVBy.js.map +1 -0
- package/dist/chunks/{CurveEditor-CSKqAXMp.js → CurveEditor-CqIt_Xph.js} +3 -3
- package/dist/chunks/CurveEditor-CqIt_Xph.js.map +1 -0
- package/dist/chunks/DataGrid-BQklXGRl.js.map +1 -1
- package/dist/chunks/{Dialog-BFuakycR.js → Dialog-3FCd_gYi.js} +1 -1
- package/dist/chunks/Dialog-3FCd_gYi.js.map +1 -0
- package/dist/chunks/{Field-DPxmJzZz.js → Field-VQwmrIna.js} +1 -1
- package/dist/chunks/Field-VQwmrIna.js.map +1 -0
- package/dist/chunks/{GradientEditor-3pYXGAPg.js → GradientEditor-DeptQpWX.js} +5 -5
- package/dist/chunks/GradientEditor-DeptQpWX.js.map +1 -0
- package/dist/chunks/{Input-Dg_CYXg4.js → Input-DpkwYtUY.js} +2 -2
- package/dist/chunks/Input-DpkwYtUY.js.map +1 -0
- package/dist/chunks/Keycaps-CFX7UAj8.js.map +1 -1
- package/dist/chunks/{NumberField-BhxUgi43.js → NumberField-BbzI0GtU.js} +2 -2
- package/dist/chunks/NumberField-BbzI0GtU.js.map +1 -0
- package/dist/chunks/{OptionsBar-hQN-wfd-.js → OptionsBar-CHvH4n4u.js} +10 -17
- package/dist/chunks/OptionsBar-CHvH4n4u.js.map +1 -0
- package/dist/chunks/Plot2D-CEnhzodm.js.map +1 -1
- package/dist/chunks/{PointPlotter-DCSjvpN0.js → PointPlotter-DD-bWmRc.js} +2 -2
- package/dist/chunks/PointPlotter-DD-bWmRc.js.map +1 -0
- package/dist/chunks/{Powerline-DkOTPnUV.js → Powerline-Dd9Ukhcl.js} +1 -1
- package/dist/chunks/Powerline-Dd9Ukhcl.js.map +1 -0
- package/dist/chunks/{Prefs-C4HfuTvm.js → Prefs-lgI4x4jF.js} +10 -10
- package/dist/chunks/Prefs-lgI4x4jF.js.map +1 -0
- package/dist/chunks/{RadioGroup-Bg3Rqxe2.js → RadioGroup-CIpHr3g6.js} +2 -2
- package/dist/chunks/RadioGroup-CIpHr3g6.js.map +1 -0
- package/dist/chunks/{RangeSlider-BvM_DGSr.js → RangeSlider-CaDogGTj.js} +1 -1
- package/dist/chunks/RangeSlider-CaDogGTj.js.map +1 -0
- package/dist/chunks/{ResizeHandle-BUkW2kZC.js → ResizeHandle-BOpmtOOj.js} +2 -2
- package/dist/chunks/ResizeHandle-BOpmtOOj.js.map +1 -0
- package/dist/chunks/{Select-DC0AjrEe.js → Select-BE3Y1NrH.js} +2 -2
- package/dist/chunks/Select-BE3Y1NrH.js.map +1 -0
- package/dist/chunks/{SelectionPanel-DoTdisRY.js → SelectionPanel-Zaw8tYQH.js} +6 -6
- package/dist/chunks/SelectionPanel-Zaw8tYQH.js.map +1 -0
- package/dist/chunks/{Sidebar-B8qig3BQ.js → Sidebar-DP_nyJze.js} +1 -1
- package/dist/chunks/Sidebar-DP_nyJze.js.map +1 -0
- package/dist/chunks/{SidebarPanel-LOt2c-wb.js → SidebarPanel-BpD0ptyg.js} +1 -1
- package/dist/chunks/SidebarPanel-BpD0ptyg.js.map +1 -0
- package/dist/chunks/{Slider-Di2kNqVy.js → Slider-CmPeFJ3x.js} +4 -4
- package/dist/chunks/Slider-CmPeFJ3x.js.map +1 -0
- package/dist/chunks/{StatusBar-Cg2LzdAm.js → StatusBar-DBO0E0Yd.js} +1 -1
- package/dist/chunks/StatusBar-DBO0E0Yd.js.map +1 -0
- package/dist/chunks/{Switch-I4putUAY.js → Switch-JyVSSAzC.js} +1 -1
- package/dist/chunks/{Switch-I4putUAY.js.map → Switch-JyVSSAzC.js.map} +1 -1
- package/dist/chunks/{Tabs-irZuG_PN.js → Tabs-Df0sDomx.js} +1 -1
- package/dist/chunks/Tabs-Df0sDomx.js.map +1 -0
- package/dist/chunks/{Toast-BGYP8Pw0.js → Toast-DFw4IrD_.js} +1 -1
- package/dist/chunks/Toast-DFw4IrD_.js.map +1 -0
- package/dist/chunks/ToggleBar-BP1zFcvg.js.map +1 -1
- package/dist/chunks/{ToolButton-DUeg8fPN.js → ToolButton-DpT-mukU.js} +6 -5
- package/dist/chunks/ToolButton-DpT-mukU.js.map +1 -0
- package/dist/chunks/{ToolGroup-D0CvyQhQ.js → ToolGroup-BTIwd4U4.js} +1 -1
- package/dist/chunks/ToolGroup-BTIwd4U4.js.map +1 -0
- package/dist/chunks/{ToolOptionsBar-N_MQcSo4.js → ToolOptionsBar-JvmY7iNe.js} +1 -1
- package/dist/chunks/ToolOptionsBar-JvmY7iNe.js.map +1 -0
- package/dist/chunks/{ToolPalette-BipoMdZz.js → ToolPalette-DPn0owno.js} +27 -47
- package/dist/chunks/ToolPalette-DPn0owno.js.map +1 -0
- package/dist/chunks/Tooltip-Clgn4trm.js.map +1 -1
- package/dist/chunks/segmentedControl.module-DCyuDbj1.js +11 -0
- package/dist/chunks/segmentedControl.module-DCyuDbj1.js.map +1 -0
- package/dist/chunks/useRovingTabIndex-DBZtq4IH.js.map +1 -1
- package/dist/color/oklch.d.ts +12 -0
- package/dist/color/oklch.d.ts.map +1 -1
- package/dist/components/ActionBar/ActionBar.d.ts +1 -0
- package/dist/components/ActionBar/ActionBar.d.ts.map +1 -1
- package/dist/components/ActionsBar/ActionsBar.d.ts +15 -0
- package/dist/components/ActionsBar/ActionsBar.d.ts.map +1 -1
- package/dist/components/ActionsBar/index.js +1 -1
- package/dist/components/Badge/Badge.d.ts +14 -0
- package/dist/components/Badge/Badge.d.ts.map +1 -1
- package/dist/components/Badge/bases/edgeProfiles.d.ts +8 -0
- package/dist/components/Badge/bases/edgeProfiles.d.ts.map +1 -1
- package/dist/components/Badge/types.d.ts +11 -0
- package/dist/components/Badge/types.d.ts.map +1 -1
- package/dist/components/BandEditor/BandEditor.d.ts +56 -0
- package/dist/components/BandEditor/BandEditor.d.ts.map +1 -0
- package/dist/components/BandEditor/bands.d.ts +45 -0
- package/dist/components/BandEditor/bands.d.ts.map +1 -0
- package/dist/components/BandEditor/index.d.ts +4 -0
- package/dist/components/BandEditor/index.d.ts.map +1 -0
- package/dist/components/BandEditor/index.js +2 -0
- package/dist/components/BandEditor/scale.d.ts +25 -0
- package/dist/components/BandEditor/scale.d.ts.map +1 -0
- package/dist/components/Button/Button.d.ts +13 -0
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/Callout/Callout.d.ts +2 -0
- package/dist/components/Callout/Callout.d.ts.map +1 -1
- package/dist/components/Callout/index.js +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +4 -0
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/components/Checkbox/index.js +1 -1
- package/dist/components/ColorField/ColorField.d.ts +4 -0
- package/dist/components/ColorField/ColorField.d.ts.map +1 -1
- package/dist/components/ColorField/index.js +1 -1
- package/dist/components/ComboBox/ComboBox.d.ts +11 -0
- package/dist/components/ComboBox/ComboBox.d.ts.map +1 -1
- package/dist/components/ComboBox/index.js +1 -1
- package/dist/components/CurveEditor/CurveEditor.d.ts +14 -2
- package/dist/components/CurveEditor/CurveEditor.d.ts.map +1 -1
- package/dist/components/CurveEditor/LayeredCurveEditor.d.ts +15 -0
- package/dist/components/CurveEditor/LayeredCurveEditor.d.ts.map +1 -1
- package/dist/components/CurveEditor/createFunctionLayer.d.ts +39 -0
- package/dist/components/CurveEditor/createFunctionLayer.d.ts.map +1 -1
- package/dist/components/CurveEditor/index.js +1 -1
- package/dist/components/CurveEditor/layerTypes.d.ts +5 -0
- package/dist/components/CurveEditor/layerTypes.d.ts.map +1 -1
- package/dist/components/CurveEditor/setCurveOp.d.ts +4 -0
- package/dist/components/CurveEditor/setCurveOp.d.ts.map +1 -1
- package/dist/components/DataGrid/DataGrid.d.ts +12 -0
- package/dist/components/DataGrid/DataGrid.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.d.ts +1 -0
- package/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/index.js +1 -1
- package/dist/components/Field/Field.d.ts +9 -0
- package/dist/components/Field/Field.d.ts.map +1 -1
- package/dist/components/Field/index.js +1 -1
- package/dist/components/GradientEditor/GradientEditor.d.ts +4 -0
- package/dist/components/GradientEditor/GradientEditor.d.ts.map +1 -1
- package/dist/components/GradientEditor/GradientHandles.d.ts +4 -0
- package/dist/components/GradientEditor/GradientHandles.d.ts.map +1 -1
- package/dist/components/GradientEditor/index.js +1 -1
- package/dist/components/Input/Input.d.ts +1 -0
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/Input/index.js +1 -1
- package/dist/components/Keycaps/Keycap.d.ts +10 -0
- package/dist/components/Keycaps/Keycap.d.ts.map +1 -1
- package/dist/components/Keycaps/Keycaps.d.ts +2 -0
- package/dist/components/Keycaps/Keycaps.d.ts.map +1 -1
- package/dist/components/Keycaps/keySpecsFromMods.d.ts +2 -0
- package/dist/components/Keycaps/keySpecsFromMods.d.ts.map +1 -1
- package/dist/components/NumberField/NumberField.d.ts +9 -0
- package/dist/components/NumberField/NumberField.d.ts.map +1 -1
- package/dist/components/NumberField/index.js +1 -1
- package/dist/components/OptionsBar/OptionsBar.d.ts +12 -0
- package/dist/components/OptionsBar/OptionsBar.d.ts.map +1 -1
- package/dist/components/OptionsBar/index.js +1 -1
- package/dist/components/Plot2D/Plot2D.d.ts +14 -0
- package/dist/components/Plot2D/Plot2D.d.ts.map +1 -1
- package/dist/components/Plot2D/geometry.d.ts +10 -0
- package/dist/components/Plot2D/geometry.d.ts.map +1 -1
- package/dist/components/PointPlotter/PointPlotter.d.ts +3 -2
- package/dist/components/PointPlotter/PointPlotter.d.ts.map +1 -1
- package/dist/components/PointPlotter/index.js +1 -1
- package/dist/components/Powerline/Powerline.d.ts +13 -0
- package/dist/components/Powerline/Powerline.d.ts.map +1 -1
- package/dist/components/Powerline/index.js +1 -1
- package/dist/components/Prefs/PrefsDialog.d.ts +4 -0
- package/dist/components/Prefs/PrefsDialog.d.ts.map +1 -1
- package/dist/components/Prefs/PrefsForm.d.ts +6 -0
- package/dist/components/Prefs/PrefsForm.d.ts.map +1 -1
- package/dist/components/Prefs/index.js +1 -1
- package/dist/components/Prefs/schema.d.ts +17 -0
- package/dist/components/Prefs/schema.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +8 -0
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/components/RadioGroup/index.js +1 -1
- package/dist/components/RangeSlider/RangeSlider.d.ts +1 -0
- package/dist/components/RangeSlider/RangeSlider.d.ts.map +1 -1
- package/dist/components/RangeSlider/index.js +1 -1
- package/dist/components/ResizeHandle/ResizeHandle.d.ts +7 -3
- package/dist/components/ResizeHandle/ResizeHandle.d.ts.map +1 -1
- package/dist/components/ResizeHandle/index.js +1 -1
- package/dist/components/Select/Select.d.ts +11 -0
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectionPanel/SelectionPanel.d.ts +6 -0
- package/dist/components/SelectionPanel/SelectionPanel.d.ts.map +1 -1
- package/dist/components/SelectionPanel/index.js +1 -1
- package/dist/components/SelectionPanel/model.d.ts +12 -0
- package/dist/components/SelectionPanel/model.d.ts.map +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts +1 -0
- package/dist/components/Sidebar/Sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +1 -1
- package/dist/components/SidebarPanel/SidebarPanel.d.ts +1 -0
- package/dist/components/SidebarPanel/SidebarPanel.d.ts.map +1 -1
- package/dist/components/SidebarPanel/index.js +1 -1
- package/dist/components/Slider/Slider.d.ts +43 -2
- package/dist/components/Slider/Slider.d.ts.map +1 -1
- package/dist/components/Slider/index.js +1 -1
- package/dist/components/StatusBar/StatusBar.d.ts +2 -0
- package/dist/components/StatusBar/StatusBar.d.ts.map +1 -1
- package/dist/components/StatusBar/index.js +1 -1
- package/dist/components/Switch/Switch.d.ts +11 -0
- package/dist/components/Switch/Switch.d.ts.map +1 -1
- package/dist/components/Switch/index.js +1 -1
- package/dist/components/Tabs/Tabs.d.ts +11 -0
- package/dist/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs/index.js +1 -1
- package/dist/components/Toast/Toast.d.ts +2 -0
- package/dist/components/Toast/Toast.d.ts.map +1 -1
- package/dist/components/Toast/index.js +1 -1
- package/dist/components/Toast/queue.d.ts +7 -0
- package/dist/components/Toast/queue.d.ts.map +1 -1
- package/dist/components/ToggleBar/ToggleBar.d.ts +16 -0
- package/dist/components/ToggleBar/ToggleBar.d.ts.map +1 -1
- package/dist/components/ToolButton/ToolButton.d.ts +5 -1
- package/dist/components/ToolButton/ToolButton.d.ts.map +1 -1
- package/dist/components/ToolButton/index.js +1 -1
- package/dist/components/ToolGroup/ToolGroup.d.ts +1 -0
- package/dist/components/ToolGroup/ToolGroup.d.ts.map +1 -1
- package/dist/components/ToolGroup/index.js +1 -1
- package/dist/components/ToolOptionsBar/ToolOptionsBar.d.ts +1 -0
- package/dist/components/ToolOptionsBar/ToolOptionsBar.d.ts.map +1 -1
- package/dist/components/ToolOptionsBar/index.js +1 -1
- package/dist/components/ToolPalette/ToolPalette.d.ts +9 -0
- package/dist/components/ToolPalette/ToolPalette.d.ts.map +1 -1
- package/dist/components/ToolPalette/index.js +1 -1
- package/dist/components/Tooltip/Tooltip.d.ts +5 -0
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/format/number.d.ts +4 -0
- package/dist/format/number.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -37
- package/dist/index.js.map +1 -1
- package/dist/paintGradientTrack.d.ts +13 -0
- package/dist/paintGradientTrack.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/useReorderDragList.d.ts +23 -0
- package/dist/useReorderDragList.d.ts.map +1 -1
- package/dist/useRovingTabIndex.d.ts +5 -0
- package/dist/useRovingTabIndex.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/chunks/ActionsBar-CAjrj_Pt.js.map +0 -1
- package/dist/chunks/Callout-DlirfLJl.js.map +0 -1
- package/dist/chunks/Checkbox-B3Z_u7k0.js.map +0 -1
- package/dist/chunks/ColorField-B4Hd2qqS.js.map +0 -1
- package/dist/chunks/ComboBox-tbmgND-F.js.map +0 -1
- package/dist/chunks/CurveEditor-CSKqAXMp.js.map +0 -1
- package/dist/chunks/Dialog-BFuakycR.js.map +0 -1
- package/dist/chunks/Field-DPxmJzZz.js.map +0 -1
- package/dist/chunks/GradientEditor-3pYXGAPg.js.map +0 -1
- package/dist/chunks/Input-Dg_CYXg4.js.map +0 -1
- package/dist/chunks/NumberField-BhxUgi43.js.map +0 -1
- package/dist/chunks/OptionsBar-hQN-wfd-.js.map +0 -1
- package/dist/chunks/PointPlotter-DCSjvpN0.js.map +0 -1
- package/dist/chunks/Powerline-DkOTPnUV.js.map +0 -1
- package/dist/chunks/Prefs-C4HfuTvm.js.map +0 -1
- package/dist/chunks/RadioGroup-Bg3Rqxe2.js.map +0 -1
- package/dist/chunks/RangeSlider-BvM_DGSr.js.map +0 -1
- package/dist/chunks/ResizeHandle-BUkW2kZC.js.map +0 -1
- package/dist/chunks/Select-DC0AjrEe.js.map +0 -1
- package/dist/chunks/SelectionPanel-DoTdisRY.js.map +0 -1
- package/dist/chunks/Sidebar-B8qig3BQ.js.map +0 -1
- package/dist/chunks/SidebarPanel-LOt2c-wb.js.map +0 -1
- package/dist/chunks/Slider-Di2kNqVy.js.map +0 -1
- package/dist/chunks/StatusBar-Cg2LzdAm.js.map +0 -1
- package/dist/chunks/Tabs-irZuG_PN.js.map +0 -1
- package/dist/chunks/Toast-BGYP8Pw0.js.map +0 -1
- package/dist/chunks/ToolButton-DUeg8fPN.js.map +0 -1
- package/dist/chunks/ToolGroup-D0CvyQhQ.js.map +0 -1
- package/dist/chunks/ToolOptionsBar-N_MQcSo4.js.map +0 -1
- package/dist/chunks/ToolPalette-BipoMdZz.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CurveEditor-CSKqAXMp.js","names":[],"sources":["../../src/components/CurveEditor/catmullRom.ts","../../src/components/CurveEditor/monotone.ts","../../src/components/CurveEditor/interpolation.ts","../../src/components/CurveEditor/hitTest.ts","../../src/components/CurveEditor/CurveEditor.module.css","../../src/components/CurveEditor/createFunctionLayer.tsx","../../src/components/CurveEditor/LayeredCurveEditor.tsx","../../src/components/CurveEditor/CurveEditor.tsx","../../src/components/CurveEditor/setCurveOp.ts"],"sourcesContent":["/**\n * Catmull-Rom spline sampling, parameterized by α.\n *\n * α = 0 → uniform parameterization (original Catmull-Rom). Fast, can\n * produce cusps and self-intersections at sharp 2D angles.\n * α = 0.5 → centripetal (Yuksel/Schneider 2011). No cusps, no\n * self-intersection. The \"safe default\" for 2D.\n * α = 1 → chordal. Smoother than uniform, different artifacts.\n *\n * `sampleCurve(...)` is the convenience alias for centripetal (α=0.5).\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nfunction knot(a: Point, b: Point, alpha: number): number {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n return Math.pow(Math.sqrt(dx * dx + dy * dy), alpha);\n}\n\n/**\n * Sample the Catmull-Rom segment between p1 and p2 at parameter\n * t ∈ [0, 1]. p0 and p3 are the surrounding control points (use phantom\n * reflection at endpoints — see sampleCatmullRom below).\n *\n * Returns p1 at t=0 and p2 at t=1. `alpha` controls parameterization.\n */\nexport function segmentSampleAt(\n p0: Point, p1: Point, p2: Point, p3: Point, t: number,\n alpha: number = 0.5,\n): Point {\n const t0 = 0;\n const t1 = t0 + knot(p0, p1, alpha);\n const t2 = t1 + knot(p1, p2, alpha);\n const t3 = t2 + knot(p2, p3, alpha);\n\n // Degenerate: coincident p1/p2 → linear interpolation fallback.\n if (t2 - t1 === 0) {\n return { x: p1.x + (p2.x - p1.x) * t, y: p1.y + (p2.y - p1.y) * t };\n }\n\n const u = t1 + t * (t2 - t1);\n\n const a1x = ((t1 - u) * p0.x + (u - t0) * p1.x) / (t1 - t0);\n const a1y = ((t1 - u) * p0.y + (u - t0) * p1.y) / (t1 - t0);\n const a2x = ((t2 - u) * p1.x + (u - t1) * p2.x) / (t2 - t1);\n const a2y = ((t2 - u) * p1.y + (u - t1) * p2.y) / (t2 - t1);\n const a3x = ((t3 - u) * p2.x + (u - t2) * p3.x) / (t3 - t2);\n const a3y = ((t3 - u) * p2.y + (u - t2) * p3.y) / (t3 - t2);\n\n const b1x = ((t2 - u) * a1x + (u - t0) * a2x) / (t2 - t0);\n const b1y = ((t2 - u) * a1y + (u - t0) * a2y) / (t2 - t0);\n const b2x = ((t3 - u) * a2x + (u - t1) * a3x) / (t3 - t1);\n const b2y = ((t3 - u) * a2y + (u - t1) * a3y) / (t3 - t1);\n\n const cx = ((t2 - u) * b1x + (u - t1) * b2x) / (t2 - t1);\n const cy = ((t2 - u) * b1y + (u - t1) * b2y) / (t2 - t1);\n return { x: cx, y: cy };\n}\n\n/**\n * Sample the whole curve through `anchors` using Catmull-Rom with the\n * given α. Returns `(n-1) * samplesPerSegment + 1` points. Phantom\n * endpoints by reflection so the first/last segments are tangent to\n * the chord into/out of the endpoints. Returns `[]` for fewer than 2\n * anchors.\n */\nexport function sampleCatmullRom(\n anchors: readonly Point[],\n samplesPerSegment: number,\n alpha: number = 0.5,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const n = anchors.length;\n const out: Point[] = [];\n\n const reflectStart: Point = {\n x: 2 * anchors[0].x - anchors[1].x,\n y: 2 * anchors[0].y - anchors[1].y,\n };\n const reflectEnd: Point = {\n x: 2 * anchors[n - 1].x - anchors[n - 2].x,\n y: 2 * anchors[n - 1].y - anchors[n - 2].y,\n };\n\n for (let i = 0; i < n - 1; i++) {\n const p0 = i === 0 ? reflectStart : anchors[i - 1];\n const p1 = anchors[i];\n const p2 = anchors[i + 1];\n const p3 = i + 2 < n ? anchors[i + 2] : reflectEnd;\n const tStart = i === 0 ? 0 : 1;\n for (let s = tStart; s <= samplesPerSegment; s++) {\n if (s === 0) {\n out.push(p1);\n continue;\n }\n if (s === samplesPerSegment) {\n out.push(p2);\n continue;\n }\n const t = s / samplesPerSegment;\n out.push(segmentSampleAt(p0, p1, p2, p3, t, alpha));\n }\n }\n return out;\n}\n\n/** Convenience alias for centripetal Catmull-Rom (α=0.5). The most\n * common choice; safe default for 2D paths. */\nexport function sampleCurve(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n return sampleCatmullRom(anchors, samplesPerSegment, 0.5);\n}\n","/**\n * Monotone cubic interpolation (Fritsch-Carlson / Steffen variant).\n *\n * Passes through every anchor and guarantees the curve never\n * overshoots `min(neighbors)` / `max(neighbors)` along either axis.\n * The right choice for animation timing curves (no anticipation) or\n * audio envelopes (no out-of-range values).\n *\n * Parameterized by index — works for both 1D (function-shaped) and\n * 2D (parametric) anchor sequences. The x and y coordinates are\n * interpolated independently as functions of the anchor index.\n *\n * Reference: Fritsch & Carlson 1980, \"Monotone piecewise cubic\n * interpolation.\" Steffen's 1990 refinement is conceptually the same;\n * we use a min-clamp variant that's monotonicity-preserving by\n * construction.\n */\n\nimport type { Point } from './catmullRom';\n\n/** Per-axis tangent computation for a 1D series of y-values sampled at\n * unit intervals (index-parameterized). Returns one tangent per\n * anchor. */\nfunction tangents(ys: readonly number[]): number[] {\n const n = ys.length;\n if (n === 0) return [];\n if (n === 1) return [0];\n\n // Secant slopes (dx = 1 in index parameterization).\n const m: number[] = new Array(n - 1);\n for (let i = 0; i < n - 1; i++) m[i] = ys[i + 1] - ys[i];\n\n const t: number[] = new Array(n);\n t[0] = m[0];\n t[n - 1] = m[n - 2];\n\n for (let i = 1; i < n - 1; i++) {\n if (m[i - 1] * m[i] <= 0) {\n // Sign change or flat → local extremum; tangent is zero (forces\n // the curve to settle without overshoot).\n t[i] = 0;\n } else {\n // Weighted average of adjacent secants (with dx_i = dx_{i-1} = 1,\n // this is just the arithmetic mean). Clamped by Steffen's rule\n // so the cubic stays monotone within each segment.\n const avg = (m[i - 1] + m[i]) / 2;\n const mag = Math.min(Math.abs(m[i - 1]), Math.abs(m[i]), Math.abs(avg));\n t[i] = Math.sign(avg) * mag;\n }\n }\n return t;\n}\n\n/** Cubic Hermite basis at parameter t ∈ [0, 1] given endpoint values\n * p0, p1 and endpoint tangents m0, m1. */\nfunction hermite(\n p0: number, p1: number, m0: number, m1: number, t: number,\n): number {\n const t2 = t * t;\n const t3 = t2 * t;\n return (\n (2 * t3 - 3 * t2 + 1) * p0 +\n (t3 - 2 * t2 + t) * m0 +\n (-2 * t3 + 3 * t2) * p1 +\n (t3 - t2) * m1\n );\n}\n\n/**\n * Sample the monotone-cubic curve through `anchors`. Returns\n * `(n-1) * samplesPerSegment + 1` points (same shape as\n * `sampleCatmullRom`). `[]` for fewer than 2 anchors.\n */\nexport function sampleMonotone(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const n = anchors.length;\n const xs = anchors.map((a) => a.x);\n const ys = anchors.map((a) => a.y);\n const tx = tangents(xs);\n const ty = tangents(ys);\n\n const out: Point[] = [];\n for (let i = 0; i < n - 1; i++) {\n const startStep = i === 0 ? 0 : 1; // skip duplicate boundary\n for (let s = startStep; s <= samplesPerSegment; s++) {\n if (s === 0) { out.push(anchors[i]); continue; }\n if (s === samplesPerSegment) { out.push(anchors[i + 1]); continue; }\n const t = s / samplesPerSegment;\n out.push({\n x: hermite(xs[i], xs[i + 1], tx[i], tx[i + 1], t),\n y: hermite(ys[i], ys[i + 1], ty[i], ty[i + 1], t),\n });\n }\n }\n return out;\n}\n","/**\n * Interpolation mode dispatcher for CurveEditor. The vocabulary of\n * supported algorithms and the unified `sample(...)` entry point that\n * picks the right one. All algorithms produce\n * `(n-1) * samplesPerSegment + 1` points and pass through every\n * anchor — CurveEditor is strictly interpolating, never approximating.\n */\n\nimport { sampleCatmullRom, type Point } from './catmullRom';\nimport { sampleMonotone } from './monotone';\n\n/** Supported interpolation modes. All are interpolating (pass through\n * every anchor); the differences are in tangent computation and\n * parameterization. */\nexport type InterpolationMode =\n | 'linear'\n | 'catmull-rom' // centripetal (α=0.5) — safe default\n | 'catmull-rom-uniform' // α=0; can cusp on sharp 2D angles\n | 'catmull-rom-chordal' // α=1; smoother than uniform\n | 'monotone'; // Fritsch-Carlson / Steffen; no overshoot\n\n/** Sample anchors as a polyline. Produces the same sample count shape\n * as the Catmull-Rom variants so downstream segment-slicing logic\n * (hit testing, fill path construction) works uniformly. */\nfunction sampleLinear(\n anchors: readonly Point[],\n samplesPerSegment: number,\n): Point[] {\n if (anchors.length < 2) return [];\n if (samplesPerSegment < 1) samplesPerSegment = 1;\n\n const out: Point[] = [];\n for (let i = 0; i < anchors.length - 1; i++) {\n const a = anchors[i];\n const b = anchors[i + 1];\n const startStep = i === 0 ? 0 : 1;\n for (let s = startStep; s <= samplesPerSegment; s++) {\n if (s === 0) { out.push(a); continue; }\n if (s === samplesPerSegment) { out.push(b); continue; }\n const t = s / samplesPerSegment;\n out.push({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });\n }\n }\n return out;\n}\n\n/** Sample `anchors` using the chosen interpolation mode. */\nexport function sampleByInterpolation(\n anchors: readonly Point[],\n samplesPerSegment: number,\n mode: InterpolationMode,\n): Point[] {\n switch (mode) {\n case 'linear':\n return sampleLinear(anchors, samplesPerSegment);\n case 'catmull-rom-uniform':\n return sampleCatmullRom(anchors, samplesPerSegment, 0);\n case 'catmull-rom-chordal':\n return sampleCatmullRom(anchors, samplesPerSegment, 1);\n case 'monotone':\n return sampleMonotone(anchors, samplesPerSegment);\n case 'catmull-rom':\n default:\n return sampleCatmullRom(anchors, samplesPerSegment, 0.5);\n }\n}\n","/**\n * Curve-specific hit tests. Generic coord transforms live in\n * `../Plot2D/geometry`; what's here is curve-only (anchor / polyline\n * proximity).\n */\nimport type { Point } from '../Plot2D/geometry';\n\n/**\n * Nearest-anchor hit test. Anchors are already in plot/screen coords.\n * Returns the index of the nearest anchor whose squared distance to\n * the pointer is ≤ radius². Null if no anchor is within radius.\n */\nexport function hitTestAnchor(\n anchors: readonly Point[],\n pointer: Point,\n radius: number,\n): { index: number } | null {\n const r2 = radius * radius;\n let best: { index: number; d2: number } | null = null;\n for (let i = 0; i < anchors.length; i++) {\n const a = anchors[i];\n const dx = a.x - pointer.x;\n const dy = a.y - pointer.y;\n const d2 = dx * dx + dy * dy;\n if (d2 > r2) continue;\n if (best === null || d2 < best.d2) best = { index: i, d2 };\n }\n return best ? { index: best.index } : null;\n}\n\n/**\n * Hit test against a per-segment sampled polyline. Each entry in\n * `segments` is the sample list for one segment (inclusive of\n * endpoints). Returns segment index + t (∈ [0, 1]) of the closest\n * point on the polyline within radius. Null if nothing is in range.\n *\n * Distance is measured to the line-segment chords between adjacent\n * samples, not just to the samples themselves — so sparse sampling\n * still produces accurate hits along the chord.\n */\nexport function hitTestCurve(\n segments: readonly (readonly Point[])[],\n pointer: Point,\n radius: number,\n): { segIdx: number; t: number } | null {\n const r2 = radius * radius;\n let best: { segIdx: number; t: number; d2: number } | null = null;\n for (let segIdx = 0; segIdx < segments.length; segIdx++) {\n const samples = segments[segIdx];\n if (samples.length < 2) continue;\n const last = samples.length - 1;\n for (let i = 0; i < last; i++) {\n const a = samples[i];\n const b = samples[i + 1];\n const abx = b.x - a.x;\n const aby = b.y - a.y;\n const apx = pointer.x - a.x;\n const apy = pointer.y - a.y;\n const ab2 = abx * abx + aby * aby;\n let u = ab2 > 0 ? (apx * abx + apy * aby) / ab2 : 0;\n if (u < 0) u = 0;\n else if (u > 1) u = 1;\n const cx = a.x + u * abx;\n const cy = a.y + u * aby;\n const dx = cx - pointer.x;\n const dy = cy - pointer.y;\n const d2 = dx * dx + dy * dy;\n if (d2 > r2) continue;\n const t = (i + u) / last;\n if (best === null || d2 < best.d2) best = { segIdx, t, d2 };\n }\n }\n return best ? { segIdx: best.segIdx, t: best.t } : null;\n}\n",".root {\n /* Component-local vars layered over the theme tokens. Each var resolves\n * to a semantic token from @weasel-js/theme; consumers override\n * either the curve-* var (component scope) or the wzl-* token (app\n * scope) without touching this file.\n *\n * Background / grid / axis tokens live on Plot2D now (this component\n * composes Plot2D, which paints the background and chrome). */\n --curve-line: var(--wzl-curve-color);\n --curve-line-width: var(--wzl-curve-width);\n --curve-fill: color-mix(in srgb, var(--curve-line) 30%, transparent);\n --curve-anchor-fill: var(--wzl-surface);\n --curve-anchor-stroke: var(--curve-line);\n --curve-anchor-radius: 4;\n --curve-anchor-active-fill: var(--curve-line);\n --curve-pinned-fill: var(--wzl-fg-subtle);\n}\n\n.fill {\n fill: var(--curve-fill);\n stroke: none;\n pointer-events: none;\n}\n\n.curve {\n stroke: var(--curve-line);\n stroke-width: var(--curve-line-width);\n fill: none;\n pointer-events: stroke;\n}\n\n.anchor {\n fill: var(--curve-anchor-fill);\n stroke: var(--curve-anchor-stroke);\n stroke-width: 1.5;\n cursor: grab;\n}\n\n.anchor:hover {\n fill: var(--curve-anchor-active-fill);\n}\n\n.anchor.active {\n fill: var(--curve-anchor-active-fill);\n cursor: grabbing;\n}\n\n.anchor.pinned {\n fill: var(--curve-pinned-fill);\n cursor: default;\n}\n\n/* Caller-set locked anchors share the gray pinned fill but signal\n * \"not interactive at all\" via the not-allowed cursor. */\n.anchor.locked {\n cursor: not-allowed;\n}\n\n/* Endpoint vertices render as diamonds (rotated squares) instead of\n * circles, so they read as a different category of handle. The shape\n * is enforced inline via SVG <rect transform>; this class only adds\n * the hover/active styling. */\n.endpoint {\n /* No fill / stroke overrides — endpoint visuals reuse .anchor /\n * .anchor.pinned / .anchor.active styling. The class exists as a\n * marker for future per-endpoint styling needs. */\n}\n\n/* When the consumer opts into hiding non-interactive handles, pinned\n * endpoints disappear entirely. */\n.hidden {\n display: none;\n}\n","/**\n * Built-in function/curve layer for LayeredCurveEditor. Bundles the\n * curve-render + anchor-render + drag/insert/delete gestures that were\n * the entirety of the original `CurveEditor`. Multiple instances coexist\n * by id; consumers can compose several to plot e.g. a primary function\n * and a derived (read-only) curve on the same chart.\n */\nimport type { ReactNode } from 'react';\nimport { sampleByInterpolation, type InterpolationMode } from './interpolation';\nimport { hitTestAnchor, hitTestCurve } from './hitTest';\nimport { dlog } from '../../dlog';\nimport s from './CurveEditor.module.css';\nimport type {\n CurveLayer, LayerCtx, LayerGesture, LayerRenderCtx, ModelPoint, PlotPoint,\n EmptyDownArgs,\n} from './layerTypes';\n\n// ── Public types ─────────────────────────────────────────────────────\n\nexport interface ControlPoint {\n x: number;\n y: number;\n /** When true, this control point can't be moved or deleted by the\n * user. Render as a smaller diamond with locked styling. */\n locked?: boolean;\n}\n\nexport type CurveDomain = '1d' | '2d';\nexport type EndpointMode = 'free' | 'pinned-x' | 'pinned-both';\nexport type AddPointMode = 'click-curve' | 'click-empty' | 'never';\n\nexport interface AnchorRenderProps {\n point: ControlPoint;\n index: number;\n cx: number;\n cy: number;\n isActive: boolean;\n isLocked: boolean;\n isPinnedEndpoint: boolean;\n isEndpoint: boolean;\n}\n\nexport interface CurveSettings {\n // reserved for future per-instance overrides\n}\n\nexport interface FillSettings {\n side: 'below' | 'above';\n color?: string;\n}\n\nexport interface FunctionLayerConfig {\n /** Layer id; default `'function'`. Two instances on the same editor\n * must have distinct ids. */\n id?: string;\n domain?: CurveDomain;\n endpoints?: EndpointMode;\n interpolation?: InterpolationMode;\n constrain?: 'none' | 'function';\n addPointMode?: AddPointMode;\n minPoints?: number;\n maxPoints?: number;\n curve?: CurveSettings | false | null;\n fill?: FillSettings | false | null;\n hideNonInteractive?: boolean;\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** When false, the layer renders the curve+fill but doesn't render\n * anchors and doesn't accept pointer input. For derived read-only\n * curves (e.g. plotting f'(x) alongside f(x)). Default true. */\n interactive?: boolean;\n /** Override x clamp range for drags and pinned endpoints. Defaults\n * to the editor's modelRange x. Use this to constrain a layer to a\n * sub-range of the plot (e.g. a bevel curve occupying x ∈ [0, b]\n * while the editor's xRange is [0, halfWidth]). */\n xClamp?: readonly [number, number];\n}\n\nexport interface FunctionLayerState {\n points: readonly ControlPoint[];\n /** Layer-internal: the index of the anchor currently being dragged.\n * Cleared between gestures. Consumers treat the whole state as\n * opaque and round-trip it via onLayerChange. */\n activeIndex: number | null;\n}\n\nconst SAMPLES_PER_SEGMENT = 16;\nconst ANCHOR_SNAP_PX = 12;\nconst CURVE_HIT_PX = 8;\n\n// ── Helpers ───────────────────────────────────────────────────────────\n\nfunction clampToModelRange(p: ModelPoint, ctx: LayerCtx): ModelPoint {\n const m = ctx.modelRange;\n const xLo = Math.min(m.xMin, m.xMax);\n const xHi = Math.max(m.xMin, m.xMax);\n const yLo = Math.min(m.yMin, m.yMax);\n const yHi = Math.max(m.yMin, m.yMax);\n return {\n x: Math.max(xLo, Math.min(xHi, p.x)),\n y: Math.max(yLo, Math.min(yHi, p.y)),\n };\n}\n\nfunction isPinnedEndpointAt(index: number, count: number, endpoints: EndpointMode): boolean {\n if (endpoints === 'free') return false;\n return index === 0 || index === count - 1;\n}\n\nfunction projectAnchors(points: readonly ControlPoint[], ctx: LayerCtx): PlotPoint[] {\n return points.map((p) => ctx.toPlot(p));\n}\n\nfunction segmentSamples(\n points: readonly ControlPoint[],\n interpolation: InterpolationMode,\n ctx: LayerCtx,\n): PlotPoint[][] {\n if (points.length < 2) return [];\n const all = sampleByInterpolation(points, SAMPLES_PER_SEGMENT, interpolation);\n const out: PlotPoint[][] = [];\n for (let i = 0; i < points.length - 1; i++) {\n const start = i * SAMPLES_PER_SEGMENT;\n const end = start + SAMPLES_PER_SEGMENT;\n const slice = all.slice(start, end + 1);\n out.push(slice.map((p) => ctx.toPlot(p)));\n }\n return out;\n}\n\nfunction resolveInterpolation(cfg: FunctionLayerConfig): InterpolationMode {\n return (cfg.constrain ?? 'none') === 'function'\n ? 'monotone'\n : (cfg.interpolation ?? 'catmull-rom');\n}\n\n// ── Factory ───────────────────────────────────────────────────────────\n\nexport function createFunctionLayer(cfg: FunctionLayerConfig = {}): CurveLayer<FunctionLayerState> {\n const id = cfg.id ?? 'function';\n const domain = cfg.domain ?? '2d';\n const endpoints = cfg.endpoints ?? 'free';\n const constrain = cfg.constrain ?? 'none';\n const addPointMode = cfg.addPointMode ?? 'click-curve';\n const interpolation = resolveInterpolation(cfg);\n const interactive = cfg.interactive !== false;\n const curveVisible = cfg.curve !== false && cfg.curve !== null;\n\n // ── render ──────────────────────────────────────────────────────────\n function render(state: FunctionLayerState, ctx: LayerRenderCtx): ReactNode {\n const { points, activeIndex } = state;\n const plotAnchors = projectAnchors(points, ctx);\n\n let pathD = '';\n let fillD: string | null = null;\n if (points.length >= 2) {\n const samples = sampleByInterpolation(points, SAMPLES_PER_SEGMENT, interpolation)\n .map((p) => ctx.toPlot(p));\n const parts: string[] = [`M${samples[0].x.toFixed(2)},${samples[0].y.toFixed(2)}`];\n for (let i = 1; i < samples.length; i++) {\n parts.push(`L${samples[i].x.toFixed(2)},${samples[i].y.toFixed(2)}`);\n }\n pathD = parts.join('');\n\n if (cfg.fill) {\n const closingY = cfg.fill.side === 'below' ? ctx.plotSize.height : 0;\n const first = samples[0];\n const last = samples[samples.length - 1];\n const fillParts = [...parts];\n fillParts.push(`L${last.x.toFixed(2)},${closingY.toFixed(2)}`);\n fillParts.push(`L${first.x.toFixed(2)},${closingY.toFixed(2)}`);\n fillParts.push('Z');\n fillD = fillParts.join('');\n }\n }\n\n return (\n <>\n {fillD && (\n <path\n data-curve-element=\"fill\"\n className={s.fill}\n d={fillD}\n fill={cfg.fill ? cfg.fill.color : undefined}\n />\n )}\n {pathD && curveVisible && (\n <path\n className={s.curve}\n d={pathD}\n fill=\"none\"\n />\n )}\n {interactive && plotAnchors.map((a, i) => {\n const pinned = isPinnedEndpointAt(i, points.length, endpoints);\n const locked = points[i]?.locked === true;\n if ((locked || pinned) && cfg.hideNonInteractive) return null;\n const active = activeIndex === i;\n const isEndpoint = curveVisible && (i === 0 || i === points.length - 1);\n if (cfg.renderAnchor) {\n const node = cfg.renderAnchor({\n point: points[i], index: i,\n cx: a.x, cy: a.y,\n isActive: active, isLocked: locked,\n isPinnedEndpoint: pinned, isEndpoint,\n });\n if (node !== null && node !== undefined) {\n return <g key={i} data-anchor-index={i}>{node}</g>;\n }\n }\n const anchorCls = [\n s.anchor,\n isEndpoint && s.endpoint,\n (pinned || locked) && s.pinned,\n locked && s.locked,\n active && s.active,\n ].filter(Boolean).join(' ');\n const renderAsDiamond = isEndpoint || locked;\n if (renderAsDiamond) {\n const half = locked ? 3.55 : 5;\n return (\n <rect\n key={i}\n className={anchorCls}\n x={a.x - half}\n y={a.y - half}\n width={half * 2}\n height={half * 2}\n transform={`rotate(45 ${a.x} ${a.y})`}\n data-anchor-index={i}\n />\n );\n }\n return (\n <circle\n key={i}\n className={anchorCls}\n cx={a.x}\n cy={a.y}\n r={4}\n data-anchor-index={i}\n />\n );\n })}\n </>\n );\n }\n\n // ── hit test ────────────────────────────────────────────────────────\n function hitTest(state: FunctionLayerState, plot: PlotPoint, ctx: LayerCtx) {\n if (!interactive) return null;\n const plotAnchors = projectAnchors(state.points, ctx);\n const a = hitTestAnchor(plotAnchors, plot, ANCHOR_SNAP_PX);\n if (a) return { kind: 'anchor' as const, payload: { index: a.index } };\n if (addPointMode === 'click-curve' && curveVisible) {\n const segs = segmentSamples(state.points, interpolation, ctx);\n const c = hitTestCurve(segs, plot, CURVE_HIT_PX);\n if (c) return { kind: 'curve' as const, payload: { segIdx: c.segIdx, t: c.t } };\n }\n return null;\n }\n\n // ── drag gesture ────────────────────────────────────────────────────\n function makeDragGesture(index: number): LayerGesture<FunctionLayerState> {\n return {\n onMove(state, model, _e, ctx) {\n const points = state.points;\n if (index < 0 || index >= points.length) return state;\n const mr = ctx.modelRange;\n const xMin = cfg.xClamp ? cfg.xClamp[0] : mr.xMin;\n const xMax = cfg.xClamp ? cfg.xClamp[1] : mr.xMax;\n let nx = model.x;\n let ny = model.y;\n const isEndpoint = index === 0 || index === points.length - 1;\n\n if (endpoints === 'pinned-both' && isEndpoint) {\n nx = index === 0 ? xMin : xMax;\n ny = index === 0 ? mr.yMin : mr.yMax;\n } else if (endpoints === 'pinned-x' && isEndpoint) {\n nx = index === 0 ? xMin : xMax;\n } else if (constrain === 'function' || domain === '1d') {\n const epsilon = constrain === 'function'\n ? (xMax - xMin) / 1000\n : 0;\n const left = index > 0 ? points[index - 1].x + epsilon : xMin;\n const right = index < points.length - 1 ? points[index + 1].x - epsilon : xMax;\n nx = Math.max(left, Math.min(right, nx));\n }\n\n // Always clamp x to the layer's own range (so free-mode\n // interior anchors of a sub-range layer can't escape it).\n // Normalize bounds for inverted ranges.\n {\n const lo = Math.min(xMin, xMax);\n const hi = Math.max(xMin, xMax);\n nx = Math.max(lo, Math.min(hi, nx));\n }\n const clamped = clampToModelRange({ x: nx, y: ny }, ctx);\n const next = points.slice();\n next[index] = { ...points[index], x: clamped.x, y: clamped.y };\n return { ...state, points: next, activeIndex: index };\n },\n onCommit(state) { return { ...state, activeIndex: null }; },\n onCancel(state) { return { ...state, activeIndex: null }; },\n };\n }\n\n // ── pointerdown on a positive hit ───────────────────────────────────\n function onPointerDown(\n state: FunctionLayerState,\n hit: { kind: string; payload?: unknown },\n e: PointerEvent,\n ctx: LayerCtx,\n extra: EmptyDownArgs<FunctionLayerState>,\n ): LayerGesture<FunctionLayerState> | void {\n if (hit.kind === 'anchor') {\n const { index } = hit.payload as { index: number };\n dlog('function-layer', 'anchor-down', { id, index, shift: ctx.modifiers.shift, button: e.button });\n if (state.points[index]?.locked) return;\n if (ctx.modifiers.shift) {\n if (isPinnedEndpointAt(index, state.points.length, endpoints)) return;\n if (cfg.minPoints !== undefined && state.points.length <= cfg.minPoints) return;\n extra.commit({\n ...state,\n points: state.points.filter((_, i) => i !== index),\n activeIndex: null,\n });\n return;\n }\n if (e.button === 2) return;\n extra.commit({ ...state, activeIndex: index });\n return makeDragGesture(index);\n }\n if (hit.kind === 'curve') {\n const { segIdx } = hit.payload as { segIdx: number; t: number };\n if (cfg.maxPoints !== undefined && state.points.length >= cfg.maxPoints) return;\n const insertIndex = segIdx + 1;\n const modelPt = clampToModelRange(extra.model, ctx);\n\n // function/1d: refuse insert that would be immediately clamped\n // against a neighbor (mouse jitter would otherwise drop a phantom\n // point directly above an existing anchor). Snap to dragging the\n // neighbor instead.\n if (constrain === 'function' || domain === '1d') {\n const epsilon = constrain === 'function'\n ? (ctx.modelRange.xMax - ctx.modelRange.xMin) / 1000\n : 0;\n const left = insertIndex > 0 ? state.points[insertIndex - 1] : null;\n const right = insertIndex < state.points.length ? state.points[insertIndex] : null;\n if (left && Math.abs(modelPt.x - left.x) < epsilon * 2) {\n extra.commit({ ...state, activeIndex: insertIndex - 1 });\n return makeDragGesture(insertIndex - 1);\n }\n if (right && Math.abs(modelPt.x - right.x) < epsilon * 2) {\n extra.commit({ ...state, activeIndex: insertIndex });\n return makeDragGesture(insertIndex);\n }\n }\n\n const nextPoints = [...state.points.slice(0, insertIndex), modelPt, ...state.points.slice(insertIndex)];\n extra.commit({ ...state, points: nextPoints, activeIndex: insertIndex });\n return makeDragGesture(insertIndex);\n }\n }\n\n // ── empty-space click ───────────────────────────────────────────────\n function onEmptyPointerDown(\n state: FunctionLayerState,\n model: ModelPoint,\n _e: PointerEvent,\n ctx: LayerCtx,\n extra: EmptyDownArgs<FunctionLayerState>,\n ): LayerGesture<FunctionLayerState> | void {\n if (!interactive) return;\n if (addPointMode !== 'click-empty') return;\n if (cfg.maxPoints !== undefined && state.points.length >= cfg.maxPoints) return;\n\n const clamped = clampToModelRange(model, ctx);\n let insertIndex = state.points.length;\n if (domain === '1d') {\n for (let i = 0; i < state.points.length; i++) {\n if (state.points[i].x > clamped.x) { insertIndex = i; break; }\n }\n }\n const nextPoints = [...state.points.slice(0, insertIndex), clamped, ...state.points.slice(insertIndex)];\n extra.commit({ ...state, points: nextPoints, activeIndex: null });\n return;\n }\n\n return {\n id,\n render,\n hitTest,\n onPointerDown,\n onEmptyPointerDown,\n };\n}\n\n/** Convenience: build a function-layer state from a points array. */\nexport function functionLayerState(points: readonly ControlPoint[]): FunctionLayerState {\n return { points, activeIndex: null };\n}\n","import {\n useCallback, useEffect, useMemo, useRef, useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from 'react';\nimport {\n Plot2D,\n type Plot2DHandle,\n type GridSettings,\n type AxesSettings,\n} from '../Plot2D';\nimport {\n modelToPlot, plotToModel,\n type ModelRange,\n} from '../Plot2D/geometry';\nimport { dlog } from '../../dlog';\nimport s from './CurveEditor.module.css';\nimport type {\n CurveLayer, LayerCtx, LayerGesture, LayerModifiers, ModelPoint, PlotPoint,\n} from './layerTypes';\n\nexport interface LayerBinding<S = unknown> {\n layer: CurveLayer<S>;\n state: S;\n}\n\nexport interface LayeredCurveEditorProps {\n /** Bottom-to-top render order: `layers[0]` paints first, `layers[N-1]`\n * paints on top. Hit-testing runs in reverse (topmost claim wins). */\n layers: ReadonlyArray<LayerBinding<any>>;\n\n /** Fires every frame during a gesture with the live in-flight state\n * for the layer that owns the gesture. */\n onLayerChange(id: string, next: unknown): void;\n\n /** Fires once per discrete user action (drag-end, one-shot insert,\n * delete) for the layer that produced the change. `prev` is the\n * layer's state at gesture start. */\n onLayerCommit?(id: string, next: unknown, prev: unknown): void;\n\n width: number;\n height: number;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n grid?: GridSettings | false | null;\n axes?: AxesSettings | false | null;\n\n /** Built-in undo/redo via Cmd/Ctrl+Z (Shift+Z or Y for redo) on the\n * focused SVG. Snapshots every layer's state on each commit; undo\n * fires `onLayerChange(id, prevState)` for each layer whose state\n * changed. Default `true`. */\n history?: boolean;\n\n className?: string;\n style?: CSSProperties;\n\n /** Extra SVG content rendered behind all layers (under the lowest\n * layer but above grid/axes). Use this for static chrome that isn't\n * worth modeling as a layer. */\n children?: ReactNode;\n}\n\ninterface ActiveGesture {\n layerId: string;\n pointerId: number;\n gesture: LayerGesture<any>;\n startStates: Map<string, unknown>;\n startSelfState: unknown;\n}\n\nfunction readModifiers(e: PointerEvent | ReactPointerEvent<any> | KeyboardEvent | ReactKeyboardEvent<any>): LayerModifiers {\n return {\n shift: e.shiftKey,\n alt: e.altKey,\n meta: e.metaKey,\n ctrl: e.ctrlKey,\n };\n}\n\nexport function LayeredCurveEditor(props: LayeredCurveEditorProps) {\n const {\n layers, onLayerChange, onLayerCommit,\n width, height,\n xRange = [0, 1], yRange = [0, 1],\n grid, axes,\n className, style, children,\n } = props;\n\n const historyEnabled = props.history !== false;\n\n const modelRange: ModelRange = useMemo(\n () => ({ xMin: xRange[0], xMax: xRange[1], yMin: yRange[0], yMax: yRange[1] }),\n [xRange, yRange],\n );\n const plotSize = useMemo(() => ({ width, height }), [width, height]);\n\n // Refs that always hold the latest props — gesture handlers and\n // window listeners close over these refs, never over render-time\n // values, so mid-drag re-renders (including cross-layer state\n // updates) are visible to the next pointermove tick.\n const layersRef = useRef(layers);\n layersRef.current = layers;\n const modelRangeRef = useRef(modelRange);\n modelRangeRef.current = modelRange;\n const plotSizeRef = useRef(plotSize);\n plotSizeRef.current = plotSize;\n\n const plotRef = useRef<Plot2DHandle | null>(null);\n\n // ── Coord helpers ──────────────────────────────────────────────────\n const makeCtx = useCallback((modifiers: LayerModifiers): LayerCtx => {\n const mr = modelRangeRef.current;\n const ps = plotSizeRef.current;\n return {\n modelRange: mr,\n plotSize: ps,\n toPlot: (p) => modelToPlot(p, mr, ps),\n toModel: (p) => plotToModel(p, mr, ps),\n modifiers,\n };\n }, []);\n\n // ── Gesture routing ────────────────────────────────────────────────\n const activeRef = useRef(null as ActiveGesture | null);\n const [activeLayerId, setActiveLayerId] = useState<string | null>(null);\n\n // ── History (whole-state snapshots) ────────────────────────────────\n const pastRef = useRef<Map<string, unknown>[]>([]);\n const futureRef = useRef<Map<string, unknown>[]>([]);\n\n const snapshot = useCallback((): Map<string, unknown> => {\n const m = new Map<string, unknown>();\n for (const b of layersRef.current) m.set(b.layer.id, b.state);\n return m;\n }, []);\n\n const restore = useCallback((snap: Map<string, unknown>) => {\n const current = layersRef.current;\n for (const b of current) {\n const prev = snap.get(b.layer.id);\n if (prev !== undefined && prev !== b.state) {\n onLayerChange(b.layer.id, prev);\n }\n }\n }, [onLayerChange]);\n\n const undo = useCallback(() => {\n if (!historyEnabled) return;\n const past = pastRef.current;\n if (past.length === 0) return;\n const prev = past.pop()!;\n futureRef.current.push(snapshot());\n restore(prev);\n }, [historyEnabled, snapshot, restore]);\n\n const redo = useCallback(() => {\n if (!historyEnabled) return;\n const future = futureRef.current;\n if (future.length === 0) return;\n const next = future.pop()!;\n pastRef.current.push(snapshot());\n restore(next);\n }, [historyEnabled, snapshot, restore]);\n\n // ── Window-level pointer routing during an active gesture ──────────\n // Stable identity across renders so addEventListener / removeEventListener\n // see the same function. Delegate to refs that point at the latest\n // per-render impl.\n const onWindowMoveRef = useRef<(e: PointerEvent) => void>(() => {});\n const onWindowUpRef = useRef<(e: PointerEvent) => void>(() => {});\n const onWindowCancelRef = useRef<(e: PointerEvent) => void>(() => {});\n\n const stableMove = useRef((e: PointerEvent) => onWindowMoveRef.current(e)).current;\n const stableUp = useRef((e: PointerEvent) => onWindowUpRef.current(e)).current;\n const stableCancel = useRef((e: PointerEvent) => onWindowCancelRef.current(e)).current;\n\n const cleanupGesture = useCallback(() => {\n window.removeEventListener('pointermove', stableMove);\n window.removeEventListener('pointerup', stableUp);\n window.removeEventListener('pointercancel', stableCancel);\n activeRef.current = null;\n setActiveLayerId(null);\n }, [stableMove, stableUp, stableCancel]);\n\n const findLayerState = useCallback((id: string): unknown | undefined => {\n for (const b of layersRef.current) if (b.layer.id === id) return b.state;\n return undefined;\n }, []);\n\n onWindowMoveRef.current = (e: PointerEvent) => {\n const a = activeRef.current;\n if (!a || a.pointerId !== e.pointerId) return;\n const h = plotRef.current;\n if (!h) return;\n const model = h.clientToModel(e);\n const current = findLayerState(a.layerId);\n if (current === undefined) return;\n const ctx = makeCtx(readModifiers(e));\n const next = a.gesture.onMove(current, model, e, ctx);\n if (next !== current) onLayerChange(a.layerId, next);\n };\n\n onWindowUpRef.current = (e: PointerEvent) => {\n const a = activeRef.current;\n if (!a || a.pointerId !== e.pointerId) return;\n const current = findLayerState(a.layerId);\n if (current === undefined) { cleanupGesture(); return; }\n const ctx = makeCtx(readModifiers(e));\n const finalState = a.gesture.onCommit ? a.gesture.onCommit(current, ctx) : current;\n if (finalState !== current) onLayerChange(a.layerId, finalState);\n if (historyEnabled) {\n pastRef.current.push(a.startStates);\n futureRef.current = [];\n }\n if (onLayerCommit) onLayerCommit(a.layerId, finalState, a.startSelfState);\n cleanupGesture();\n };\n\n onWindowCancelRef.current = (e: PointerEvent) => {\n const a = activeRef.current;\n if (!a || a.pointerId !== e.pointerId) return;\n const current = findLayerState(a.layerId);\n if (current === undefined) { cleanupGesture(); return; }\n const ctx = makeCtx(readModifiers(e));\n const restored = a.gesture.onCancel ? a.gesture.onCancel(current, ctx) : a.startSelfState;\n if (restored !== current) onLayerChange(a.layerId, restored);\n cleanupGesture();\n };\n\n const installGesture = useCallback((layerId: string, pointerId: number, gesture: LayerGesture<any>) => {\n const startStates = snapshot();\n const startSelfState = startStates.get(layerId);\n activeRef.current = {\n layerId, pointerId, gesture,\n startStates,\n startSelfState,\n };\n setActiveLayerId(layerId);\n window.addEventListener('pointermove', stableMove);\n window.addEventListener('pointerup', stableUp);\n window.addEventListener('pointercancel', stableCancel);\n }, [snapshot, stableMove, stableUp, stableCancel]);\n\n // ── pointerdown dispatch ───────────────────────────────────────────\n const onSvgPointerDown = useCallback((\n e: ReactPointerEvent<SVGSVGElement>,\n coords: { plot: PlotPoint; model: ModelPoint },\n ) => {\n if (activeRef.current) return; // ignore secondary touches mid-gesture\n const modifiers = readModifiers(e);\n const ctx = makeCtx(modifiers);\n const ls = layersRef.current;\n const nativeEvent = e.nativeEvent;\n\n dlog('layered-curve-editor', 'pointerdown', { plot: coords.plot, model: coords.model, layers: ls.length });\n\n // Capture the pre-event snapshot once; reuse for history if any\n // path produces a commit (gesture-start commit or one-shot).\n const preEventSnapshot = snapshot();\n\n // Top-to-bottom hit test.\n for (let i = ls.length - 1; i >= 0; i--) {\n const b = ls[i];\n const hit = b.layer.hitTest?.(b.state, coords.plot, ctx);\n if (!hit) continue;\n // Found a claim. onPointerDown may start a gesture, absorb the\n // click, or commit a one-shot edit via extra.commit.\n const prevState = b.state;\n let committed: unknown = undefined;\n const gesture = b.layer.onPointerDown?.(\n prevState, hit, nativeEvent, ctx,\n {\n plot: coords.plot,\n model: coords.model,\n commit(next) { committed = next; onLayerChange(b.layer.id, next); },\n },\n );\n e.stopPropagation();\n if (gesture) {\n installGesture(b.layer.id, e.pointerId, gesture);\n return;\n }\n if (committed !== undefined) {\n if (historyEnabled) {\n pastRef.current.push(preEventSnapshot);\n futureRef.current = [];\n }\n onLayerCommit?.(b.layer.id, committed, prevState);\n }\n return;\n }\n\n // No hit — empty space dispatch, top-to-bottom.\n let pendingCommit: { id: string; next: unknown; prev: unknown } | null = null;\n for (let i = ls.length - 1; i >= 0; i--) {\n const b = ls[i];\n if (!b.layer.onEmptyPointerDown) continue;\n const prevState = b.state;\n let committed: unknown = undefined;\n const gesture = b.layer.onEmptyPointerDown(\n prevState, coords.model, nativeEvent, ctx,\n {\n plot: coords.plot,\n model: coords.model,\n commit(next) {\n committed = next;\n onLayerChange(b.layer.id, next);\n },\n },\n );\n if (gesture) {\n // If onEmptyPointerDown also published a commit (e.g. click-curve\n // insert publishes the new point then drags it), the gesture's\n // startStates already capture the post-insert state; the commit\n // for history is the *pre*-event snapshot, written when the\n // gesture eventually finishes via the gesture-commit path. We\n // override the gesture's startStates so undo restores correctly.\n installGesture(b.layer.id, e.pointerId, gesture);\n if (committed !== undefined && activeRef.current !== null) {\n (activeRef.current as ActiveGesture).startStates = preEventSnapshot;\n (activeRef.current as ActiveGesture).startSelfState = preEventSnapshot.get(b.layer.id);\n }\n return;\n }\n if (committed !== undefined) {\n pendingCommit = { id: b.layer.id, next: committed, prev: prevState };\n break;\n }\n }\n if (pendingCommit) {\n if (historyEnabled) {\n pastRef.current.push(preEventSnapshot);\n futureRef.current = [];\n }\n onLayerCommit?.(pendingCommit.id, pendingCommit.next, pendingCommit.prev);\n }\n }, [makeCtx, installGesture, onLayerChange, onLayerCommit, historyEnabled, snapshot]);\n\n // ── keyboard ───────────────────────────────────────────────────────\n const onSvgKeyDown = useCallback((e: ReactKeyboardEvent<SVGSVGElement>) => {\n const modifiers = readModifiers(e);\n const ctx = makeCtx(modifiers);\n const ls = layersRef.current;\n // History first.\n if (historyEnabled && (e.metaKey || e.ctrlKey)) {\n const k = e.key.toLowerCase();\n if (k === 'z') {\n if (e.shiftKey) redo();\n else undo();\n e.preventDefault();\n return;\n }\n if (k === 'y') { redo(); e.preventDefault(); return; }\n }\n // Then layers (top-to-bottom).\n for (let i = ls.length - 1; i >= 0; i--) {\n const b = ls[i];\n b.layer.onKeyDown?.(b.state, e.nativeEvent, ctx);\n if (e.isDefaultPrevented()) return;\n }\n }, [historyEnabled, undo, redo, makeCtx]);\n\n // Clean up listeners on unmount.\n useEffect(() => () => {\n window.removeEventListener('pointermove', stableMove);\n window.removeEventListener('pointerup', stableUp);\n window.removeEventListener('pointercancel', stableCancel);\n }, [stableMove, stableUp, stableCancel]);\n\n // ── render ─────────────────────────────────────────────────────────\n const renderCtxBase: Omit<LayerCtx, 'modifiers'> = useMemo(() => ({\n modelRange,\n plotSize,\n toPlot: (p) => modelToPlot(p, modelRange, plotSize),\n toModel: (p) => plotToModel(p, modelRange, plotSize),\n }), [modelRange, plotSize]);\n\n const cls = [s.root, className].filter(Boolean).join(' ');\n\n return (\n <Plot2D\n ref={plotRef}\n className={cls}\n style={style}\n width={width}\n height={height}\n xRange={xRange}\n yRange={yRange}\n grid={grid}\n axes={axes}\n tabIndex={historyEnabled ? 0 : undefined}\n onPointerDown={onSvgPointerDown}\n onKeyDown={onSvgKeyDown}\n >\n {children}\n {layers.map((b) => {\n const ctx = {\n ...renderCtxBase,\n modifiers: { shift: false, alt: false, meta: false, ctrl: false },\n isActive: activeLayerId === b.layer.id,\n };\n return (\n <g key={b.layer.id} data-layer-id={b.layer.id}>\n {b.layer.render(b.state, ctx)}\n </g>\n );\n })}\n </Plot2D>\n );\n}\n","/**\n * Back-compat wrapper around `LayeredCurveEditor` that preserves the\n * original single-curve API. New consumers wanting multi-layer\n * composition should reach for `LayeredCurveEditor` + `createFunctionLayer`\n * directly.\n */\nimport {\n useCallback, useEffect, useMemo, useRef, useState,\n type CSSProperties,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from 'react';\nimport {\n type GridSettings,\n type AxesSettings,\n} from '../Plot2D';\nimport type { InterpolationMode } from './interpolation';\nimport {\n createFunctionLayer,\n type ControlPoint,\n type CurveDomain,\n type EndpointMode,\n type AddPointMode,\n type CurveSettings,\n type FillSettings,\n type AnchorRenderProps,\n type FunctionLayerState,\n} from './createFunctionLayer';\nimport { LayeredCurveEditor } from './LayeredCurveEditor';\n\nexport type { GridSettings, AxesSettings };\nexport type {\n ControlPoint, CurveDomain, EndpointMode, AddPointMode,\n CurveSettings, FillSettings, AnchorRenderProps,\n};\n\nexport interface CurveEditorProps {\n value: readonly ControlPoint[];\n onChange: (next: ControlPoint[]) => void;\n onChangeCommit?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;\n domain?: CurveDomain;\n interpolation?: InterpolationMode;\n endpoints?: EndpointMode;\n xRange?: readonly [number, number];\n yRange?: readonly [number, number];\n width: number;\n height: number;\n grid?: GridSettings | false | null;\n axes?: AxesSettings | false | null;\n curve?: CurveSettings | false | null;\n fill?: FillSettings | false | null;\n hideNonInteractive?: boolean;\n constrain?: 'none' | 'function';\n history?: boolean;\n minPoints?: number;\n maxPoints?: number;\n addPointMode?: AddPointMode;\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** Extra SVG content rendered behind the curve and anchors. */\n decorations?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\nexport function CurveEditor(props: CurveEditorProps) {\n const {\n value, onChange, onChangeCommit,\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n width, height, xRange, yRange, grid, axes, history,\n className, style, decorations,\n } = props;\n\n // Layer is config-only; rebuild only when *config* changes.\n const layer = useMemo(() => createFunctionLayer({\n id: 'function',\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n }), [\n domain, endpoints, interpolation, constrain, addPointMode,\n minPoints, maxPoints, curve, fill, hideNonInteractive, renderAnchor,\n ]);\n\n // Hybrid controlled-state: the wrapper holds the full FunctionLayerState\n // locally so that consumers using the original CurveEditor API don't\n // have to wire their `value` prop back through on every drag tick to\n // keep the in-flight gesture advancing. We adopt the consumer's\n // `value` prop when it changes externally (and no gesture is active).\n const [layerState, setLayerState] = useState<FunctionLayerState>(\n () => ({ points: value, activeIndex: null }),\n );\n // Ref mirror so the layerChange callback can compare against the\n // freshest state without re-binding on every render.\n const layerStateRef = useRef(layerState);\n layerStateRef.current = layerState;\n\n // Sync from controlled `value` when the consumer updates it externally\n // and we're idle. Skip the update during a gesture so drag-in-flight\n // points aren't clobbered by a stale `value`.\n useEffect(() => {\n if (layerStateRef.current.activeIndex !== null) return;\n if (layerStateRef.current.points === value) return;\n setLayerState({ points: value, activeIndex: null });\n }, [value]);\n\n const handleLayerChange = useCallback((_id: string, nextUnknown: unknown) => {\n const next = nextUnknown as FunctionLayerState;\n const prevPoints = layerStateRef.current.points;\n setLayerState(next);\n if (next.points !== prevPoints) {\n onChange(next.points as ControlPoint[]);\n }\n }, [onChange]);\n\n const handleLayerCommit = useCallback((_id: string, nextUnknown: unknown, prevUnknown: unknown) => {\n if (!onChangeCommit) return;\n const next = nextUnknown as FunctionLayerState;\n const prev = prevUnknown as FunctionLayerState;\n if (next.points !== prev.points) {\n onChangeCommit(next.points as ControlPoint[], prev.points);\n }\n }, [onChangeCommit]);\n\n // Right-click on an anchor → delete (preserves original CurveEditor\n // behavior). LayeredCurveEditor doesn't model contextmenu as a\n // gesture; we intercept at the wrapper level by walking the DOM\n // target for `data-anchor-index`.\n const onContextMenu = useCallback((e: ReactMouseEvent<HTMLDivElement>) => {\n const target = e.target as Element | null;\n const node = target?.closest?.('[data-anchor-index]') as Element | null;\n if (!node) return;\n e.preventDefault();\n e.stopPropagation();\n const idxAttr = node.getAttribute('data-anchor-index');\n if (idxAttr === null) return;\n const idx = Number(idxAttr);\n const points = layerStateRef.current.points;\n if (!Number.isInteger(idx) || idx < 0 || idx >= points.length) return;\n if (points[idx]?.locked) return;\n if (endpoints !== undefined && endpoints !== 'free' && (idx === 0 || idx === points.length - 1)) return;\n if (minPoints !== undefined && points.length <= minPoints) return;\n const next = points.filter((_, i) => i !== idx);\n setLayerState({ points: next, activeIndex: null });\n onChange(next as ControlPoint[]);\n onChangeCommit?.(next as ControlPoint[], points);\n }, [endpoints, minPoints, onChange, onChangeCommit]);\n\n const layers = useMemo(() => [{ layer, state: layerState }], [layer, layerState]);\n\n return (\n <div\n onContextMenu={onContextMenu}\n style={{ display: 'contents' }}\n >\n <LayeredCurveEditor\n layers={layers}\n onLayerChange={handleLayerChange}\n onLayerCommit={handleLayerCommit}\n width={width}\n height={height}\n xRange={xRange}\n yRange={yRange}\n grid={grid}\n axes={axes}\n history={history}\n className={className}\n style={style}\n >\n {decorations}\n </LayeredCurveEditor>\n </div>\n );\n}\n","import type { Op } from '@weasel-js/core';\nimport type { ControlPoint } from './CurveEditor';\n\n/**\n * The adapter contract for {@link createSetCurveOp}. Consumers wire\n * this to whatever state container holds the curve's value — typically\n * a small callback that calls a React state setter or dispatches a\n * Redux/Zustand action.\n */\nexport interface SetCurveAdapter {\n setValue(id: string, next: readonly ControlPoint[]): void;\n}\n\nexport interface CreateSetCurveOpArgs {\n /** Stable id the consumer uses for this curve. Used so a single\n * adapter can handle multiple curves; the op's apply routes by id. */\n id: string;\n from: readonly ControlPoint[];\n to: readonly ControlPoint[];\n label?: string;\n coalesceKey?: string;\n}\n\n/**\n * Op factory for editing a CurveEditor's value through weasel-history.\n *\n * history.applyOps(\n * [createSetCurveOp({ id: 'easing', from, to, label: 'Edit easing' })],\n * 'Edit easing',\n * );\n *\n * The op's `apply(adapter)` calls `adapter.setValue(id, to)`. `invert()`\n * returns a mirror op with `from`/`to` swapped. When `from` and `to`\n * are structurally equal, `apply` returns `false` so history can skip\n * the entry — same convention as `createSetPathOp`.\n */\nexport function createSetCurveOp(args: CreateSetCurveOpArgs): Op {\n const { id, from, to, label, coalesceKey } = args;\n return {\n name: 'setCurve',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n if (controlPointsEqual(from, to)) return false;\n (adapter as SetCurveAdapter).setValue(id, to);\n return undefined;\n },\n invert() {\n return createSetCurveOp({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nfunction controlPointsEqual(\n a: readonly ControlPoint[],\n b: readonly ControlPoint[],\n): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i].x !== b[i].x || a[i].y !== b[i].y) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog-BFuakycR.js","names":[],"sources":["../../src/components/Dialog/Dialog.module.css","../../src/components/Dialog/Dialog.tsx"],"sourcesContent":[".overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n opacity: 0;\n transition: opacity var(--wzl-motion-fast);\n}\n\n.overlay[data-entering] {\n opacity: 0;\n}\n\n.overlay[data-entering][data-rac] {\n opacity: 1;\n}\n\n.overlay[data-exiting] {\n opacity: 0;\n}\n\n.overlay[data-rac]:not([data-entering]):not([data-exiting]) {\n opacity: 1;\n}\n\n.modal {\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n max-width: min(900px, calc(100vw - 32px));\n max-height: calc(100vh - 32px);\n display: flex;\n flex-direction: column;\n outline: none;\n}\n\n.dialog {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1;\n outline: none;\n}\n\n.header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-bottom: 1px solid var(--wzl-border);\n flex: 0 0 auto;\n}\n\n.title {\n flex: 1;\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--wzl-fg);\n}\n\n.close {\n flex: 0 0 auto;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid transparent;\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n font-size: 16px;\n line-height: 1;\n}\n\n.close:hover {\n background: color-mix(in srgb, var(--wzl-fg) 10%, transparent);\n color: var(--wzl-fg);\n}\n\n.body {\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding: 16px;\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 16px;\n border-top: 1px solid var(--wzl-border);\n flex: 0 0 auto;\n}\n","import type { ReactNode } from 'react';\nimport {\n Modal as RACModal,\n ModalOverlay,\n Dialog as RACDialog,\n Heading,\n type ModalOverlayProps,\n type DialogProps as RACDialogProps,\n} from 'react-aria-components';\nimport s from './Dialog.module.css';\n\nexport type DialogProps = Omit<ModalOverlayProps, 'children' | 'className'> & {\n /** Heading rendered in the dialog's default header. Omit when supplying\n * a custom `header` slot via children. */\n title?: ReactNode;\n /** Show the built-in close button. Defaults to true when `onOpenChange`\n * is wired so the user has an escape hatch. */\n showCloseButton?: boolean;\n /** Body content. */\n children?: ReactNode;\n /** Footer slot — typically action buttons. */\n footer?: ReactNode;\n /** ARIA role. Defaults to `dialog`. Use `alertdialog` for confirms. */\n role?: RACDialogProps['role'];\n /** Class applied to the modal box (inside the overlay). */\n className?: string;\n};\n\n/**\n * Modal dialog wrapping React Aria. Supplies a default header (title +\n * close), scrollable body, and footer slot. Pass `isOpen` + `onOpenChange`\n * for controlled visibility, or omit both and use a `<DialogTrigger>` from\n * `react-aria-components` upstream of this component.\n *\n * Escape, click-outside, focus trap, and scroll lock all come from the\n * underlying primitives.\n */\nexport function Dialog(props: DialogProps) {\n const {\n title,\n showCloseButton,\n children,\n footer,\n role = 'dialog',\n className,\n isOpen,\n onOpenChange,\n ...rest\n } = props;\n\n const showClose = showCloseButton ?? Boolean(onOpenChange);\n\n return (\n <ModalOverlay {...rest} isOpen={isOpen} onOpenChange={onOpenChange} className={s.overlay}>\n <RACModal className={[s.modal, className].filter(Boolean).join(' ')}>\n <RACDialog role={role} className={s.dialog}>\n {({ close }) => (\n <>\n {(title !== undefined || showClose) && (\n <header className={s.header}>\n {title !== undefined && (\n <Heading slot=\"title\" className={s.title}>{title}</Heading>\n )}\n {showClose && (\n <button\n type=\"button\"\n className={s.close}\n onClick={close}\n aria-label=\"Close dialog\"\n >\n ×\n </button>\n )}\n </header>\n )}\n <div className={s.body}>{children}</div>\n {footer !== undefined && <footer className={s.footer}>{footer}</footer>}\n </>\n )}\n </RACDialog>\n </RACModal>\n </ModalOverlay>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;ACqCA,SAAgB,EAAO,GAAoB;CACzC,IAAM,EACJ,UACA,oBACA,aACA,WACA,UAAO,UACP,cACA,WACA,iBACA,GAAG,MACD,GAEE,IAAY,KAAmB,EAAQ;AAE7C,QACE,kBAAC,GAAD;EAAc,GAAI;EAAc;EAAsB;EAAc,WAAW,EAAE;YAC/E,kBAAC,GAAD;GAAU,WAAW,CAAC,EAAE,OAAO,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;aACjE,kBAAC,GAAD;IAAiB;IAAM,WAAW,EAAE;eAChC,EAAE,eACF,kBAAA,GAAA,EAAA,UAAA;MACI,MAAU,KAAA,KAAa,MACvB,kBAAC,UAAD;MAAQ,WAAW,EAAE;gBAArB,CACG,MAAU,KAAA,KACT,kBAAC,GAAD;OAAS,MAAK;OAAQ,WAAW,EAAE;iBAAQ;OAAgB,CAAA,EAE5D,KACC,kBAAC,UAAD;OACE,MAAK;OACL,WAAW,EAAE;OACb,SAAS;OACT,cAAW;iBACZ;OAEQ,CAAA,CAEJ;;KAEX,kBAAC,OAAD;MAAK,WAAW,EAAE;MAAO;MAAe,CAAA;KACvC,MAAW,KAAA,KAAa,kBAAC,UAAD;MAAQ,WAAW,EAAE;gBAAS;MAAgB,CAAA;KACtE,EAAA,CAAA;IAEK,CAAA;GACH,CAAA;EACE,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Field-DPxmJzZz.js","names":[],"sources":["../../src/components/Field/Field.module.css","../../src/components/Field/Field.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.label {\n font-size: 12px;\n font-weight: 500;\n color: var(--wzl-fg);\n cursor: default;\n}\n\n.hint {\n font-size: 11px;\n color: var(--wzl-fg-subtle);\n line-height: 1.4;\n}\n\n.error {\n font-size: 11px;\n color: var(--wzl-danger, #ff5b5b);\n line-height: 1.4;\n}\n\n.row {\n flex-direction: row;\n align-items: center;\n gap: 8px;\n}\n\n.row .label {\n flex: 1;\n min-width: 0;\n}\n","import type { ReactNode } from 'react';\nimport s from './Field.module.css';\n\nexport type FieldOrientation = 'stacked' | 'row';\n\nexport type FieldProps = {\n orientation?: FieldOrientation;\n className?: string;\n children?: ReactNode;\n};\n\n/**\n * Layout primitive that arranges a label, control, hint, and error in the\n * canonical weasel-ui form-row shape. The control itself owns its semantics\n * (RAC `TextField`, `Checkbox`, etc.); this just paints the layout.\n *\n * Most users will instead reach for `Input` / `Checkbox` / `Switch`, which\n * compose Field internally. Use Field directly when you need a custom\n * control to sit in the same visual rhythm.\n */\nexport function Field({ orientation = 'stacked', className, children }: FieldProps) {\n const cls = [s.field, orientation === 'row' && s.row, className].filter(Boolean).join(' ');\n return <div className={cls}>{children}</div>;\n}\n\nexport const fieldClasses = {\n root: s.field,\n row: s.row,\n label: s.label,\n hint: s.hint,\n error: s.error,\n};\n"],"mappings":";;;;;;;;;;ACoBA,SAAgB,EAAM,EAAE,iBAAc,WAAW,cAAW,eAAwB;AAElF,QAAO,kBAAC,OAAD;EAAK,WADA;GAAC,EAAE;GAAO,MAAgB,SAAS,EAAE;GAAK;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAC/D;EAAM;EAAe,CAAA;;AAG9C,IAAa,IAAe;CAC1B,MAAM,EAAE;CACR,KAAK,EAAE;CACP,OAAO,EAAE;CACT,MAAM,EAAE;CACR,OAAO,EAAE;CACV"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GradientEditor-3pYXGAPg.js","names":[],"sources":["../../src/paintGradientTrack.tsx","../../src/components/GradientEditor/GradientEditor.module.css","../../src/components/GradientEditor/GradientEditor.tsx","../../src/components/GradientEditor/GradientHandles.module.css","../../src/components/GradientEditor/GradientHandles.tsx"],"sourcesContent":["import type { ReactNode, CSSProperties } from 'react';\nimport type { TrackCtx } from './components/Slider';\n\nexport type GradientTrackOpts = {\n gradient: (t: number) => string;\n samples?: number;\n activeRange?: [number, number];\n hatch?: {\n angleDeg?: number;\n stripe?: number;\n gap?: number;\n dim?: number;\n };\n};\n\nconst DEFAULT_HATCH = { angleDeg: 135, stripe: 2, gap: 4, dim: 75 };\n\nexport function paintGradientTrack(opts: GradientTrackOpts): (ctx: TrackCtx) => ReactNode {\n const { gradient, samples = 16, activeRange, hatch } = opts;\n\n return (ctx: TrackCtx) => {\n const stops: string[] = [];\n for (let i = 0; i <= samples; i++) {\n const t = i / samples;\n stops.push(`${gradient(t)} ${(t * 100).toFixed(1)}%`);\n }\n const baseGradient = `linear-gradient(to right, ${stops.join(', ')})`;\n\n const layers: string[] = [];\n if (activeRange) {\n const lowPct = ctx.valueToFraction(activeRange[0]) * 100;\n const highPct = ctx.valueToFraction(activeRange[1]) * 100;\n const h = { ...DEFAULT_HATCH, ...hatch };\n const stripe = `repeating-linear-gradient(${h.angleDeg}deg, transparent 0 ${h.stripe}px, var(--wzl-surface) ${h.stripe}px ${h.stripe + h.gap}px)`;\n const dimColor = `color-mix(in srgb, var(--wzl-surface) ${h.dim}%, transparent)`;\n const dimOverlay = `linear-gradient(${dimColor}, ${dimColor})`;\n\n if (lowPct > 0) {\n layers.push(`${dimOverlay} left 0 / ${lowPct.toFixed(2)}% 100% no-repeat`);\n layers.push(`${stripe} left 0 / ${lowPct.toFixed(2)}% 100% no-repeat`);\n }\n if (highPct < 100) {\n const wR = (100 - highPct).toFixed(2);\n layers.push(`${dimOverlay} right 0 / ${wR}% 100% no-repeat`);\n layers.push(`${stripe} right 0 / ${wR}% 100% no-repeat`);\n }\n }\n layers.push(baseGradient);\n\n const style: CSSProperties = {\n position: 'absolute',\n inset: 0,\n background: layers.join(', '),\n };\n return <div style={style} />;\n };\n}\n",".root {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-inline-size: 0;\n}\n\n.swatches {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* Each stop's opacity slider would otherwise stretch the row to a width no\n * properties panel has; the chip stays legible, the slider yields. */\n.swatch {\n flex: 0 1 auto;\n min-inline-size: 0;\n}\n","import { useCallback, type ReactElement } from 'react';\nimport {\n sampleGradientStops,\n withGradientKind,\n type GradStop,\n type GradientFill,\n type GradientKind,\n} from '@weasel-js/core';\nimport { Slider, type Thumb } from '../Slider';\nimport { ColorField } from '../ColorField';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport { paintGradientTrack } from '../../paintGradientTrack';\nimport s from './GradientEditor.module.css';\n\nconst KINDS: readonly ToggleBarItem<GradientKind>[] = [\n { value: 'linear-gradient', label: 'Linear' },\n { value: 'radial-gradient', label: 'Radial' },\n { value: 'conic-gradient', label: 'Conic' },\n];\n\n/** Fewer than two stops is not a gradient any renderer can ramp between. */\nconst MIN_STOPS = 2;\n\nexport interface GradientEditorProps {\n /** The gradient being edited. */\n value: GradientFill;\n /**\n * Live value during a gesture — a stop drag, a color-picker scrub. Wire\n * it for preview; it fires many times per gesture and must not be\n * written to history.\n */\n onInput?: (next: GradientFill) => void;\n /** Committed value: one call per completed gesture. Pair with an\n * undoable write. */\n onChange: (next: GradientFill) => void;\n /** Show the linear / radial / conic switch. Default true; turn it off\n * when the surrounding UI already owns the kind. */\n kindSwitch?: boolean;\n className?: string;\n}\n\ntype StopThumb = Thumb & { color: string };\n\n/**\n * Editor for a gradient's kind and stop list.\n *\n * Geometry (`from`/`to`, `center`, `radius`, `angle`) is deliberately not\n * edited here — on a canvas that belongs on the artwork, via\n * `<GradientHandles>`. This component owns the parts with no spatial\n * meaning, so it composes into a properties panel at any width.\n *\n * Stops are addressed by their position in `value.stops`, which is never\n * reordered — dragging one stop past another leaves both indices alone, so\n * a drag can cross a neighbour without the two swapping under the pointer.\n * Rendering sorts a copy.\n */\nexport function GradientEditor(props: GradientEditorProps): ReactElement {\n const { value, onInput, onChange, kindSwitch = true, className } = props;\n const stops = value.stops;\n\n const withStops = useCallback(\n (next: GradStop[]): GradientFill => ({ ...value, stops: next }),\n [value],\n );\n\n const thumbs: StopThumb[] = stops.map((stop) => ({ value: stop.offset, color: stop.color }));\n\n const applyThumbs = (next: StopThumb[]): GradStop[] =>\n next.map((t) => ({ offset: t.value, color: t.color }));\n\n const setStopColor = (index: number, color: string): GradStop[] =>\n stops.map((stop, i) => (i === index ? { ...stop, color } : stop));\n\n // Sorted view for the swatch row, carrying each stop's real index so a\n // recolor writes back to the right entry.\n const ordered = stops\n .map((stop, index) => ({ stop, index }))\n .sort((a, b) => a.stop.offset - b.stop.offset);\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n {kindSwitch && (\n <ToggleBar<GradientKind>\n items={KINDS}\n value={value.fill}\n size=\"sm\"\n ariaLabel=\"Gradient kind\"\n onChange={(kind) => kind && onChange(withGradientKind(value, kind))}\n />\n )}\n\n <Slider<StopThumb>\n min={0}\n max={1}\n step={0.005}\n constraint=\"free\"\n thumbs={thumbs}\n ariaLabel=\"Gradient stops\"\n readoutPlacement=\"none\"\n onChange={(next) => onInput?.(withStops(applyThumbs(next)))}\n onCommit={(next) => onChange(withStops(applyThumbs(next)))}\n onAddThumb={(at) => ({ value: at, color: sampleGradientStops(stops, at) })}\n onRemoveThumb={() => stops.length > MIN_STOPS}\n renderTrack={paintGradientTrack({\n gradient: (t) => sampleGradientStops(stops, t),\n samples: 32,\n })}\n />\n\n <div className={s.swatches}>\n {ordered.map(({ stop, index }) => (\n <ColorField\n key={index}\n value={stop.color}\n alpha\n aria-label={`Stop ${index + 1} at ${Math.round(stop.offset * 100)}%`}\n className={s.swatch}\n onInput={(hex) => onInput?.(withStops(setStopColor(index, hex)))}\n onChange={(hex) => onChange(withStops(setStopColor(index, hex)))}\n />\n ))}\n </div>\n </div>\n );\n}\n","/* The overlay covers the canvas but must not intercept tool input; only the\n * handles opt back into hit-testing. */\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* These sit over artwork of unknown color, not over the app's own surface,\n * so they are deliberately not themed: a themed handle disappears against\n * whatever the user painted underneath. White-on-dark reads on anything,\n * which is why every editor draws handles this way. */\n.guide {\n fill: none;\n stroke: #ffffff;\n stroke-opacity: 0.7;\n stroke-dasharray: 4 4;\n paint-order: stroke;\n}\n\n.handle {\n fill: #ffffff;\n stroke: #222222;\n stroke-width: 2;\n cursor: grab;\n pointer-events: auto;\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.handle:focus-visible {\n outline: 2px solid var(--wzl-accent, #4a9eff);\n outline-offset: 2px;\n}\n","import { useRef, type ReactElement, type ReactNode } from 'react';\nimport { useHandleDrag, gradientGeometry, type GradientFill } from '@weasel-js/core';\nimport s from './GradientHandles.module.css';\n\n/** Structural, and deliberately not exported — `Plot2D` and `CurveEditor`\n * each publish their own `Point`, and a third would only be ambiguous at\n * the package barrel. Consumers pass any `{ x, y }`. */\ninterface Point {\n x: number;\n y: number;\n}\n\nexport interface GradientHandlesProps {\n /**\n * The gradient whose geometry these handles move, in a **resolved,\n * isotropic** frame — one where `radius` is a length in the same units as\n * `center.x` and a right angle is a right angle.\n *\n * A `units: 'bounds'` gradient is not such a frame: `x` and `y` are\n * fractions of two different lengths, so a circle there is an ellipse on\n * screen and polar math silently mixes scales. Resolve it first with\n * `fillInPoseFrame(fill, box)` and convert edits back with\n * `fillToBoundsFrame(next, box)`.\n */\n value: GradientFill;\n /**\n * Gradient space → overlay pixels. For a `units: 'local'` gradient this\n * is the node's local-to-screen transform; for `'world'`, the view.\n */\n toScreen: (p: Point) => Point;\n /** Overlay pixels → gradient space. Must invert `toScreen`. */\n toLocal: (p: Point) => Point;\n /** Live during a drag — wire for preview, do not write to history. */\n onInput?: (next: GradientFill) => void;\n /** Committed at drag end: one call per gesture. */\n onChange: (next: GradientFill) => void;\n /** Overlay size in CSS pixels. */\n width: number;\n height: number;\n className?: string;\n}\n\n/**\n * Direct-manipulation handles for a gradient's geometry, drawn as an SVG\n * overlay above a canvas: endpoints for linear, center and radius for\n * radial, center and angle arm for conic.\n *\n * Positioning is entirely the consumer's `toScreen` / `toLocal` — this\n * component never sees a view or a scene node, so the same handles serve a\n * node-local gradient, a world-space one, and a plain unzoomed preview.\n *\n * The overlay ignores pointer events except on the handles themselves, so\n * it can sit over live canvas content without swallowing tool input.\n */\nexport function GradientHandles(props: GradientHandlesProps): ReactElement {\n const { value, toScreen, toLocal, onInput, onChange, width, height, className } = props;\n\n return (\n <svg\n className={[s.overlay, className].filter(Boolean).join(' ')}\n width={width}\n height={height}\n >\n {renderForKind(value, toScreen, toLocal, onInput, onChange)}\n </svg>\n );\n}\n\nfunction renderForKind(\n value: GradientFill,\n toScreen: (p: Point) => Point,\n toLocal: (p: Point) => Point,\n onInput: ((next: GradientFill) => void) | undefined,\n onChange: (next: GradientFill) => void,\n): ReactNode {\n const emit = (next: GradientFill, phase: 'input' | 'commit'): void => {\n if (phase === 'input') onInput?.(next);\n else onChange(next);\n };\n\n if (value.fill === 'linear-gradient') {\n const from = toScreen(value.from);\n const to = toScreen(value.to);\n return (\n <>\n <Guide x1={from.x} y1={from.y} x2={to.x} y2={to.y} />\n <DragPoint\n at={from}\n label=\"Gradient start\"\n onDrag={(p, phase) => emit({ ...value, from: toLocal(p) }, phase)}\n />\n <DragPoint\n at={to}\n label=\"Gradient end\"\n onDrag={(p, phase) => emit({ ...value, to: toLocal(p) }, phase)}\n />\n </>\n );\n }\n\n if (value.fill === 'radial-gradient') {\n const center = toScreen(value.center);\n // The radius handle rides the +x axis of gradient space, so it stays on\n // the drawn circle under a rotated or anisotropic transform.\n const edge = toScreen({ x: value.center.x + value.radius, y: value.center.y });\n const screenRadius = Math.hypot(edge.x - center.x, edge.y - center.y);\n return (\n <>\n <circle className={s.guide} cx={center.x} cy={center.y} r={screenRadius} />\n <DragPoint\n at={center}\n label=\"Gradient center\"\n onDrag={(p, phase) => emit({ ...value, center: toLocal(p) }, phase)}\n />\n <DragPoint\n at={edge}\n label=\"Gradient radius\"\n onDrag={(p, phase) => {\n const local = toLocal(p);\n const radius = Math.hypot(local.x - value.center.x, local.y - value.center.y);\n emit({ ...value, radius: Math.max(MIN_RADIUS, radius) }, phase);\n }}\n />\n </>\n );\n }\n\n const center = toScreen(value.center);\n const { radius } = gradientGeometry(value);\n const tip = toScreen({\n x: value.center.x + Math.cos(value.angle) * radius,\n y: value.center.y + Math.sin(value.angle) * radius,\n });\n return (\n <>\n <Guide x1={center.x} y1={center.y} x2={tip.x} y2={tip.y} />\n <DragPoint\n at={center}\n label=\"Gradient center\"\n onDrag={(p, phase) => emit({ ...value, center: toLocal(p) }, phase)}\n />\n <DragPoint\n at={tip}\n label=\"Gradient angle\"\n onDrag={(p, phase) => {\n const local = toLocal(p);\n const angle = Math.atan2(local.y - value.center.y, local.x - value.center.x);\n emit({ ...value, angle }, phase);\n }}\n />\n </>\n );\n}\n\n/** A radius of zero divides by zero in the shader's `t`; keep it off the floor. */\nconst MIN_RADIUS = 1;\n\nfunction Guide(props: { x1: number; y1: number; x2: number; y2: number }): ReactElement {\n return <line className={s.guide} {...props} />;\n}\n\nfunction DragPoint({\n at,\n label,\n onDrag,\n}: {\n at: Point;\n label: string;\n onDrag: (p: Point, phase: 'input' | 'commit') => void;\n}): ReactElement {\n // `useHandleDrag` reports the pointer on move but not on end, so the last\n // position is held here to commit with — otherwise releasing the pointer\n // would write whatever the gradient held before the drag began.\n const last = useRef<Point>(at);\n const drag = useHandleDrag<SVGCircleElement>({\n onMove: (p) => {\n last.current = p;\n onDrag(p, 'input');\n },\n onEnd: () => onDrag(last.current, 'commit'),\n });\n return (\n <circle\n className={s.handle}\n cx={at.x}\n cy={at.y}\n r={HANDLE_RADIUS}\n role=\"slider\"\n aria-label={label}\n tabIndex={0}\n {...drag}\n />\n );\n}\n\nconst HANDLE_RADIUS = 7;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Input-Dg_CYXg4.js","names":[],"sources":["../../src/components/Input/Input.module.css","../../src/components/Input/Input.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: 4px;\n}\n\n.frame {\n display: flex;\n align-items: center;\n gap: 6px;\n height: 24px;\n padding: 0 8px;\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: 13px;\n line-height: 1;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.frame:focus-within {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .frame {\n border-color: var(--wzl-danger, #ff5b5b);\n}\n\n.field[data-disabled] .frame {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.frame input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font: inherit;\n padding: 0;\n}\n\n.frame input::placeholder {\n color: var(--wzl-fg-subtle);\n}\n\n.adornment {\n display: inline-flex;\n align-items: center;\n color: var(--wzl-fg-subtle);\n font-size: 12px;\n pointer-events: none;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n TextField,\n Label,\n Input as RACInput,\n Text,\n FieldError,\n type TextFieldProps as RACTextFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './Input.module.css';\n\nexport type InputProps = Omit<RACTextFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n placeholder?: string;\n leadingAdornment?: ReactNode;\n trailingAdornment?: ReactNode;\n className?: string;\n};\n\n/**\n * Single-line text input wrapping React Aria's TextField. Supplies a\n * default skin against the `--wzl-*` token system, and exposes the\n * familiar label / description / errorMessage / adornment slot shape.\n *\n * `ref` forwards to the underlying `<input>`.\n */\nexport const Input = forwardRef(function Input(\n props: InputProps,\n ref: Ref<HTMLInputElement>,\n) {\n const {\n label,\n description,\n errorMessage,\n placeholder,\n leadingAdornment,\n trailingAdornment,\n className,\n ...textFieldProps\n } = props;\n\n return (\n <TextField {...textFieldProps} className={[s.field, fieldClasses.root, className].filter(Boolean).join(' ')}>\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <div className={s.frame}>\n {leadingAdornment !== undefined && <span className={s.adornment}>{leadingAdornment}</span>}\n <RACInput ref={ref} placeholder={placeholder} />\n {trailingAdornment !== undefined && <span className={s.adornment}>{trailingAdornment}</span>}\n </div>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </TextField>\n );\n});\n"],"mappings":";;;;;;;;GC8Ba,IAAQ,EAAW,SAC9B,GACA,GACA;CACA,IAAM,EACJ,UACA,gBACA,iBACA,gBACA,qBACA,sBACA,cACA,GAAG,MACD;AAEJ,QACE,kBAAC,GAAD;EAAW,GAAI;EAAgB,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA3G;GACG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,OAAD;IAAK,WAAW,EAAE;cAAlB;KACG,MAAqB,KAAA,KAAa,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAY;MAAwB,CAAA;KAC1F,kBAAC,GAAD;MAAe;MAAkB;MAAe,CAAA;KAC/C,MAAsB,KAAA,KAAa,kBAAC,QAAD;MAAM,WAAW,EAAE;gBAAY;MAAyB,CAAA;KACxF;;GACL,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GAC5D;;EAEd"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NumberField-BhxUgi43.js","names":[],"sources":["../../src/components/NumberField/NumberField.module.css","../../src/components/NumberField/NumberField.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: 4px;\n}\n\n.frame {\n display: flex;\n align-items: stretch;\n height: 24px;\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: 13px;\n line-height: 1;\n overflow: hidden;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.frame:focus-within {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .frame {\n border-color: var(--wzl-danger, #ff5b5b);\n}\n\n.field[data-disabled] .frame {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.frame input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font: inherit;\n padding: 0 8px;\n text-align: right;\n -moz-appearance: textfield;\n}\n\n.frame input::-webkit-outer-spin-button,\n.frame input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.steppers {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--wzl-border);\n}\n\n.stepper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n flex: 1;\n background: transparent;\n border: none;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n font-size: 9px;\n line-height: 1;\n}\n\n.stepper:not(:last-child) {\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.stepper[data-hovered]:not([data-disabled]) {\n background: color-mix(in srgb, var(--wzl-fg) 10%, transparent);\n color: var(--wzl-fg);\n}\n\n.stepper[data-pressed]:not([data-disabled]) {\n background: color-mix(in srgb, var(--wzl-fg) 18%, transparent);\n}\n\n.stepper[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n NumberField as RACNumberField,\n Label,\n Input as RACInput,\n Group,\n Button as RACButton,\n Text,\n FieldError,\n type NumberFieldProps as RACNumberFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './NumberField.module.css';\n\nexport type NumberFieldProps = Omit<RACNumberFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n /** Hide the up/down stepper buttons. Defaults to false. */\n hideSteppers?: boolean;\n /** Native input placeholder — e.g. `'Mixed'` for a multi-selection\n * editor with no shared value. */\n placeholder?: string;\n className?: string;\n};\n\nexport const NumberField = forwardRef(function NumberField(\n props: NumberFieldProps,\n ref: Ref<HTMLInputElement>,\n) {\n const { label, description, errorMessage, hideSteppers, placeholder, className, ...rest } = props;\n return (\n <RACNumberField\n {...rest}\n className={[s.field, fieldClasses.root, className].filter(Boolean).join(' ')}\n >\n {label !== undefined && <Label className={fieldClasses.label}>{label}</Label>}\n <Group className={s.frame}>\n <RACInput ref={ref} placeholder={placeholder} />\n {!hideSteppers && (\n <div className={s.steppers}>\n <RACButton slot=\"increment\" className={s.stepper} aria-label=\"Increment\">▲</RACButton>\n <RACButton slot=\"decrement\" className={s.stepper} aria-label=\"Decrement\">▼</RACButton>\n </div>\n )}\n </Group>\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </RACNumberField>\n );\n});\n"],"mappings":";;;;;;;;;GC2Ba,IAAc,EAAW,SACpC,GACA,GACA;CACA,IAAM,EAAE,UAAO,gBAAa,iBAAc,iBAAc,gBAAa,cAAW,GAAG,MAAS;AAC5F,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAF9E;GAIG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,GAAD;IAAO,WAAW,EAAE;cAApB,CACE,kBAAC,GAAD;KAAe;KAAkB;KAAe,CAAA,EAC/C,CAAC,KACA,kBAAC,OAAD;KAAK,WAAW,EAAE;eAAlB,CACE,kBAAC,GAAD;MAAW,MAAK;MAAY,WAAW,EAAE;MAAS,cAAW;gBAAY;MAAa,CAAA,EACtF,kBAAC,GAAD;MAAW,MAAK;MAAY,WAAW,EAAE;MAAS,cAAW;gBAAY;MAAa,CAAA,CAClF;OAEF;;GACP,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GACvD;;EAEnB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"OptionsBar-hQN-wfd-.js","names":[],"sources":["../../src/components/OptionsBar/OptionsBar.module.css","../../src/components/OptionsBar/OptionsBar.tsx"],"sourcesContent":["/* Aqua-glass segmented control. The track is a pill-shaped well; the\n selected segment becomes a tinted glass cylinder that reads as the same\n \"lozenge\" family as the standalone Button. Visual params come from the\n `--btn-*` custom properties (see Button.module.css) so the Button Lab\n can tune both surfaces with one set of sliders. */\n.root {\n display: inline-flex;\n height: var(--wzl-tb-height, 28px);\n padding: 2px;\n background: linear-gradient(\n 180deg,\n rgba(0, 0, 0, 0.18) 0%,\n rgba(0, 0, 0, 0.05) 100%\n );\n border-radius: 999px;\n /* Clip segments to the track outline so interior corners read crisp and\n the selected segment's glass treatment doesn't paint over the rounded\n outer pixels. */\n overflow: hidden;\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.4),\n 0 1px 0 rgba(255, 255, 255, 0.06);\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n}\n\n.segment {\n position: relative;\n flex: 1 1 0;\n min-width: 0;\n padding: 0 12px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n margin: 0;\n font-family: var(--wzl-font-ui);\n font-weight: 300;\n font-size: var(--btn-fontsize, 14px);\n line-height: 1;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n /* Only round the outer corners of the bar — middle segments stay square\n so the seams between them don't render as facing parentheses. */\n border-radius: 0;\n isolation: isolate;\n overflow: hidden;\n transition: color var(--wzl-motion-fast);\n}\n\n.segment:first-child {\n border-radius: 999px 0 0 999px;\n}\n\n.segment:last-child {\n border-radius: 0 999px 999px 0;\n}\n\n.segment:only-child {\n border-radius: 999px;\n}\n\n.segment:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -2px;\n}\n\n.segment:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.segment:hover:not(:disabled):not(.segmentSelected) {\n color: var(--wzl-fg);\n}\n\n/* Selected segment — full Aqua glass cylinder. Mirrors Button.variant_primary. */\n.segmentSelected {\n background: linear-gradient(\n 180deg,\n color-mix(in srgb, var(--wzl-accent) var(--btn-top-alpha, 45%), transparent) 0%,\n color-mix(in srgb, var(--wzl-accent) var(--btn-upper-alpha, 70%), transparent) calc(var(--btn-equator, 52%) - var(--btn-equator-spread, 12%)),\n color-mix(in srgb, var(--wzl-accent) var(--btn-equator-tint, 95%), white) var(--btn-equator, 52%),\n var(--wzl-accent) calc(var(--btn-equator, 52%) + var(--btn-equator-spread, 12%) * 1.6),\n color-mix(in srgb, var(--wzl-accent) var(--btn-base-tint, 70%), black) 100%\n );\n backdrop-filter: blur(var(--btn-blur, 6px)) saturate(var(--btn-saturate, 1.3));\n -webkit-backdrop-filter: blur(var(--btn-blur, 6px)) saturate(var(--btn-saturate, 1.3));\n color: var(--wzl-fg-on-accent);\n text-shadow:\n 0 1px 0 rgba(0, 0, 0, var(--btn-text-shadow, 0.35)),\n var(--btn-text-inset-x, 0px) var(--btn-text-inset-y, 1px) 0 rgba(0, 0, 0, var(--btn-text-inset, 0.55)),\n 0 0 6px rgba(0, 0, 0, calc(var(--btn-text-shadow, 0.35) * 0.5));\n box-shadow:\n inset 0 1px 0 rgba(255, 255, 255, var(--btn-inset-hi, 0.42)),\n inset 0 -1px 2px rgba(0, 0, 0, var(--btn-inset-lo, 0.3)),\n 0 0 0 1px rgba(0, 0, 0, var(--btn-rim, 0.22)),\n 0 1px 2px rgba(0, 0, 0, var(--btn-drop-near, 0.3)),\n 0 2px 4px rgba(0, 0, 0, calc(var(--btn-drop-far, 0.18) * 0.8));\n}\n\n/* Specular gloss on the selected segment. Top corners inherit the\n segment's radius so they hug the upper edge cleanly; bottom corners\n are flattened so the gloss ends in a straight line at the equator\n instead of two little curves that read as `)(` brackets. */\n.segmentSelected::before {\n content: '';\n position: absolute;\n inset: 1px 1px var(--btn-gloss-bottom, 50%) 1px;\n border-radius: inherit;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background: linear-gradient(\n 180deg,\n rgba(255, 255, 255, var(--btn-gloss-top, 0.42)) 0%,\n rgba(255, 255, 255, var(--btn-gloss-mid, 0.12)) 60%,\n rgba(255, 255, 255, 0) 100%\n );\n pointer-events: none;\n z-index: 1;\n}\n\n.segmentSelected > * {\n position: relative;\n z-index: 2;\n}\n\n/* Small variant — ~60% of the default height, with proportionally reduced\n padding and font. Sized for dense surfaces (lab control panels, inline\n filters, etc.). */\n.size_sm {\n height: 17px;\n padding: 1px;\n}\n\n.size_sm .segment {\n padding: 0 8px;\n font-size: 11px;\n}\n\n/* Minimal variant — strips the pill track, glass selected state, inset\n shadows, and outer padding. Used in dense diagnostic surfaces (dispatch\n trace, lab inspectors) where the bar must read as plain text controls. */\n.variant_minimal {\n height: auto;\n padding: 0;\n background: none;\n border-radius: 0;\n box-shadow: none;\n gap: 2px;\n}\n\n.variant_minimal .segment {\n padding: 0 4px;\n font-size: 11px;\n font-weight: 400;\n line-height: 1.4;\n border-radius: 2px;\n color: var(--wzl-fg-muted);\n transition: none;\n}\n\n.variant_minimal .segment:first-child,\n.variant_minimal .segment:last-child,\n.variant_minimal .segment:only-child {\n border-radius: 2px;\n}\n\n.variant_minimal .segment:hover:not(:disabled):not(.segmentSelected) {\n color: var(--wzl-fg);\n background: rgba(255, 255, 255, 0.06);\n}\n\n.variant_minimal .segmentSelected {\n background: none;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n color: var(--wzl-accent-fg, var(--wzl-accent));\n text-shadow: none;\n box-shadow: none;\n font-weight: 500;\n}\n\n.variant_minimal .segmentSelected::before {\n content: none;\n}\n","import { type CSSProperties, type ReactElement, type ReactNode } from 'react';\nimport s from './OptionsBar.module.css';\nimport { useRovingTabIndex } from '../../useRovingTabIndex';\n\nexport type OptionsBarItem<V extends string | number = string> = {\n value: V;\n label?: ReactNode;\n ariaLabel?: string;\n disabled?: boolean;\n selected: boolean;\n onChange: (next: boolean) => void;\n};\n\nexport type OptionsBarSize = 'sm' | 'md';\nexport type OptionsBarVariant = 'default' | 'minimal';\n\nexport type OptionsBarProps<V extends string | number = string> = {\n items: readonly OptionsBarItem<V>[];\n ariaLabel?: string;\n className?: string;\n height?: number;\n /** Size variant. `sm` is ~60% of the default height with reduced padding\n * and font size — sized for dense surfaces like lab control panels. */\n size?: OptionsBarSize;\n /** Visual variant. `minimal` strips the pill track and glass treatment;\n * selection becomes a flat accent. For dense diagnostic surfaces. */\n variant?: OptionsBarVariant;\n};\n\nexport function OptionsBar<V extends string | number = string>(props: OptionsBarProps<V>): ReactElement {\n const { items, ariaLabel, className, height, size, variant } = props;\n\n const toggle = (index: number) => {\n const item = items[index];\n if (item.disabled) return;\n item.onChange(!item.selected);\n };\n\n const roving = useRovingTabIndex({ items, itemClassName: s.segment, onActivate: toggle });\n\n const style: CSSProperties | undefined = height !== undefined\n ? ({ ['--wzl-tb-height' as string]: `${height}px` } as CSSProperties)\n : undefined;\n\n const rootCls = [\n s.root,\n size && s[`size_${size}`],\n variant && s[`variant_${variant}`],\n className,\n ].filter(Boolean).join(' ');\n\n return (\n <div\n ref={roving.rootRef}\n className={rootCls}\n role=\"group\"\n aria-label={ariaLabel}\n style={style}\n >\n {items.map((item, i) => {\n const cls = `${s.segment}${item.selected ? ` ${s.segmentSelected}` : ''}`;\n return (\n <button\n key={item.value}\n type=\"button\"\n aria-pressed={item.selected}\n aria-label={item.ariaLabel}\n disabled={item.disabled}\n tabIndex={roving.tabIndexFor(i)}\n className={cls}\n onClick={() => toggle(i)}\n onKeyDown={roving.onKeyDown(i)}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AC6BA,SAAgB,EAA+C,GAAyC;CACtG,IAAM,EAAE,UAAO,cAAW,cAAW,WAAQ,SAAM,eAAY,GAEzD,KAAU,MAAkB;EAChC,IAAM,IAAO,EAAM;AACf,IAAK,YACT,EAAK,SAAS,CAAC,EAAK,SAAS;IAGzB,IAAS,EAAkB;EAAE;EAAO,eAAe,EAAE;EAAS,YAAY;EAAQ,CAAC,EAEnF,IAAmC,MAAW,KAAA,IAEhD,KAAA,IADC,EAAG,mBAA8B,GAAG,EAAO,KAAK,EAG/C,IAAU;EACd,EAAE;EACF,KAAQ,EAAE,QAAQ;EAClB,KAAW,EAAE,WAAW;EACxB;EACD,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAE3B,QACE,kBAAC,OAAD;EACE,KAAK,EAAO;EACZ,WAAW;EACX,MAAK;EACL,cAAY;EACL;YAEN,EAAM,KAAK,GAAM,MAAM;GACtB,IAAM,IAAM,GAAG,EAAE,UAAU,EAAK,WAAW,IAAI,EAAE,oBAAoB;AACrE,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,gBAAc,EAAK;IACnB,cAAY,EAAK;IACjB,UAAU,EAAK;IACf,UAAU,EAAO,YAAY,EAAE;IAC/B,WAAW;IACX,eAAe,EAAO,EAAE;IACxB,WAAW,EAAO,UAAU,EAAE;cAE7B,EAAK;IACC,EAXF,EAAK,MAWH;IAEX;EACE,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PointPlotter-DCSjvpN0.js","names":[],"sources":["../../src/components/PointPlotter/PointPlotter.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\nimport {\n CurveEditor,\n type AnchorRenderProps,\n type ControlPoint,\n type CurveEditorProps,\n} from '../CurveEditor';\nimport type { GridSettings, AxesSettings } from '../Plot2D';\n\nexport interface PointPlotterProps {\n /** Plotted points; caller-owned. */\n value: readonly ControlPoint[];\n /** Fires every frame during drag with the live in-flight value. */\n onChange: (next: ControlPoint[]) => void;\n /** Fires once per discrete user action (drag-end, add, delete) with\n * the new value and the value at gesture start. Wire history here. */\n onChangeCommit?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n width: number;\n height: number;\n grid?: GridSettings | false | null;\n axes?: AxesSettings | false | null;\n /** Minimum allowed point count. User-initiated deletion is refused\n * while `value.length <= minPoints`. */\n minPoints?: number;\n /** Maximum allowed point count. User-initiated insertion is refused\n * while `value.length >= maxPoints`. */\n maxPoints?: number;\n /** How new points are added. Default `'click-empty'`. Set to `'never'`\n * to disable insertion entirely. */\n addPointMode?: 'click-empty' | 'never';\n /** Built-in undo/redo via keyboard when the component has focus.\n * Default `true`. See `CurveEditor.history` for details. */\n history?: boolean;\n /** Custom per-anchor renderer. See `CurveEditor.renderAnchor`. */\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** Extra SVG content beneath the anchors. See `CurveEditor.decorations`. */\n decorations?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * 2D point plotter — a CurveEditor with the curve hidden. Anchors,\n * drag, shift+click delete, right-click delete, and click-empty insert\n * are inherited from CurveEditor verbatim, so the two components share\n * gesture semantics and stay in sync as CurveEditor evolves.\n *\n * Differences from CurveEditor: no curve `<path>` drawn, no fill, no\n * endpoint pinning, no interpolation, default `addPointMode` is\n * `'click-empty'` (CurveEditor defaults to `'click-curve'`, which\n * doesn't make sense without a curve to hit-test against).\n */\nexport function PointPlotter(props: PointPlotterProps) {\n const curveProps: CurveEditorProps = {\n ...props,\n addPointMode: props.addPointMode ?? 'click-empty',\n curve: false,\n fill: false,\n endpoints: 'free',\n };\n return <CurveEditor {...curveProps} />;\n}\n"],"mappings":";;;AAwDA,SAAgB,EAAa,GAA0B;AAQrD,QAAO,kBAAC,GAAD;EANL,GAAG;EACH,cAAc,EAAM,gBAAgB;EACpC,OAAO;EACP,MAAM;EACN,WAAW;EAEyB,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Powerline-DkOTPnUV.js","names":[],"sources":["../../src/components/Powerline/Powerline.module.css","../../src/components/Powerline/Powerline.tsx"],"sourcesContent":[".row {\n display: inline-flex;\n align-items: stretch;\n gap: var(--powerline-gap, 0.2em);\n}\n\n.matrix {\n display: grid;\n gap: 12px;\n}\n\n/* Reset Badge's opinionated text defaults — Powerline segments should pick up\n * the surrounding text style instead of forcing uppercase / 10px / extra\n * tracking. Consumers can still override via className on Powerline. */\n.segment {\n font-size: inherit;\n text-transform: none;\n letter-spacing: normal;\n}\n","import type { CSSProperties, ReactNode } from 'react';\nimport { Badge } from '../Badge/Badge';\nimport type { BadgeSize, BadgeTone, BadgeVariant } from '../Badge/types';\nimport type { EdgeCap } from '../Badge/bases/edgeProfiles';\nimport s from './Powerline.module.css';\n\nexport interface PowerlineSegment {\n text: ReactNode;\n /** Cap on this segment's right edge. Next segment's left edge adopts the same profile. */\n endCap?: EdgeCap;\n tone?: BadgeTone;\n variant?: BadgeVariant;\n size?: BadgeSize;\n onClick?: () => void;\n href?: string;\n 'aria-label'?: string;\n}\n\nexport interface PowerlineProps {\n segments: PowerlineSegment[];\n /** Left edge of the first segment. Defaults to 'flat'. */\n startCap?: EdgeCap;\n /** Default size for every segment (per-segment `size` wins). */\n size?: BadgeSize;\n /** Default variant for every segment (per-segment `variant` wins). */\n variant?: BadgeVariant;\n /** Protrusion depth in CSS px, passed through to every segment's base. */\n depth?: number;\n /** Visible gap between adjacent segments. Number → px; string → literal CSS length.\n * Default: `0.2em` (scales with the row's font size). Pass `0` for flush. */\n gap?: number | string;\n className?: string;\n 'aria-label'?: string;\n}\n\nexport function Powerline({\n segments,\n startCap = 'flat',\n size,\n variant,\n depth,\n gap,\n className,\n ...rest\n}: PowerlineProps) {\n const cls = [s.row, className].filter(Boolean).join(' ');\n const style: CSSProperties | undefined = gap !== undefined\n ? { ['--powerline-gap' as never]: typeof gap === 'number' ? `${gap}px` : gap }\n : undefined;\n return (\n <span className={cls} style={style} aria-label={rest['aria-label']}>\n {segments.map((seg, i) => {\n const leftEdge: EdgeCap = i === 0 ? startCap : (segments[i - 1].endCap ?? 'flat');\n const rightEdge: EdgeCap = seg.endCap ?? 'flat';\n return (\n <Badge\n key={i}\n base=\"powerline\"\n baseParams={{ leftEdge, rightEdge, ...(depth !== undefined && { depth }) }}\n tone={seg.tone}\n variant={seg.variant ?? variant}\n size={seg.size ?? size}\n onClick={seg.onClick}\n href={seg.href}\n aria-label={seg['aria-label']}\n className={s.segment}\n >\n {seg.text}\n </Badge>\n );\n })}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;ACmCA,SAAgB,EAAU,EACxB,aACA,cAAW,QACX,SACA,YACA,UACA,QACA,cACA,GAAG,KACc;AAKjB,QACE,kBAAC,QAAD;EAAM,WALI,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAKjC;EAAY,OAJU,MAAQ,KAAA,IAE7C,KAAA,IADA,EAAG,mBAA6B,OAAO,KAAQ,WAAW,GAAG,EAAI,MAAM,GAAK;EAG1C,cAAY,EAAK;YAClD,EAAS,KAAK,GAAK,MAIhB,kBAAC,GAAD;GAEE,MAAK;GACL,YAAY;IAAE,UANQ,MAAM,IAAI,IAAY,EAAS,IAAI,GAAG,UAAU;IAM9C,WALD,EAAI,UAAU;IAKF,GAAI,MAAU,KAAA,KAAa,EAAE,UAAO;IAAG;GAC1E,MAAM,EAAI;GACV,SAAS,EAAI,WAAW;GACxB,MAAM,EAAI,QAAQ;GAClB,SAAS,EAAI;GACb,MAAM,EAAI;GACV,cAAY,EAAI;GAChB,WAAW,EAAE;aAEZ,EAAI;GACC,EAZD,EAYC,CAEV;EACG,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Prefs-C4HfuTvm.js","names":[],"sources":["../../src/components/Prefs/schema.ts","../../src/components/Prefs/Prefs.module.css","../../src/components/Prefs/PrefsForm.tsx","../../src/components/Prefs/PrefsDialog.tsx"],"sourcesContent":["// Preference-schema vocabulary for PrefsForm / PrefsDialog.\n//\n// Deliberately field-compatible with core's `ToolPref*` family\n// (src/tools/prefs.ts): this schema module deliberately avoids importing\n// @weasel-js/core, so the contract is structural — a `ToolPrefGroup` (and\n// any host-app superset, like WeaselDraw's registry) assigns into\n// `PrefGroup` with no import and no cast. Keep the two in sync\n// field-for-field.\n\n/** Control-presentation hints per kind — visually-equivalent renderings\n * of the same value type. The persisted value is unchanged either way. */\nexport type PrefNumberControl = 'input' | 'slider';\nexport type PrefBooleanControl = 'checkbox' | 'switch';\nexport type PrefStringControl = 'input' | 'textarea';\nexport type PrefEnumControl = 'select' | 'radio';\n\ninterface PrefBase<K extends string, Value> {\n kind: K;\n /** Human-readable label, e.g. \"Show grid\". */\n name: string;\n /** Longer help text — surfaces as a tooltip on the row label. */\n description: string;\n /** Fallback when `values` holds nothing at this leaf's path. */\n default: Value;\n /** Omitted from the form unless `showHidden` is set. */\n hidden?: boolean;\n /** Render the control full-width with no label/tooltip row — for\n * controls that supply their own chrome (embedded sub-panel editors).\n * The `description` still applies wherever the renderer surfaces it. */\n block?: boolean;\n /** Row-pairing hint for compact property UIs (`SelectionPanel`):\n * leaves sharing a `pair` id render side-by-side on one row labeled\n * with the `pair` string. Purely presentational. */\n pair?: string;\n}\n\n/** Display-unit conversion for number leaves whose stored value uses a\n * canonical unit the user shouldn't see (radians → degrees). Mirrors\n * core's `ToolPrefNumberUnit`. */\nexport interface PrefNumberUnit {\n toDisplay: (stored: number) => number;\n fromDisplay: (display: number) => number;\n suffix?: string;\n}\n\nexport interface PrefNumber extends PrefBase<'number', number> {\n min?: number;\n max?: number;\n step?: number;\n control?: PrefNumberControl;\n unit?: PrefNumberUnit;\n}\nexport interface PrefBoolean extends PrefBase<'boolean', boolean> {\n control?: PrefBooleanControl;\n}\nexport interface PrefString extends PrefBase<'string', string> {\n control?: PrefStringControl;\n}\nexport interface PrefEnum<T extends string = string> extends PrefBase<'enum', T> {\n options: readonly { value: T; label: string }[];\n control?: PrefEnumControl;\n}\n\nexport interface PrefColor extends PrefBase<'color', string> {\n /** Value is `#rrggbb`, or `#rrggbbaa` when `alpha` is set. */\n alpha?: boolean;\n}\n\nexport type BuiltinPref = PrefNumber | PrefBoolean | PrefString | PrefEnum | PrefColor;\n\n/**\n * Open leaf: any node with a `kind` the form doesn't know renders via the\n * `renderers` map (`renderers[kind]`). App schemas keep their extra fields\n * (e.g. a registry-enum's `source`/`filter`) — renderers receive the node\n * and narrow it themselves. Deliberately NOT index-signatured so concrete\n * app interfaces stay assignable.\n */\nexport interface PrefCustom extends PrefBase<string, unknown> {}\n\nexport type PrefLeaf = BuiltinPref | PrefCustom;\n\n/** Nestable group: branch nodes in the schema tree. */\nexport interface PrefGroup {\n name: string;\n description?: string;\n children: Record<string, PrefLeaf | PrefGroup>;\n}\n\nexport function isPrefLeaf(node: PrefLeaf | PrefGroup): node is PrefLeaf {\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 PrefLeaf | PrefGroup>(\n node: T,\n showHidden: boolean,\n): T | null {\n if (isPrefLeaf(node)) return node.hidden && !showHidden ? null : node;\n const group = node as PrefGroup;\n const children: Record<string, PrefLeaf | PrefGroup> = {};\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: 2px;\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: 12px;\n font-weight: 600;\n color: var(--wzl-fg);\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n\n.subpanel .groupTitle {\n font-size: 11px;\n text-transform: none;\n letter-spacing: 0;\n}\n\n.groupDesc {\n margin: 0;\n font-size: 11px;\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: 12px;\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: 11px;\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: 12px;\n resize: vertical;\n}\n\n.unrenderable {\n font-family: var(--wzl-font-mono);\n font-size: 11px;\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","import { useMemo, type ReactNode } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { Checkbox } from '../Checkbox';\nimport { ColorField } from '../ColorField';\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 {\n isPrefLeaf,\n prefValueAtPath,\n visiblePrefSubtree,\n type PrefBoolean,\n type PrefColor,\n type PrefEnum,\n type PrefGroup,\n type PrefLeaf,\n type PrefNumber,\n type PrefString,\n} from './schema';\nimport s from './Prefs.module.css';\n\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\nexport type PrefRenderer = (ctx: PrefRenderContext) => ReactNode;\n\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(ctx: PrefRenderContext): ReactNode {\n const { pref, value, setValue } = ctx;\n switch (pref.kind) {\n case 'boolean': {\n const p = pref as PrefBoolean;\n const checked = Boolean(value);\n return p.control === 'switch' ? (\n <Switch isSelected={checked} onChange={setValue} aria-label={p.name} />\n ) : (\n <Checkbox isSelected={checked} onChange={setValue} aria-label={p.name} />\n );\n }\n case 'number': {\n const p = pref as PrefNumber;\n const n = typeof value === 'number' && Number.isFinite(value) ? value : 0;\n if (p.control === 'slider') {\n return (\n <RangeSlider\n value={n}\n onChange={(v) => setValue(typeof v === 'number' ? v : v[0])}\n minValue={p.min}\n maxValue={p.max}\n step={p.step ?? 1}\n aria-label={p.name}\n />\n );\n }\n return (\n <NumberField\n value={n}\n onChange={setValue}\n minValue={p.min}\n maxValue={p.max}\n step={p.step ?? 1}\n aria-label={p.name}\n />\n );\n }\n case 'string': {\n const p = pref as PrefString;\n const text = typeof value === 'string' ? value : '';\n if (p.control === 'textarea') {\n return (\n <textarea\n className={s.textarea}\n value={text}\n onChange={(e) => setValue(e.target.value)}\n aria-label={p.name}\n rows={3}\n />\n );\n }\n return <Input value={text} onChange={setValue} aria-label={p.name} />;\n }\n case 'enum': {\n const p = pref as PrefEnum;\n const selected = typeof value === 'string' ? value : p.default;\n if (p.control === 'radio') {\n return (\n <RadioGroup\n value={selected}\n onChange={(v) => setValue(v)}\n aria-label={p.name}\n >\n {p.options.map((o) => (\n <Radio key={o.value} value={o.value}>{o.label}</Radio>\n ))}\n </RadioGroup>\n );\n }\n return (\n <Select<string>\n options={p.options.map((o) => ({ value: o.value, label: o.label }))}\n selectedKey={selected}\n onSelectionChange={(v) => setValue(v)}\n aria-label={p.name}\n />\n );\n }\n case 'color': {\n const p = pref as PrefColor;\n const hex = typeof value === 'string' ? value : p.default;\n return (\n <ColorField\n value={hex}\n alpha={p.alpha}\n onChange={setValue}\n aria-label={p.name}\n />\n );\n }\n default:\n // Unknown kind with no renderer 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}\n","import type { ReactNode } from 'react';\nimport { Dialog } from '../Dialog';\nimport { PrefsForm, type PrefsFormProps } from './PrefsForm';\nimport s from './Prefs.module.css';\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":"RadioGroup-Bg3Rqxe2.js","names":[],"sources":["../../src/components/RadioGroup/RadioGroup.module.css","../../src/components/RadioGroup/RadioGroup.tsx"],"sourcesContent":[".group {\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.group[data-orientation=\"horizontal\"] {\n flex-direction: row;\n gap: 16px;\n}\n\n.legend {\n font-size: 12px;\n font-weight: 500;\n color: var(--wzl-fg);\n}\n\n.radio {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n font-size: 13px;\n color: var(--wzl-fg);\n user-select: none;\n -webkit-user-select: none;\n}\n\n.radio[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.dot {\n position: relative;\n flex: 0 0 auto;\n width: 14px;\n height: 14px;\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border-strong);\n border-radius: 50%;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.radio[data-focus-visible] .dot {\n box-shadow: 0 0 0 2px var(--wzl-focus-ring);\n}\n\n.radio[data-hovered]:not([data-disabled]) .dot {\n border-color: var(--wzl-fg-muted);\n}\n\n.radio[data-selected] .dot {\n border-color: var(--wzl-accent-strong);\n}\n\n.dot::after {\n content: '';\n position: absolute;\n inset: 3px;\n border-radius: 50%;\n background: var(--wzl-accent-strong);\n transform: scale(0);\n transition: transform var(--wzl-motion-fast);\n}\n\n.radio[data-selected] .dot::after {\n transform: scale(1);\n}\n","import type { ReactNode } from 'react';\nimport {\n RadioGroup as RACRadioGroup,\n Radio as RACRadio,\n Label,\n Text,\n FieldError,\n type RadioGroupProps as RACRadioGroupProps,\n type RadioProps as RACRadioProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses } from '../Field/Field';\nimport s from './RadioGroup.module.css';\n\nexport type RadioGroupProps = Omit<RACRadioGroupProps, 'children' | 'className'> & {\n label?: ReactNode;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n children?: ReactNode;\n className?: string;\n};\n\nexport function RadioGroup(props: RadioGroupProps) {\n const { label, description, errorMessage, children, className, ...rest } = props;\n return (\n <RACRadioGroup\n {...rest}\n className={[s.group, fieldClasses.root, className].filter(Boolean).join(' ')}\n >\n {label !== undefined && <Label className={s.legend}>{label}</Label>}\n {children}\n {description !== undefined && (\n <Text slot=\"description\" className={fieldClasses.hint}>\n {description}\n </Text>\n )}\n <FieldError className={fieldClasses.error}>{errorMessage}</FieldError>\n </RACRadioGroup>\n );\n}\n\nexport type RadioProps = Omit<RACRadioProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\nexport function Radio({ children, className, ...rest }: RadioProps) {\n return (\n <RACRadio {...rest} className={[s.radio, className].filter(Boolean).join(' ')}>\n <span className={s.dot} />\n {children !== undefined && <span>{children}</span>}\n </RACRadio>\n );\n}\n"],"mappings":";;;;;;;;;;;ACsBA,SAAgB,EAAW,GAAwB;CACjD,IAAM,EAAE,UAAO,gBAAa,iBAAc,aAAU,cAAW,GAAG,MAAS;AAC3E,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAF9E;GAIG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAE;cAAS;IAAc,CAAA;GAClE;GACA,MAAgB,KAAA,KACf,kBAAC,GAAD;IAAM,MAAK;IAAc,WAAW,EAAa;cAC9C;IACI,CAAA;GAET,kBAAC,GAAD;IAAY,WAAW,EAAa;cAAQ;IAA0B,CAAA;GACxD;;;AASpB,SAAgB,EAAM,EAAE,aAAU,cAAW,GAAG,KAAoB;AAClE,QACE,kBAAC,GAAD;EAAU,GAAI;EAAM,WAAW,CAAC,EAAE,OAAO,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA7E,CACE,kBAAC,QAAD,EAAM,WAAW,EAAE,KAAO,CAAA,EACzB,MAAa,KAAA,KAAa,kBAAC,QAAD,EAAO,aAAgB,CAAA,CACzC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RangeSlider-BvM_DGSr.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: 12px;\n font-weight: 500;\n color: var(--wzl-fg);\n}\n\n.output {\n font-size: 12px;\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: 3px;\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: 3px;\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\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 the kit's\n * canvas-scrub `Slider`, which is shaped for tool-canvas interactions.\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":";;;;;;;;;;;;;AC0BA,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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ResizeHandle-BUkW2kZC.js","names":[],"sources":["../../src/components/ResizeHandle/ResizeHandle.module.css","../../src/components/ResizeHandle/ResizeHandle.tsx"],"sourcesContent":[".handle {\n flex-shrink: 0;\n width: 4px;\n align-self: stretch;\n cursor: ew-resize;\n background: transparent;\n border-left: 1px solid var(--wzl-border);\n /* The bar is 4px of hit area — thin enough to read as a seam, wide\n enough to grab. `touch-action: none` keeps a touch drag from being\n stolen by the scroll container it usually sits inside. */\n touch-action: none;\n}\n\n.handle.horizontal {\n width: auto;\n height: 4px;\n align-self: auto;\n cursor: ns-resize;\n border-left: none;\n border-top: 1px solid var(--wzl-border);\n}\n\n.handle:hover {\n background: var(--wzl-border);\n}\n\n.handle:focus-visible {\n outline: 2px solid var(--wzl-focus-ring, var(--wzl-accent));\n outline-offset: -1px;\n}\n","import { useCallback, useRef, type KeyboardEvent, type PointerEvent } from 'react';\nimport s from './ResizeHandle.module.css';\n\nexport interface ResizeHandleProps {\n /**\n * Which way the handle *runs*, matching `aria-orientation` on the\n * separator role: a `vertical` handle is a vertical bar between two\n * side-by-side panes and drags horizontally. `horizontal` is the\n * stacked case (a bar between rows, dragged up and down).\n */\n orientation?: 'vertical' | 'horizontal';\n /** Current size of the pane being resized, in pixels. */\n value: number;\n min: number;\n max: number;\n /**\n * Flip the sign of the drag. Panes docked to the *trailing* edge (a\n * right sidebar, a bottom drawer) grow as the pointer moves toward the\n * start of the axis, so they want `invert`. Leading-edge panes don't.\n */\n invert?: boolean;\n /**\n * Fires continuously while dragging and on each keyboard step, always\n * clamped to [min, max]. Drive the live size from this.\n */\n onChange(next: number): void;\n /**\n * Fires once when a drag ends or a keyboard step settles, with the final\n * size. Persist here rather than in `onChange` — one write per gesture\n * instead of one per pointer sample.\n */\n onChangeEnd?(next: number): void;\n /**\n * The granularity of `value`. Arrow keys move by one step (Shift by\n * eight), and pointer drags snap to the same grid — a pointer reports\n * fractional coordinates on a scaled display, and nobody wants\n * `362.9453125` in their persisted layout. Set a fractional step for a\n * pane measured in something finer than whole pixels.\n */\n step?: number;\n ariaLabel?: string;\n className?: string;\n}\n\nconst clamp = (n: number, min: number, max: number): number => Math.min(max, Math.max(min, n));\n\n/**\n * Snap to the step grid, then clamp. Bounds win over the grid: `min`/`max`\n * are hard limits, so a range that isn't a whole number of steps clips to\n * the bound rather than overshooting it.\n */\nfunction settle(n: number, min: number, max: number, step: number): number {\n const snapped = step > 0 ? Math.round(n / step) * step : n;\n // Re-round to kill the float dust `x / 0.1 * 0.1` leaves behind.\n const precise = Number.isInteger(step) ? snapped : Number(snapped.toFixed(10));\n return clamp(precise, min, max);\n}\n\n/**\n * Draggable divider for resizing a pane — the \"window splitter\" pattern.\n * Owns no size of its own: the consumer holds `value` and applies it\n * (typically as a CSS custom property on the pane), which keeps the handle\n * usable for flex, grid, and absolutely-positioned layouts alike.\n *\n * Keyboard-operable as a matter of course: it's focusable, arrows step by\n * `step` (Shift for a coarse step), Home/End jump to the bounds. A splitter\n * that only responds to a pointer drag strands anyone who can't make one.\n */\nexport function ResizeHandle(props: ResizeHandleProps) {\n const {\n orientation = 'vertical', value, min, max, invert, onChange, onChangeEnd,\n step = 1, ariaLabel, className,\n } = props;\n\n // Drag origin. Captured on pointerdown so every move is measured against\n // the gesture's start rather than the previous sample — accumulating\n // per-sample deltas drifts once the pointer leaves the clamped range.\n const drag = useRef<{ origin: number; startValue: number } | null>(null);\n\n const axisOf = useCallback(\n (e: PointerEvent<HTMLDivElement>) => (orientation === 'vertical' ? e.clientX : e.clientY),\n [orientation],\n );\n\n const onPointerDown = useCallback((e: PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return;\n drag.current = { origin: axisOf(e), startValue: value };\n // jsdom has no pointer capture; the guard keeps tests on the real path.\n e.currentTarget.setPointerCapture?.(e.pointerId);\n e.preventDefault();\n }, [axisOf, value]);\n\n const onPointerMove = useCallback((e: PointerEvent<HTMLDivElement>) => {\n const d = drag.current;\n if (!d) return;\n const delta = axisOf(e) - d.origin;\n onChange(settle(d.startValue + (invert ? -delta : delta), min, max, step));\n }, [axisOf, invert, max, min, onChange, step]);\n\n const endDrag = useCallback((e: PointerEvent<HTMLDivElement>) => {\n const d = drag.current;\n if (!d) return;\n drag.current = null;\n e.currentTarget.releasePointerCapture?.(e.pointerId);\n const delta = axisOf(e) - d.origin;\n onChangeEnd?.(settle(d.startValue + (invert ? -delta : delta), min, max, step));\n }, [axisOf, invert, max, min, onChangeEnd, step]);\n\n const onKeyDown = useCallback((e: KeyboardEvent<HTMLDivElement>) => {\n const grow = orientation === 'vertical' ? 'ArrowRight' : 'ArrowDown';\n const shrink = orientation === 'vertical' ? 'ArrowLeft' : 'ArrowUp';\n const amount = step * (e.shiftKey ? 8 : 1) * (invert ? -1 : 1);\n let next: number | undefined;\n if (e.key === grow) next = value + amount;\n else if (e.key === shrink) next = value - amount;\n else if (e.key === 'Home') next = invert ? max : min;\n else if (e.key === 'End') next = invert ? min : max;\n if (next === undefined) return;\n e.preventDefault();\n const settled = settle(next, min, max, step);\n onChange(settled);\n onChangeEnd?.(settled);\n }, [invert, max, min, onChange, onChangeEnd, orientation, step, value]);\n\n const cls = [s.handle, orientation === 'horizontal' && s.horizontal, className]\n .filter(Boolean).join(' ');\n\n return (\n <div\n className={cls}\n role=\"separator\"\n tabIndex={0}\n aria-label={ariaLabel}\n aria-orientation={orientation}\n aria-valuenow={value}\n aria-valuemin={min}\n aria-valuemax={max}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={onKeyDown}\n />\n );\n}\n"],"mappings":";;;;;GC4CM,KAAS,GAAW,GAAa,MAAwB,KAAK,IAAI,GAAK,KAAK,IAAI,GAAK,EAAE,CAAC;AAO9F,SAAS,EAAO,GAAW,GAAa,GAAa,GAAsB;CACzE,IAAM,IAAU,IAAO,IAAI,KAAK,MAAM,IAAI,EAAK,GAAG,IAAO;AAGzD,QAAO,EADS,OAAO,UAAU,EAAK,GAAG,IAAU,OAAO,EAAQ,QAAQ,GAAG,CAAC,EACxD,GAAK,EAAI;;AAajC,SAAgB,EAAa,GAA0B;CACrD,IAAM,EACJ,iBAAc,YAAY,UAAO,QAAK,QAAK,WAAQ,aAAU,gBAC7D,UAAO,GAAG,cAAW,iBACnB,GAKE,IAAO,EAAsD,KAAK,EAElE,IAAS,GACZ,MAAqC,MAAgB,aAAa,EAAE,UAAU,EAAE,SACjF,CAAC,EAAY,CACd,EAEK,IAAgB,GAAa,MAAoC;AACjE,IAAE,WAAW,MACjB,EAAK,UAAU;GAAE,QAAQ,EAAO,EAAE;GAAE,YAAY;GAAO,EAEvD,EAAE,cAAc,oBAAoB,EAAE,UAAU,EAChD,EAAE,gBAAgB;IACjB,CAAC,GAAQ,EAAM,CAAC,EAEb,IAAgB,GAAa,MAAoC;EACrE,IAAM,IAAI,EAAK;AACf,MAAI,CAAC,EAAG;EACR,IAAM,IAAQ,EAAO,EAAE,GAAG,EAAE;AAC5B,IAAS,EAAO,EAAE,cAAc,IAAS,CAAC,IAAQ,IAAQ,GAAK,GAAK,EAAK,CAAC;IACzE;EAAC;EAAQ;EAAQ;EAAK;EAAK;EAAU;EAAK,CAAC,EAExC,IAAU,GAAa,MAAoC;EAC/D,IAAM,IAAI,EAAK;AACf,MAAI,CAAC,EAAG;AAER,EADA,EAAK,UAAU,MACf,EAAE,cAAc,wBAAwB,EAAE,UAAU;EACpD,IAAM,IAAQ,EAAO,EAAE,GAAG,EAAE;AAC5B,MAAc,EAAO,EAAE,cAAc,IAAS,CAAC,IAAQ,IAAQ,GAAK,GAAK,EAAK,CAAC;IAC9E;EAAC;EAAQ;EAAQ;EAAK;EAAK;EAAa;EAAK,CAAC,EAE3C,IAAY,GAAa,MAAqC;EAClE,IAAM,IAAO,MAAgB,aAAa,eAAe,aACnD,IAAS,MAAgB,aAAa,cAAc,WACpD,IAAS,KAAQ,EAAE,WAAW,IAAI,MAAM,IAAS,KAAK,IACxD;AAKJ,MAJI,EAAE,QAAQ,IAAM,IAAO,IAAQ,IAC1B,EAAE,QAAQ,IAAQ,IAAO,IAAQ,IACjC,EAAE,QAAQ,SAAQ,IAAO,IAAS,IAAM,IACxC,EAAE,QAAQ,UAAO,IAAO,IAAS,IAAM,IAC5C,MAAS,KAAA,EAAW;AACxB,IAAE,gBAAgB;EAClB,IAAM,IAAU,EAAO,GAAM,GAAK,GAAK,EAAK;AAE5C,EADA,EAAS,EAAQ,EACjB,IAAc,EAAQ;IACrB;EAAC;EAAQ;EAAK;EAAK;EAAU;EAAa;EAAa;EAAM;EAAM,CAAC;AAKvE,QACE,kBAAC,OAAD;EACE,WALQ;GAAC,EAAE;GAAQ,MAAgB,gBAAgB,EAAE;GAAY;GAAU,CAC5E,OAAO,QAAQ,CAAC,KAAK,IAIT;EACX,MAAK;EACL,UAAU;EACV,cAAY;EACZ,oBAAkB;EAClB,iBAAe;EACf,iBAAe;EACf,iBAAe;EACA;EACA;EACf,aAAa;EACb,iBAAiB;EACN;EACX,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Select-DC0AjrEe.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: 24px;\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: 13px;\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, #ff5b5b);\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: 13px;\n}\n\n.option {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 8px;\n border-radius: 3px;\n cursor: pointer;\n color: var(--wzl-fg);\n outline: none;\n}\n\n.option[data-focused] {\n background: color-mix(in srgb, var(--wzl-fg) 10%, transparent);\n}\n\n.option[data-selected] {\n color: var(--wzl-fg);\n font-weight: 500;\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","import 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\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\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 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 className,\n ...rest\n } = props;\n\n return (\n <RACSelect\n {...rest}\n selectedKey={selectedKey ?? undefined}\n defaultSelectedKey={defaultSelectedKey}\n onSelectionChange={onSelectionChange ? (k) => onSelectionChange(k as T) : undefined}\n className={[s.field, fieldClasses.root, className].filter(Boolean).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 <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\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\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 <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 {children}\n </RACListBoxItem>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;ACuDA,SAAgB,EAA+B,GAAuB;CACpE,IAAM,EACJ,UACA,gBACA,iBACA,gBACA,YACA,aACA,gBACA,uBACA,sBACA,cACA,GAAG,MACD;AAEJ,QACE,kBAAC,GAAD;EACE,GAAI;EACJ,aAAa,KAAe,KAAA;EACR;EACpB,mBAAmB,KAAqB,MAAM,EAAkB,EAAO,GAAG,KAAA;EAC1E,WAAW;GAAC,EAAE;GAAO,EAAa;GAAM;GAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAL9E;GAOG,MAAU,KAAA,KAAa,kBAAC,GAAD;IAAO,WAAW,EAAa;cAAQ;IAAc,CAAA;GAC7E,kBAAC,GAAD;IAAW,WAAW,EAAE;cAAxB,CACE,kBAAC,GAAD;KAAa,WAAW,EAAE;gBACtB,EAAE,oBAAiB,uBACnB,IAAiB,KAAe,IAAmB;KAEzC,CAAA,EACd,kBAAC,OAAD;KAAK,WAAW,EAAE;KAAS,SAAQ;KAAY,eAAY;eACzD,kBAAC,QAAD;MAAM,GAAE;MAAiB,MAAK;MAAO,QAAO;MAAe,aAAY;MAAM,eAAc;MAAQ,gBAAe;MAAU,CAAA;KACxH,CAAA,CACI;;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;;;AAgBhB,SAAS,EAAY,GAAqB,GAAkD;AAE1F,QADI,MAAa,KAAA,IACV,OAAO,KAAa,YAAY,OAAO,KAAa,WACvD,OAAO,EAAS,GAChB,KAAA,IAH+B;;AAMrC,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,OAAD;GAAK,WAAW,EAAE;GAAO,SAAQ;GAAY,eAAY;aACvD,kBAAC,YAAD;IAAU,QAAO;IAAoB,MAAK;IAAO,QAAO;IAAe,aAAY;IAAM,eAAc;IAAQ,gBAAe;IAAU,CAAA;GACpI,CAAA,EACL,EACc"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SelectionPanel-DoTdisRY.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\nexport interface PanelLeaf {\n /** Dotted node path — the leaf's key in the schema. */\n path: string;\n leaf: ToolPrefLeaf;\n}\n\nexport interface PanelRow {\n label: string;\n leaves: PanelLeaf[];\n}\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: 12px;\n color: var(--wzl-fg);\n}\n\n.header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 10px;\n border-block-end: 1px solid var(--wzl-border);\n}\n\n.kind {\n font-weight: 600;\n font-size: 13px;\n}\n\n.breakdown {\n font-size: 11px;\n opacity: 0.6;\n}\n\n.section {\n padding: 6px 10px 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: 10px;\n font-weight: 600;\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 gap: 6px;\n align-items: center;\n min-inline-size: 0;\n}\n\n.rowControls > * {\n flex: 1;\n min-inline-size: 0;\n}\n\n.number :global(input),\n.select {\n font-size: 11px;\n}\n\n.unitSuffix {\n flex: none;\n font-size: 11px;\n opacity: 0.6;\n}\n\n.mixedSwitch {\n opacity: 0.55;\n}\n\n.unrenderable {\n font-size: 11px;\n opacity: 0.5;\n font-style: italic;\n}\n","import { Fragment, useState, useSyncExternalStore, type ReactNode } from 'react';\nimport {\n asNodeId,\n type NodePropertiesEntry,\n type NodeRoutingEntry,\n type Scene,\n type SelectionApi,\n type ToolPrefColor,\n type ToolPrefEnum,\n type ToolPrefLeaf,\n type ToolPrefNumber,\n type ToolPrefPaint,\n} from '@weasel-js/core';\nimport { ColorField } from '../ColorField';\nimport { Input } from '../Input';\nimport { NumberField } from '../NumberField';\nimport { Select } from '../Select';\nimport { Switch } from '../Switch';\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\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. */\n value: unknown;\n /** True when selected nodes disagree at this path. */\n mixed: boolean;\n /** Commit a value — fans out to every selected node in one undo step. */\n setValue: (value: unknown) => void;\n}\n\nexport type PropertyRenderer = (ctx: PropertyRenderContext) => ReactNode;\n\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 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 = row.leaves\n .map((panelLeaf) => ({\n panelLeaf,\n content: renderLeafControl(\n panelLeaf,\n row.leaves.length > 1\n ? `${row.label} ${panelLeaf.leaf.name}`\n : panelLeaf.leaf.name,\n nodes,\n renderers,\n commit,\n ),\n }))\n .filter((c) => c.content != null);\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 <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(({ panelLeaf, content }) => (\n <Fragment key={panelLeaf.path}>{content}</Fragment>\n ))}\n </span>\n </div>\n ))}\n </section>\n );\n })}\n </div>\n );\n}\n\nfunction renderLeafControl(\n panelLeaf: PanelLeaf,\n ariaLabel: string,\n nodes: readonly AnyNode[],\n renderers: Record<string, PropertyRenderer> | undefined,\n commit: (leaf: PanelLeaf, value: unknown) => void,\n): ReactNode {\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 setValue: (v) => commit(panelLeaf, v),\n };\n\n const custom = renderers?.[path] ?? renderers?.[leaf.kind];\n if (custom) return custom(ctx);\n return renderBuiltin(ctx, ariaLabel);\n}\n\n/** The color of a solid `FillStyle`, or `undefined` for anything else —\n * including a value that isn't a paint at all. `fill` is optional on the\n * solid member of the union, so the tag alone can't decide it. */\nfunction 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\nfunction renderBuiltin(ctx: PropertyRenderContext, ariaLabel: string): ReactNode {\n const { pref, value, mixed, setValue } = ctx;\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 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 control = (\n <Switch isSelected={Boolean(value)} onChange={setValue} aria-label={ariaLabel} />\n );\n // Switch has no indeterminate state; a reduced-opacity wrapper with\n // a title is the cheap honest cue for a mixed selection.\n if (!mixed) return control;\n return (\n <span className={s.mixedSwitch} title=\"Mixed\">\n {control}\n </span>\n );\n }\n case 'enum': {\n const p = pref as ToolPrefEnum;\n return (\n <Select<string>\n className={s.select}\n options={p.options.map((o) => ({ value: o.value, label: o.label }))}\n selectedKey={mixed ? null : typeof value === 'string' ? value : p.default}\n placeholder={mixed ? 'Mixed' : undefined}\n onSelectionChange={setValue}\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`. A solid one has a color to show; a\n // pattern or gradient does not, and showing the control's default there\n // would claim a color the shape doesn't have. It gets the same\n // indeterminate chip a genuinely mixed selection gets — in both cases\n // the honest statement is \"there is no single color here\".\n const p = pref as ToolPrefPaint;\n const solid = solidColorOf(value) ?? (mixed ? undefined : solidColorOf(p.default));\n return (\n <ColorField\n value={mixed ? undefined : solid}\n mixed={mixed || (value !== undefined && solidColorOf(value) === undefined)}\n alpha={p.alpha}\n // Write the whole union member. Setting a `color` key on the\n // existing paint would leave a `{ fill: 'linear-gradient', stops,\n // color }` hybrid that every structural `'color' in paint` check\n // downstream reads as solid.\n onChange={(color) => setValue({ fill: 'solid', color })}\n aria-label={ariaLabel}\n />\n );\n }\n default:\n return <span className={s.unrenderable}>({pref.kind}: no renderer)</span>;\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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar-B8qig3BQ.js","names":[],"sources":["../../src/components/Sidebar/Sidebar.module.css","../../src/components/Sidebar/Sidebar.tsx"],"sourcesContent":[".sidebar {\n display: flex;\n flex-direction: column;\n gap: 4px;\n background: var(--wzl-surface);\n color: var(--wzl-fg);\n}\n\n.sidebar.left {\n border-right: 1px solid var(--wzl-border);\n}\n.sidebar.right {\n border-left: 1px solid var(--wzl-border);\n}\n","import type { CSSProperties, ReactNode } from 'react';\nimport s from './Sidebar.module.css';\n\nexport interface SidebarProps {\n /**\n * Edge the sidebar docks to. Adds a class hook (`s.left` / `s.right`)\n * so consumers can target either edge for borders, shadows, etc.\n * Purely cosmetic — layout is the parent's job.\n */\n side?: 'left' | 'right';\n /**\n * Accessible label — rendered onto the `<aside>`. Without it, screen\n * readers fall back to \"complementary landmark,\" which is fine for\n * apps with a single sidebar but ambiguous when there's both a left\n * and a right.\n */\n ariaLabel?: string;\n children?: ReactNode;\n className?: string;\n /** Inline style — useful for width-as-custom-property handles or any\n * other one-off CSS property the consumer wants to set dynamically. */\n style?: CSSProperties;\n}\n\n/**\n * Generic sidebar shell — `<aside>` with column layout and token-driven\n * surface treatment. Width, sticky positioning, and resize handles stay\n * the consumer's concern (every app has its own layout grid); the kit\n * just provides the chrome.\n *\n * Pair with `<SidebarPanel>` for collapsible sections.\n */\nexport function Sidebar(props: SidebarProps) {\n const { side, ariaLabel, children, className, style } = props;\n const cls = [s.sidebar, side === 'left' && s.left, side === 'right' && s.right, className]\n .filter(Boolean).join(' ');\n return (\n <aside className={cls} aria-label={ariaLabel} style={style}>\n {children}\n </aside>\n );\n}\n"],"mappings":";;;;;;;;ACgCA,SAAgB,EAAQ,GAAqB;CAC3C,IAAM,EAAE,SAAM,cAAW,aAAU,cAAW,aAAU;AAGxD,QACE,kBAAC,SAAD;EAAO,WAHG;GAAC,EAAE;GAAS,MAAS,UAAU,EAAE;GAAM,MAAS,WAAW,EAAE;GAAO;GAAU,CACvF,OAAO,QAAQ,CAAC,KAAK,IAEJ;EAAK,cAAY;EAAkB;EAClD;EACK,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SidebarPanel-LOt2c-wb.js","names":[],"sources":["../../src/components/SidebarPanel/SidebarPanel.module.css","../../src/components/SidebarPanel/SidebarPanel.tsx"],"sourcesContent":[".panel {\n padding: 8px;\n background: var(--wzl-surface);\n color: var(--wzl-fg);\n}\n\n.title {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--wzl-fg-muted);\n}\n\n.titleRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 4px;\n padding: 0;\n}\n\n.titleButton {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex: 1 1 auto;\n background: transparent;\n border: 0;\n padding: 4px 8px;\n cursor: pointer;\n color: inherit;\n font: inherit;\n text-align: left;\n}\n.titleButton:hover {\n color: var(--wzl-fg);\n}\n\n.chevron {\n display: inline-block;\n font-size: 10px;\n line-height: 1;\n color: var(--wzl-fg-muted);\n transition: transform var(--wzl-motion-fast) ease;\n}\n.chevronCollapsed {\n transform: rotate(-90deg);\n}\n\n.hideButton {\n background: transparent;\n border: 0;\n padding: 0 6px;\n font-size: 14px;\n line-height: 1;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n}\n.hideButton:hover {\n color: var(--wzl-fg);\n}\n\n.panel.collapsed {\n padding-bottom: 4px;\n}\n\n.body {\n padding: 0 4px;\n}\n","import type { ReactNode } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { Tooltip, TooltipTrigger } from '../Tooltip';\nimport s from './SidebarPanel.module.css';\n\nexport interface SidebarPanelProps {\n /** Title rendered in the panel's header row. */\n title?: ReactNode;\n /**\n * Collapsed state — when true, only the header paints; the body is\n * omitted. Caller owns the state (typically via prefs) and flips it\n * back via `onToggleCollapse`.\n */\n collapsed?: boolean;\n /**\n * Click handler for the title row. When provided, the header renders\n * as a button (chevron + label) and acts as the collapse toggle.\n * When omitted, the title is static text and the panel can't collapse.\n */\n onToggleCollapse?(): void;\n /**\n * Optional close handler — when provided, the header gains a `×`\n * button on the trailing edge. Caller flips its \"hidden\" state and\n * conditionally unmounts the panel from the parent.\n */\n onHide?(): void;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * Generic collapsible panel section for sidebars. Pure presentation —\n * caller manages `collapsed`/`hidden` state externally (the pattern is\n * to drive it from app prefs so the Preferences modal and inline\n * chevrons toggle the same map).\n *\n * Pair with `<Sidebar>` for the docked column layout.\n */\nexport function SidebarPanel(props: SidebarPanelProps) {\n const { title, collapsed, onToggleCollapse, onHide, children, className } = props;\n const cls = [s.panel, collapsed && s.collapsed, className].filter(Boolean).join(' ');\n const headerInteractive = onToggleCollapse !== undefined;\n return (\n <div className={cls}>\n {title !== undefined && (\n <div className={s.titleRow}>\n {headerInteractive ? (\n <button\n type=\"button\"\n className={s.titleButton}\n onClick={onToggleCollapse}\n aria-expanded={!collapsed}\n >\n <span className={[s.chevron, collapsed && s.chevronCollapsed].filter(Boolean).join(' ')} aria-hidden=\"true\">▾</span>\n <span className={s.title}>{title}</span>\n </button>\n ) : (\n <span className={s.title}>{title}</span>\n )}\n {onHide !== undefined && (\n <TooltipTrigger>\n <Focusable>\n <button\n type=\"button\"\n className={s.hideButton}\n onClick={onHide}\n aria-label=\"Hide panel\"\n >\n ×\n </button>\n </Focusable>\n <Tooltip>Hide panel</Tooltip>\n </TooltipTrigger>\n )}\n </div>\n )}\n {!collapsed && <div className={s.body}>{children}</div>}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;ACsCA,SAAgB,EAAa,GAA0B;CACrD,IAAM,EAAE,UAAO,cAAW,qBAAkB,WAAQ,aAAU,iBAAc,GACtE,IAAM;EAAC,EAAE;EAAO,KAAa,EAAE;EAAW;EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,EAC9E,IAAoB,MAAqB,KAAA;AAC/C,QACE,kBAAC,OAAD;EAAK,WAAW;YAAhB,CACG,MAAU,KAAA,KACT,kBAAC,OAAD;GAAK,WAAW,EAAE;aAAlB,CACG,IACC,kBAAC,UAAD;IACE,MAAK;IACL,WAAW,EAAE;IACb,SAAS;IACT,iBAAe,CAAC;cAJlB,CAME,kBAAC,QAAD;KAAM,WAAW,CAAC,EAAE,SAAS,KAAa,EAAE,iBAAiB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;KAAE,eAAY;eAAO;KAAQ,CAAA,EACpH,kBAAC,QAAD;KAAM,WAAW,EAAE;eAAQ;KAAa,CAAA,CACjC;QAET,kBAAC,QAAD;IAAM,WAAW,EAAE;cAAQ;IAAa,CAAA,EAEzC,MAAW,KAAA,KACV,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,UAAD;IACE,MAAK;IACL,WAAW,EAAE;IACb,SAAS;IACT,cAAW;cACZ;IAEQ,CAAA,EACC,CAAA,EACZ,kBAAC,GAAD,EAAA,UAAS,cAAoB,CAAA,CACd,EAAA,CAAA,CAEf;MAEP,CAAC,KAAa,kBAAC,OAAD;GAAK,WAAW,EAAE;GAAO;GAAe,CAAA,CACnD"}
|