@weasel-js/ui 1.5.1 → 1.6.0
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-CYvfmEz0.js +57 -0
- package/dist/chunks/ActionBar-CYvfmEz0.js.map +1 -0
- package/dist/chunks/{Badge-Bf8Rd1R6.js → Badge-DZ0lFs14.js} +755 -738
- package/dist/chunks/Badge-DZ0lFs14.js.map +1 -0
- package/dist/chunks/{BandEditor-D8I5Uun-.js → BandEditor-D-nVrVO4.js} +44 -44
- package/dist/chunks/BandEditor-D-nVrVO4.js.map +1 -0
- package/dist/chunks/Button-Dyp31jMt.js +111 -0
- package/dist/chunks/Button-Dyp31jMt.js.map +1 -0
- package/dist/chunks/ButtonBar-smZzFuEo.js +46 -0
- package/dist/chunks/ButtonBar-smZzFuEo.js.map +1 -0
- package/dist/chunks/Callout-xLjnx7Mp.js +119 -0
- package/dist/chunks/Callout-xLjnx7Mp.js.map +1 -0
- package/dist/chunks/{Checkbox-BZZsPaVF.js → Checkbox-ChZz30Ds.js} +5 -5
- package/dist/chunks/Checkbox-ChZz30Ds.js.map +1 -0
- package/dist/chunks/Code-v4tDWuuN.js +24 -0
- package/dist/chunks/Code-v4tDWuuN.js.map +1 -0
- package/dist/chunks/{ColorField-YVf8irfr.js → ColorField-DJSCuSJv.js} +15 -15
- package/dist/chunks/{ColorField-YVf8irfr.js.map → ColorField-DJSCuSJv.js.map} +1 -1
- package/dist/chunks/ColorModeControl-JOEiqtE0.js +47 -0
- package/dist/chunks/ColorModeControl-JOEiqtE0.js.map +1 -0
- package/dist/chunks/{ComboBox-D57tNYDD.js → ComboBox-C1JgIpML.js} +13 -13
- package/dist/chunks/ComboBox-C1JgIpML.js.map +1 -0
- package/dist/chunks/{CurveEditor-BXiMoN9d.js → CurveEditor-f1R4kSbD.js} +2 -2
- package/dist/chunks/{CurveEditor-BXiMoN9d.js.map → CurveEditor-f1R4kSbD.js.map} +1 -1
- package/dist/chunks/DataGrid-CZPrB_3a.js +152 -0
- package/dist/chunks/DataGrid-CZPrB_3a.js.map +1 -0
- package/dist/chunks/DetailList-DHaXX4TG.js +43 -0
- package/dist/chunks/DetailList-DHaXX4TG.js.map +1 -0
- package/dist/chunks/{DetentSlider-BWNSv07Z.js → DetentSlider-BYyaZha6.js} +2 -2
- package/dist/chunks/{DetentSlider-BWNSv07Z.js.map → DetentSlider-BYyaZha6.js.map} +1 -1
- package/dist/chunks/Dialog-GIvjoDQj.js +66 -0
- package/dist/chunks/Dialog-GIvjoDQj.js.map +1 -0
- package/dist/chunks/Disclosure-D79jhmh2.js +58 -0
- package/dist/chunks/Disclosure-D79jhmh2.js.map +1 -0
- package/dist/chunks/DragGhost-DcU7cOwy.js +30 -0
- package/dist/chunks/DragGhost-DcU7cOwy.js.map +1 -0
- package/dist/chunks/{DragHandleGlyph-CppAz4_8.js → DragHandleGlyph-BtBxHjZS.js} +1 -1
- package/dist/chunks/{DragHandleGlyph-CppAz4_8.js.map → DragHandleGlyph-BtBxHjZS.js.map} +1 -1
- package/dist/chunks/{Field-BNqvUJfg.js → Field-xbqRmjsI.js} +6 -6
- package/dist/chunks/Field-xbqRmjsI.js.map +1 -0
- package/dist/chunks/FillStrokeSwatch-BViKXIPx.js +111 -0
- package/dist/chunks/FillStrokeSwatch-BViKXIPx.js.map +1 -0
- package/dist/chunks/{FontFamilySelect-CO19kyOO.js → FontFamilySelect-r9TV_xIH.js} +2 -2
- package/dist/chunks/{FontFamilySelect-CO19kyOO.js.map → FontFamilySelect-r9TV_xIH.js.map} +1 -1
- package/dist/chunks/GradientEditor-BRTxHG-W.js +333 -0
- package/dist/chunks/GradientEditor-BRTxHG-W.js.map +1 -0
- package/dist/chunks/{Icon-QhLhaGFe.js → Icon-BW0qv_U7.js} +50 -21
- package/dist/chunks/Icon-BW0qv_U7.js.map +1 -0
- package/dist/chunks/InlineRange-BwGFDhpT.js +29 -0
- package/dist/chunks/InlineRange-BwGFDhpT.js.map +1 -0
- package/dist/chunks/{Input-B9Vh7ASP.js → Input-Bx2cuUbS.js} +24 -19
- package/dist/chunks/Input-Bx2cuUbS.js.map +1 -0
- package/dist/chunks/ItemList-DzJ2lK0R.js +175 -0
- package/dist/chunks/ItemList-DzJ2lK0R.js.map +1 -0
- package/dist/chunks/ItemList.module-DDUXfuZL.js +14 -0
- package/dist/chunks/ItemList.module-DDUXfuZL.js.map +1 -0
- package/dist/chunks/{Jog-BYrxbAQU.js → Jog-LPoac9s4.js} +10 -10
- package/dist/chunks/Jog-LPoac9s4.js.map +1 -0
- package/dist/chunks/{Keycaps-CGNQV2Pz.js → Keycaps-CKiKOJnJ.js} +75 -69
- package/dist/chunks/Keycaps-CKiKOJnJ.js.map +1 -0
- package/dist/chunks/LayerList-BeGrkLb9.js +427 -0
- package/dist/chunks/LayerList-BeGrkLb9.js.map +1 -0
- package/dist/chunks/ListEditor-C6zLzCsJ.js +74 -0
- package/dist/chunks/ListEditor-C6zLzCsJ.js.map +1 -0
- package/dist/chunks/MenuButton-BcdhrNGh.js +70 -0
- package/dist/chunks/MenuButton-BcdhrNGh.js.map +1 -0
- package/dist/chunks/MeshEditor-BOOIFWnz.js +87 -0
- package/dist/chunks/MeshEditor-BOOIFWnz.js.map +1 -0
- package/dist/chunks/{NumberField-CQzS6TjF.js → NumberField-C1Y7zP-a.js} +27 -22
- package/dist/chunks/NumberField-C1Y7zP-a.js.map +1 -0
- package/dist/chunks/{OptionsBar-BWaYG_OH.js → OptionsBar-DlcagPYK.js} +3 -3
- package/dist/chunks/{OptionsBar-BWaYG_OH.js.map → OptionsBar-DlcagPYK.js.map} +1 -1
- package/dist/chunks/{PaintField-BVYMu3OI.js → PaintField-B2mLRZa4.js} +20 -20
- package/dist/chunks/PaintField-B2mLRZa4.js.map +1 -0
- package/dist/chunks/{PaintInput-BzAHCKro.js → PaintInput-BFKhd83I.js} +27 -27
- package/dist/chunks/PaintInput-BFKhd83I.js.map +1 -0
- package/dist/chunks/{PatternPicker-C4OryrYi.js → PatternPicker-DdfpxoQy.js} +6 -6
- package/dist/chunks/PatternPicker-DdfpxoQy.js.map +1 -0
- package/dist/chunks/{Plot2D-Czvyt_vG.js → Plot2D-lyO7waPQ.js} +1 -1
- package/dist/chunks/{Plot2D-Czvyt_vG.js.map → Plot2D-lyO7waPQ.js.map} +1 -1
- package/dist/chunks/{PointPlotter-Xvb98GwB.js → PointPlotter-DGs9i5P-.js} +2 -2
- package/dist/chunks/{PointPlotter-Xvb98GwB.js.map → PointPlotter-DGs9i5P-.js.map} +1 -1
- package/dist/chunks/{Powerline-B_EH-7Z-.js → Powerline-DBEPceLE.js} +7 -5
- package/dist/chunks/Powerline-DBEPceLE.js.map +1 -0
- package/dist/chunks/Prefs-Ctu0H4-b.js +475 -0
- package/dist/chunks/Prefs-Ctu0H4-b.js.map +1 -0
- package/dist/chunks/Properties-Bbneh1ws.js +281 -0
- package/dist/chunks/Properties-Bbneh1ws.js.map +1 -0
- package/dist/chunks/PropertyField-CDb19d3F.js +756 -0
- package/dist/chunks/PropertyField-CDb19d3F.js.map +1 -0
- package/dist/chunks/{RadioGroup-C6xpdQNg.js → RadioGroup-BK2mD6Q0.js} +6 -6
- package/dist/chunks/RadioGroup-BK2mD6Q0.js.map +1 -0
- package/dist/chunks/{RangeSlider-CzgeGLVS.js → RangeSlider-BiRAOsTh.js} +8 -8
- package/dist/chunks/RangeSlider-BiRAOsTh.js.map +1 -0
- package/dist/chunks/{ResizeHandle-1Ey8AOij.js → ResizeHandle-Cc6EOehs.js} +9 -9
- package/dist/chunks/{ResizeHandle-1Ey8AOij.js.map → ResizeHandle-Cc6EOehs.js.map} +1 -1
- package/dist/chunks/Select-DVValBe1.js +225 -0
- package/dist/chunks/Select-DVValBe1.js.map +1 -0
- package/dist/chunks/SelectionPanel-C5LmZA7b.js +103 -0
- package/dist/chunks/SelectionPanel-C5LmZA7b.js.map +1 -0
- package/dist/chunks/{Sidebar-BlUjn8lD.js → Sidebar-DKKd4R-x.js} +4 -4
- package/dist/chunks/Sidebar-DKKd4R-x.js.map +1 -0
- package/dist/chunks/{SidebarPanel-BqHfz6VK.js → SidebarPanel-u5NsuAZY.js} +15 -19
- package/dist/chunks/SidebarPanel-u5NsuAZY.js.map +1 -0
- package/dist/chunks/{Slider-B2ywUZH6.js → Slider-BwRrWHFW.js} +58 -58
- package/dist/chunks/Slider-BwRrWHFW.js.map +1 -0
- package/dist/chunks/{StatusBar-BFt7Yw5O.js → StatusBar-CdPdmmda.js} +6 -6
- package/dist/chunks/StatusBar-CdPdmmda.js.map +1 -0
- package/dist/chunks/SwatchGrid-C-Zx2Exd.js +73 -0
- package/dist/chunks/SwatchGrid-C-Zx2Exd.js.map +1 -0
- package/dist/chunks/{Switch-Om3t3ASc.js → Switch-DxF7TsEu.js} +5 -5
- package/dist/chunks/Switch-DxF7TsEu.js.map +1 -0
- package/dist/chunks/{Tabs-EAvYywil.js → Tabs-GXEfoe3o.js} +5 -5
- package/dist/chunks/Tabs-GXEfoe3o.js.map +1 -0
- package/dist/chunks/{Timeline-BEyKaBXl.js → Timeline-SgmpQtUV.js} +128 -132
- package/dist/chunks/Timeline-SgmpQtUV.js.map +1 -0
- package/dist/chunks/{Toast-iqLDD_XZ.js → Toast-BfcljZz6.js} +15 -15
- package/dist/chunks/Toast-BfcljZz6.js.map +1 -0
- package/dist/chunks/ToggleBar-Btnl5r7F.js +80 -0
- package/dist/chunks/ToggleBar-Btnl5r7F.js.map +1 -0
- package/dist/chunks/TokenPanel-5ajHEHha.js +634 -0
- package/dist/chunks/TokenPanel-5ajHEHha.js.map +1 -0
- package/dist/chunks/{ToolButton-Dd-1T5aX.js → ToolButton-rzqlFlAc.js} +9 -9
- package/dist/chunks/ToolButton-rzqlFlAc.js.map +1 -0
- package/dist/chunks/{ToolGroup-BBI-W-Gm.js → ToolGroup-4bUBV5k6.js} +3 -3
- package/dist/chunks/ToolGroup-4bUBV5k6.js.map +1 -0
- package/dist/chunks/ToolOptionsBar-BF3TNXA3.js +85 -0
- package/dist/chunks/ToolOptionsBar-BF3TNXA3.js.map +1 -0
- package/dist/chunks/ToolPalette-Uw4PvWwa.js +83 -0
- package/dist/chunks/ToolPalette-Uw4PvWwa.js.map +1 -0
- package/dist/chunks/Tooltip-9XiJF937.js +42 -0
- package/dist/chunks/Tooltip-9XiJF937.js.map +1 -0
- package/dist/chunks/Tree-BGt6P8tI.js +203 -0
- package/dist/chunks/Tree-BGt6P8tI.js.map +1 -0
- package/dist/chunks/{createKeyframeLayer-7j7ukrSx.js → createKeyframeLayer-BzDRCrrh.js} +71 -71
- package/dist/chunks/{createKeyframeLayer-7j7ukrSx.js.map → createKeyframeLayer-BzDRCrrh.js.map} +1 -1
- package/dist/chunks/formatShortcut-BoGqUoWi.js +15 -0
- package/dist/chunks/formatShortcut-BoGqUoWi.js.map +1 -0
- package/dist/chunks/icons-DbScMMqX.js +179 -0
- package/dist/chunks/icons-DbScMMqX.js.map +1 -0
- package/dist/chunks/interactiveTarget-C7QUBaJN.js +24 -0
- package/dist/chunks/interactiveTarget-C7QUBaJN.js.map +1 -0
- package/dist/chunks/{keyGlyph-Bp0wnLHn.js → keyGlyph-CLvUHU8c.js} +1 -1
- package/dist/chunks/{keyGlyph-Bp0wnLHn.js.map → keyGlyph-CLvUHU8c.js.map} +1 -1
- package/dist/chunks/model-BQMvTLnW.js +360 -0
- package/dist/chunks/model-BQMvTLnW.js.map +1 -0
- package/dist/chunks/number-6S8IzXVt.js +89 -0
- package/dist/chunks/number-6S8IzXVt.js.map +1 -0
- package/dist/chunks/paintPreview-DPtZS4yU.js +42 -0
- package/dist/chunks/paintPreview-DPtZS4yU.js.map +1 -0
- package/dist/chunks/paintValue-Bz6AVuh3.js +18 -0
- package/dist/chunks/paintValue-Bz6AVuh3.js.map +1 -0
- package/dist/chunks/prefField-CLkhrvYl.js +197 -0
- package/dist/chunks/prefField-CLkhrvYl.js.map +1 -0
- package/dist/chunks/segmentTooltip-CAkZlW-t.js +29 -0
- package/dist/chunks/segmentTooltip-CAkZlW-t.js.map +1 -0
- package/dist/chunks/segmentedControl.module-D2nw68Yo.js +11 -0
- package/dist/chunks/segmentedControl.module-D2nw68Yo.js.map +1 -0
- package/dist/chunks/{snap-B0WQwetQ.js → snap-DC4Km8xG.js} +1 -1
- package/dist/chunks/{snap-B0WQwetQ.js.map → snap-DC4Km8xG.js.map} +1 -1
- package/dist/chunks/stance-CarrcbbP.js +17 -0
- package/dist/chunks/stance-CarrcbbP.js.map +1 -0
- package/dist/chunks/useReorderDragList-Dcezzgix.js +131 -0
- package/dist/chunks/useReorderDragList-Dcezzgix.js.map +1 -0
- package/dist/chunks/{useRovingTabIndex-Dkpf_KhN.js → useRovingTabIndex-B4uJqiKP.js} +1 -1
- package/dist/chunks/useRovingTabIndex-B4uJqiKP.js.map +1 -0
- package/dist/color/oklch.d.ts +2 -1
- package/dist/color/oklch.d.ts.map +1 -1
- package/dist/color/ramp.d.ts +38 -0
- package/dist/color/ramp.d.ts.map +1 -0
- package/dist/components/ActionBar/ActionBar.d.ts +6 -4
- package/dist/components/ActionBar/ActionBar.d.ts.map +1 -1
- package/dist/components/ActionBar/index.js +1 -1
- package/dist/components/Badge/Badge.d.ts +21 -7
- package/dist/components/Badge/Badge.d.ts.map +1 -1
- package/dist/components/Badge/effects/Aqua.d.ts +1 -1
- package/dist/components/Badge/effects/Aqua.d.ts.map +1 -1
- package/dist/components/Badge/index.d.ts +1 -1
- package/dist/components/Badge/index.d.ts.map +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Badge/types.d.ts +6 -5
- package/dist/components/Badge/types.d.ts.map +1 -1
- package/dist/components/BandEditor/index.js +1 -1
- package/dist/components/Button/Button.d.ts +17 -3
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/Button/index.d.ts +1 -1
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Button/index.js +1 -1
- package/dist/components/ButtonBar/ButtonBar.d.ts +36 -0
- package/dist/components/ButtonBar/ButtonBar.d.ts.map +1 -0
- package/dist/components/ButtonBar/index.d.ts +3 -0
- package/dist/components/ButtonBar/index.d.ts.map +1 -0
- package/dist/components/ButtonBar/index.js +2 -0
- package/dist/components/Callout/Callout.d.ts +2 -5
- package/dist/components/Callout/Callout.d.ts.map +1 -1
- package/dist/components/Callout/index.d.ts +1 -1
- package/dist/components/Callout/index.d.ts.map +1 -1
- package/dist/components/Callout/index.js +1 -1
- package/dist/components/Checkbox/index.js +1 -1
- package/dist/components/Code/Code.d.ts +28 -0
- package/dist/components/Code/Code.d.ts.map +1 -0
- package/dist/components/Code/index.d.ts +3 -0
- package/dist/components/Code/index.d.ts.map +1 -0
- package/dist/components/Code/index.js +2 -0
- package/dist/components/ColorField/index.js +1 -1
- package/dist/components/ColorModeControl/ColorModeControl.d.ts +21 -0
- package/dist/components/ColorModeControl/ColorModeControl.d.ts.map +1 -0
- package/dist/components/ColorModeControl/index.d.ts +3 -0
- package/dist/components/ColorModeControl/index.d.ts.map +1 -0
- package/dist/components/ColorModeControl/index.js +2 -0
- package/dist/components/ComboBox/index.js +1 -1
- package/dist/components/CurveEditor/index.js +3 -3
- package/dist/components/DataGrid/DataGrid.d.ts +22 -0
- package/dist/components/DataGrid/DataGrid.d.ts.map +1 -1
- package/dist/components/DataGrid/index.js +1 -1
- package/dist/components/DetailList/DetailList.d.ts +36 -0
- package/dist/components/DetailList/DetailList.d.ts.map +1 -0
- package/dist/components/DetailList/index.d.ts +3 -0
- package/dist/components/DetailList/index.d.ts.map +1 -0
- package/dist/components/DetailList/index.js +2 -0
- package/dist/components/DetentSlider/index.js +1 -1
- package/dist/components/Dialog/Dialog.d.ts +8 -2
- package/dist/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog/index.js +1 -1
- package/dist/components/Disclosure/Disclosure.d.ts +25 -17
- package/dist/components/Disclosure/Disclosure.d.ts.map +1 -1
- package/dist/components/Disclosure/index.d.ts +2 -2
- package/dist/components/Disclosure/index.d.ts.map +1 -1
- package/dist/components/Disclosure/index.js +2 -2
- package/dist/components/DragGhost/DragGhost.d.ts +21 -0
- package/dist/components/DragGhost/DragGhost.d.ts.map +1 -0
- package/dist/components/DragGhost/index.d.ts +3 -0
- package/dist/components/DragGhost/index.d.ts.map +1 -0
- package/dist/components/DragGhost/index.js +2 -0
- package/dist/components/DragHandleGlyph/index.js +1 -1
- package/dist/components/Field/index.js +1 -1
- package/dist/components/FillStrokeSwatch/FillStrokeSwatch.d.ts +45 -0
- package/dist/components/FillStrokeSwatch/FillStrokeSwatch.d.ts.map +1 -0
- package/dist/components/FillStrokeSwatch/index.d.ts +3 -0
- package/dist/components/FillStrokeSwatch/index.d.ts.map +1 -0
- package/dist/components/FillStrokeSwatch/index.js +2 -0
- package/dist/components/FontFamilySelect/index.js +1 -1
- package/dist/components/GradientEditor/SceneGradientHandles.d.ts.map +1 -1
- package/dist/components/GradientEditor/index.js +1 -1
- package/dist/components/InlineRange/InlineRange.d.ts +6 -2
- package/dist/components/InlineRange/InlineRange.d.ts.map +1 -1
- package/dist/components/InlineRange/index.js +1 -1
- package/dist/components/Input/Input.d.ts +13 -0
- package/dist/components/Input/Input.d.ts.map +1 -1
- package/dist/components/Input/index.js +1 -1
- package/dist/components/ItemList/ItemList.d.ts +70 -10
- package/dist/components/ItemList/ItemList.d.ts.map +1 -1
- package/dist/components/ItemList/index.js +1 -1
- package/dist/components/Jog/index.js +1 -1
- package/dist/components/Keycaps/index.d.ts +1 -0
- package/dist/components/Keycaps/index.d.ts.map +1 -1
- package/dist/components/Keycaps/index.js +3 -3
- package/dist/components/Keycaps/keySpecsFromMods.d.ts +2 -0
- package/dist/components/Keycaps/keySpecsFromMods.d.ts.map +1 -1
- package/dist/components/Keycaps/keySpecsFromShortcut.d.ts +16 -0
- package/dist/components/Keycaps/keySpecsFromShortcut.d.ts.map +1 -0
- package/dist/components/LayerList/LayerList.d.ts +79 -0
- package/dist/components/LayerList/LayerList.d.ts.map +1 -0
- package/dist/components/LayerList/index.d.ts +3 -0
- package/dist/components/LayerList/index.d.ts.map +1 -0
- package/dist/components/LayerList/index.js +2 -0
- package/dist/components/LayerList/useSceneLayerList.d.ts +20 -0
- package/dist/components/LayerList/useSceneLayerList.d.ts.map +1 -0
- package/dist/components/ListEditor/ListEditor.d.ts +23 -0
- package/dist/components/ListEditor/ListEditor.d.ts.map +1 -0
- package/dist/components/ListEditor/index.d.ts +3 -0
- package/dist/components/ListEditor/index.d.ts.map +1 -0
- package/dist/components/ListEditor/index.js +2 -0
- package/dist/components/MenuButton/MenuButton.d.ts +6 -2
- package/dist/components/MenuButton/MenuButton.d.ts.map +1 -1
- package/dist/components/MenuButton/index.js +1 -1
- package/dist/components/MeshEditor/index.js +1 -1
- package/dist/components/NumberField/NumberField.d.ts +13 -0
- package/dist/components/NumberField/NumberField.d.ts.map +1 -1
- package/dist/components/NumberField/UnitField.d.ts +2 -1
- package/dist/components/NumberField/UnitField.d.ts.map +1 -1
- package/dist/components/NumberField/index.js +1 -1
- package/dist/components/OptionsBar/index.js +1 -1
- package/dist/components/PaintField/index.js +1 -1
- package/dist/components/PaintInput/index.js +1 -1
- package/dist/components/PatternPicker/index.js +1 -1
- package/dist/components/Plot2D/index.js +1 -1
- package/dist/components/PointPlotter/index.js +1 -1
- package/dist/components/Powerline/Powerline.d.ts +4 -3
- package/dist/components/Powerline/Powerline.d.ts.map +1 -1
- package/dist/components/Powerline/index.js +2 -2
- package/dist/components/Prefs/PrefsDialog.d.ts +10 -2
- package/dist/components/Prefs/PrefsDialog.d.ts.map +1 -1
- package/dist/components/Prefs/PrefsForm.d.ts +26 -25
- package/dist/components/Prefs/PrefsForm.d.ts.map +1 -1
- package/dist/components/Prefs/PrefsPane.d.ts +24 -0
- package/dist/components/Prefs/PrefsPane.d.ts.map +1 -0
- package/dist/components/Prefs/PrefsRail.d.ts +32 -0
- package/dist/components/Prefs/PrefsRail.d.ts.map +1 -0
- package/dist/components/Prefs/PrefsRow.d.ts +32 -0
- package/dist/components/Prefs/PrefsRow.d.ts.map +1 -0
- package/dist/components/Prefs/index.d.ts +5 -2
- package/dist/components/Prefs/index.d.ts.map +1 -1
- package/dist/components/Prefs/index.js +3 -3
- package/dist/components/Prefs/prefField.d.ts +36 -0
- package/dist/components/Prefs/prefField.d.ts.map +1 -0
- package/dist/components/Prefs/schema.d.ts +32 -1
- package/dist/components/Prefs/schema.d.ts.map +1 -1
- package/dist/components/Properties/DialogRow.d.ts +33 -0
- package/dist/components/Properties/DialogRow.d.ts.map +1 -0
- package/dist/components/Properties/PropertyField.d.ts +238 -0
- package/dist/components/Properties/PropertyField.d.ts.map +1 -0
- package/dist/components/Properties/PropertyGroup.d.ts +18 -3
- package/dist/components/Properties/PropertyGroup.d.ts.map +1 -1
- package/dist/components/Properties/PropertyPanel.d.ts +50 -256
- package/dist/components/Properties/PropertyPanel.d.ts.map +1 -1
- package/dist/components/Properties/Subpanel.d.ts +14 -0
- package/dist/components/Properties/Subpanel.d.ts.map +1 -0
- package/dist/components/Properties/index.d.ts +8 -6
- package/dist/components/Properties/index.d.ts.map +1 -1
- package/dist/components/Properties/index.js +3 -2
- package/dist/components/Properties/storyLayouts.d.ts.map +1 -1
- package/dist/components/RadioGroup/index.js +1 -1
- package/dist/components/RangeSlider/index.js +1 -1
- package/dist/components/ResizeHandle/index.js +1 -1
- package/dist/components/Select/Select.d.ts +56 -5
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/index.d.ts +2 -2
- package/dist/components/Select/index.d.ts.map +1 -1
- package/dist/components/Select/index.js +2 -2
- package/dist/components/SelectionPanel/SelectionPanel.d.ts +3 -50
- package/dist/components/SelectionPanel/SelectionPanel.d.ts.map +1 -1
- package/dist/components/SelectionPanel/index.js +3 -2
- package/dist/components/SelectionPanel/model.d.ts +4 -1
- package/dist/components/SelectionPanel/model.d.ts.map +1 -1
- package/dist/components/SelectionPanel/renderLeaf.d.ts +112 -0
- package/dist/components/SelectionPanel/renderLeaf.d.ts.map +1 -0
- package/dist/components/Sidebar/index.js +1 -1
- package/dist/components/SidebarPanel/SidebarPanel.d.ts +2 -2
- package/dist/components/SidebarPanel/SidebarPanel.d.ts.map +1 -1
- package/dist/components/SidebarPanel/index.js +1 -1
- package/dist/components/Slider/index.js +1 -1
- package/dist/components/StatusBar/index.js +1 -1
- package/dist/components/SwatchGrid/SwatchGrid.d.ts +34 -0
- package/dist/components/SwatchGrid/SwatchGrid.d.ts.map +1 -0
- package/dist/components/SwatchGrid/index.d.ts +3 -0
- package/dist/components/SwatchGrid/index.d.ts.map +1 -0
- package/dist/components/SwatchGrid/index.js +2 -0
- package/dist/components/Switch/index.js +1 -1
- package/dist/components/Tabs/index.js +1 -1
- package/dist/components/Timeline/Lane.d.ts.map +1 -1
- package/dist/components/Timeline/index.js +1 -1
- package/dist/components/Toast/index.js +1 -1
- package/dist/components/ToggleBar/ToggleBar.d.ts +2 -1
- package/dist/components/ToggleBar/ToggleBar.d.ts.map +1 -1
- package/dist/components/ToggleBar/index.js +1 -1
- package/dist/components/TokenPanel/TokenPanel.d.ts +10 -4
- package/dist/components/TokenPanel/TokenPanel.d.ts.map +1 -1
- package/dist/components/TokenPanel/index.d.ts +2 -2
- package/dist/components/TokenPanel/index.d.ts.map +1 -1
- package/dist/components/TokenPanel/index.js +2 -2
- package/dist/components/TokenPanel/tokenTypes.d.ts +24 -0
- package/dist/components/TokenPanel/tokenTypes.d.ts.map +1 -1
- package/dist/components/ToolButton/index.js +1 -1
- package/dist/components/ToolGroup/index.js +1 -1
- package/dist/components/ToolOptionsBar/ToolOptionsBar.d.ts +29 -18
- 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 +5 -4
- package/dist/components/ToolPalette/ToolPalette.d.ts.map +1 -1
- package/dist/components/ToolPalette/index.d.ts +1 -0
- package/dist/components/ToolPalette/index.d.ts.map +1 -1
- package/dist/components/ToolPalette/index.js +3 -2
- package/dist/components/Tooltip/Tooltip.d.ts +6 -2
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/index.d.ts +1 -1
- package/dist/components/Tooltip/index.d.ts.map +1 -1
- package/dist/components/Tooltip/index.js +2 -2
- package/dist/components/Tree/Tree.d.ts +84 -0
- package/dist/components/Tree/Tree.d.ts.map +1 -0
- package/dist/components/Tree/index.d.ts +3 -0
- package/dist/components/Tree/index.d.ts.map +1 -0
- package/dist/components/Tree/index.js +2 -0
- package/dist/components/segmentTooltip.d.ts +30 -0
- package/dist/components/segmentTooltip.d.ts.map +1 -0
- package/dist/components/stance.d.ts +30 -0
- package/dist/components/stance.d.ts.map +1 -0
- package/dist/components/stanceCss.d.ts +31 -0
- package/dist/components/stanceCss.d.ts.map +1 -0
- package/dist/components/stanceSurfaces.d.ts +4 -0
- package/dist/components/stanceSurfaces.d.ts.map +1 -0
- package/dist/format/number.d.ts +13 -3
- package/dist/format/number.d.ts.map +1 -1
- package/dist/icons/Icon.d.ts.map +1 -1
- package/dist/icons/ShapeKindIcon.d.ts +10 -0
- package/dist/icons/ShapeKindIcon.d.ts.map +1 -0
- package/dist/icons/index.d.ts +5 -3
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/icons/paths.d.ts +11 -1
- package/dist/icons/paths.d.ts.map +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -84
- package/dist/index.js.map +1 -1
- package/dist/interactiveTarget.d.ts +9 -0
- package/dist/interactiveTarget.d.ts.map +1 -0
- package/dist/style.css +1 -1
- package/dist/useReorderDragList.d.ts +36 -7
- package/dist/useReorderDragList.d.ts.map +1 -1
- package/dist/useRovingTabIndex.d.ts +8 -6
- package/dist/useRovingTabIndex.d.ts.map +1 -1
- package/dist/useScrollSpy.d.ts +48 -0
- package/dist/useScrollSpy.d.ts.map +1 -0
- package/package.json +8 -8
- package/dist/chunks/ActionBar-GZdcfdQL.js +0 -62
- package/dist/chunks/ActionBar-GZdcfdQL.js.map +0 -1
- package/dist/chunks/ActionsBar-Fhx_40iP.js +0 -41
- package/dist/chunks/ActionsBar-Fhx_40iP.js.map +0 -1
- package/dist/chunks/Badge-Bf8Rd1R6.js.map +0 -1
- package/dist/chunks/BandEditor-D8I5Uun-.js.map +0 -1
- package/dist/chunks/Button-CExdkQcU.js +0 -90
- package/dist/chunks/Button-CExdkQcU.js.map +0 -1
- package/dist/chunks/Callout-cx5_CWcW.js +0 -122
- package/dist/chunks/Callout-cx5_CWcW.js.map +0 -1
- package/dist/chunks/Checkbox-BZZsPaVF.js.map +0 -1
- package/dist/chunks/ComboBox-D57tNYDD.js.map +0 -1
- package/dist/chunks/DataGrid-BVdDMZkQ.js +0 -104
- package/dist/chunks/DataGrid-BVdDMZkQ.js.map +0 -1
- package/dist/chunks/Dialog-DuOzsR2g.js +0 -61
- package/dist/chunks/Dialog-DuOzsR2g.js.map +0 -1
- package/dist/chunks/Disclosure-9UgDzc7Z.js +0 -47
- package/dist/chunks/Disclosure-9UgDzc7Z.js.map +0 -1
- package/dist/chunks/Field-BNqvUJfg.js.map +0 -1
- package/dist/chunks/GradientEditor-B_ymIr4Q.js +0 -333
- package/dist/chunks/GradientEditor-B_ymIr4Q.js.map +0 -1
- package/dist/chunks/Icon-QhLhaGFe.js.map +0 -1
- package/dist/chunks/InlineRange-DdIlfGqG.js +0 -29
- package/dist/chunks/InlineRange-DdIlfGqG.js.map +0 -1
- package/dist/chunks/Input-B9Vh7ASP.js.map +0 -1
- package/dist/chunks/ItemList-BZJQReXD.js +0 -42
- package/dist/chunks/ItemList-BZJQReXD.js.map +0 -1
- package/dist/chunks/Jog-BYrxbAQU.js.map +0 -1
- package/dist/chunks/Keycaps-CGNQV2Pz.js.map +0 -1
- package/dist/chunks/LayerStack-DOylrQcm.js +0 -142
- package/dist/chunks/LayerStack-DOylrQcm.js.map +0 -1
- package/dist/chunks/MenuButton-DGE-0DP0.js +0 -60
- package/dist/chunks/MenuButton-DGE-0DP0.js.map +0 -1
- package/dist/chunks/MeshEditor-CHoGQ9K8.js +0 -123
- package/dist/chunks/MeshEditor-CHoGQ9K8.js.map +0 -1
- package/dist/chunks/NumberField-CQzS6TjF.js.map +0 -1
- package/dist/chunks/PaintField-BVYMu3OI.js.map +0 -1
- package/dist/chunks/PaintInput-BzAHCKro.js.map +0 -1
- package/dist/chunks/PatternPicker-C4OryrYi.js.map +0 -1
- package/dist/chunks/Powerline-B_EH-7Z-.js.map +0 -1
- package/dist/chunks/Prefs-FuaJBs3t.js +0 -308
- package/dist/chunks/Prefs-FuaJBs3t.js.map +0 -1
- package/dist/chunks/Properties-B3KU69NI.js +0 -874
- package/dist/chunks/Properties-B3KU69NI.js.map +0 -1
- package/dist/chunks/RadioGroup-C6xpdQNg.js.map +0 -1
- package/dist/chunks/RangeSlider-CzgeGLVS.js.map +0 -1
- package/dist/chunks/Select-BMYVh10x.js +0 -127
- package/dist/chunks/Select-BMYVh10x.js.map +0 -1
- package/dist/chunks/SelectionPanel-DTpcQidl.js +0 -572
- package/dist/chunks/SelectionPanel-DTpcQidl.js.map +0 -1
- package/dist/chunks/Sidebar-BlUjn8lD.js.map +0 -1
- package/dist/chunks/SidebarPanel-BqHfz6VK.js.map +0 -1
- package/dist/chunks/Slider-B2ywUZH6.js.map +0 -1
- package/dist/chunks/StatusBar-BFt7Yw5O.js.map +0 -1
- package/dist/chunks/Switch-Om3t3ASc.js.map +0 -1
- package/dist/chunks/Tabs-EAvYywil.js.map +0 -1
- package/dist/chunks/Timeline-BEyKaBXl.js.map +0 -1
- package/dist/chunks/Toast-iqLDD_XZ.js.map +0 -1
- package/dist/chunks/ToggleBar--9As2PWG.js +0 -75
- package/dist/chunks/ToggleBar--9As2PWG.js.map +0 -1
- package/dist/chunks/TokenPanel-BL9hlq7x.js +0 -374
- package/dist/chunks/TokenPanel-BL9hlq7x.js.map +0 -1
- package/dist/chunks/ToolButton-Dd-1T5aX.js.map +0 -1
- package/dist/chunks/ToolGroup-BBI-W-Gm.js.map +0 -1
- package/dist/chunks/ToolOptionsBar-B2xPWzJf.js +0 -27
- package/dist/chunks/ToolOptionsBar-B2xPWzJf.js.map +0 -1
- package/dist/chunks/ToolPalette-BmTDDpmz.js +0 -97
- package/dist/chunks/ToolPalette-BmTDDpmz.js.map +0 -1
- package/dist/chunks/Tooltip-BHre_vbI.js +0 -42
- package/dist/chunks/Tooltip-BHre_vbI.js.map +0 -1
- package/dist/chunks/icons-Btrs-JD7.js +0 -165
- package/dist/chunks/icons-Btrs-JD7.js.map +0 -1
- package/dist/chunks/number-f6PydOZK.js +0 -52
- package/dist/chunks/number-f6PydOZK.js.map +0 -1
- package/dist/chunks/range.module-DSsOIq1R.js +0 -8
- package/dist/chunks/range.module-DSsOIq1R.js.map +0 -1
- package/dist/chunks/schema-MCVT1MLv.js +0 -47
- package/dist/chunks/schema-MCVT1MLv.js.map +0 -1
- package/dist/chunks/segmentedControl.module-DmeUMYe3.js +0 -11
- package/dist/chunks/segmentedControl.module-DmeUMYe3.js.map +0 -1
- package/dist/chunks/useReorderDragList-CSQGd7zQ.js +0 -102
- package/dist/chunks/useReorderDragList-CSQGd7zQ.js.map +0 -1
- package/dist/chunks/useRovingTabIndex-Dkpf_KhN.js.map +0 -1
- package/dist/components/ActionsBar/ActionsBar.d.ts +0 -35
- package/dist/components/ActionsBar/ActionsBar.d.ts.map +0 -1
- package/dist/components/ActionsBar/index.d.ts +0 -3
- package/dist/components/ActionsBar/index.d.ts.map +0 -1
- package/dist/components/ActionsBar/index.js +0 -2
- package/dist/components/LayerStack/LayerStack.d.ts +0 -62
- package/dist/components/LayerStack/LayerStack.d.ts.map +0 -1
- package/dist/components/LayerStack/index.d.ts +0 -2
- package/dist/components/LayerStack/index.d.ts.map +0 -1
- package/dist/components/LayerStack/index.js +0 -2
- package/dist/components/Properties/EffectCard.d.ts +0 -69
- package/dist/components/Properties/EffectCard.d.ts.map +0 -1
- package/dist/components/Properties/PinDot.d.ts +0 -18
- package/dist/components/Properties/PinDot.d.ts.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SelectionPanel-DTpcQidl.js","names":[],"sources":["../../src/components/SelectionPanel/model.ts","../../src/components/SelectionPanel/SelectionPanel.module.css","../../src/components/SelectionPanel/SelectionPanel.tsx"],"sourcesContent":["// Pure selection→panel derivations for SelectionPanel. Kept free of\n// React so intersection/aggregation semantics are unit-testable.\n//\n// Path convention (see core `NodePropertiesEntry`): a leaf's OWN KEY in\n// the schema is its node path — a dotted path of any depth rooted at\n// `pose` or `data` (`pose.x`, `data.fill`, `data.style.fontSize`). Group\n// keys are organizational only.\n\nimport {\n MIXED,\n type Mixed,\n type NodePropertiesEntry,\n type NodeRoutingEntry,\n type SceneNode,\n type ToolPrefGroup,\n type ToolPrefLeaf,\n} from '@weasel-js/core';\n\n// Re-exported under its existing public name here: the kit-wide \"these\n// values disagree\" sentinel (`@weasel-js/core`'s `MIXED`) also covers\n// multi-node aggregation, so this panel doesn't get its own symbol.\nexport { MIXED };\nexport type { Mixed };\n\nexport type AnyNode = SceneNode<unknown, string, unknown>;\n\n/**\n * One editable property in a {@link SelectionPanel}, paired with the dotted\n * node path it reads and writes.\n */\nexport interface PanelLeaf {\n /** Dotted node path — the leaf's key in the schema. */\n path: string;\n leaf: ToolPrefLeaf;\n}\n\n/**\n * One labeled row of a panel section. Holds several leaves when the schema\n * paired them.\n */\nexport interface PanelRow {\n label: string;\n leaves: PanelLeaf[];\n}\n\n/**\n * A titled block of rows, derived from one top-level group of a properties\n * schema. `key` is that group's schema key.\n */\nexport interface PanelSection {\n key: string;\n name: string;\n rows: PanelRow[];\n}\n\n/** Derive a node's kind: containers are `'group'`; leaves classify their\n * `data` through the routing entries (first match wins, `'unknown'`\n * otherwise) — same semantics as core's `NodeRouting.classify`. */\nexport function classifyKind(\n node: AnyNode,\n routing: readonly NodeRoutingEntry[],\n): string {\n if (node.kind === 'container') return 'group';\n for (const entry of routing) {\n if (entry.matches(node.data)) return entry.name;\n }\n return 'unknown';\n}\n\nfunction isGroup(n: ToolPrefLeaf | ToolPrefGroup): n is ToolPrefGroup {\n return !('kind' in n);\n}\n\n/** Flatten a schema to sections of leaves. Nested groups fold into their\n * top-level section; top-level leaves get an untitled leading section. */\nfunction flatten(schema: ToolPrefGroup): PanelSection[] {\n const sections: PanelSection[] = [];\n const untitled: PanelLeaf[] = [];\n\n const collect = (group: ToolPrefGroup, into: PanelLeaf[]): void => {\n for (const [key, child] of Object.entries(group.children)) {\n if (isGroup(child)) collect(child, into);\n else into.push({ path: key, leaf: child });\n }\n };\n\n for (const [key, child] of Object.entries(schema.children)) {\n if (isGroup(child)) {\n const leaves: PanelLeaf[] = [];\n collect(child, leaves);\n sections.push({ key, name: child.name, rows: pairRows(leaves) });\n } else {\n untitled.push({ path: key, leaf: child });\n }\n }\n if (untitled.length > 0) {\n sections.unshift({ key: '', name: '', rows: pairRows(untitled) });\n }\n return sections;\n}\n\n/** Merge consecutive leaves sharing a `pair` id into one labeled row. */\nfunction pairRows(leaves: readonly PanelLeaf[]): PanelRow[] {\n const rows: PanelRow[] = [];\n for (const item of leaves) {\n const pair = item.leaf.pair;\n const prev = rows[rows.length - 1];\n if (pair !== undefined && prev !== undefined && prev.label === pair) {\n prev.leaves.push(item);\n } else {\n rows.push({ label: pair ?? item.leaf.name, leaves: [item] });\n }\n }\n return rows;\n}\n\n/**\n * The schema the panel shows for a set of kinds: one kind → its full\n * schema; several → the intersection by (path, leaf kind), shaped by the\n * first kind's section/row layout. Kinds without a registered schema\n * contribute nothing, so their presence collapses the intersection.\n */\nexport function effectiveSections(\n kinds: readonly string[],\n entries: readonly NodePropertiesEntry[],\n): PanelSection[] {\n const uniq = [...new Set(kinds)];\n if (uniq.length === 0) return [];\n const byName = new Map(entries.map((e) => [e.name, e]));\n const schemas = uniq.map((k) => byName.get(k)?.schema);\n if (schemas.some((s) => s === undefined)) return [];\n const [first, ...rest] = schemas as ToolPrefGroup[];\n\n const flatFirst = flatten(first);\n if (rest.length === 0) return flatFirst;\n\n // '\\0' delimiter: path and kind strings are unconstrained, so a\n // printable separator could collide with real content.\n const leafKey = (l: PanelLeaf): string => `${l.path}\\0${l.leaf.kind}`;\n const restKeys = rest.map(\n (schema) =>\n new Set(\n flatten(schema)\n .flatMap((s) => s.rows)\n .flatMap((r) => r.leaves)\n .map(leafKey),\n ),\n );\n const keep = (l: PanelLeaf): boolean =>\n restKeys.every((set) => set.has(leafKey(l)));\n\n return flatFirst\n .map((section) => ({\n ...section,\n rows: section.rows\n .map((row) => ({ ...row, leaves: row.leaves.filter(keep) }))\n .filter((row) => row.leaves.length > 0),\n }))\n .filter((section) => section.rows.length > 0);\n}\n\n/** Read a node value at a dotted path of any depth (`pose.x`,\n * `data.style.fontSize`, `data.style.fill.color`). Returns `undefined` if\n * the path is dotless (mirrors `commit`'s no-op on a malformed schema\n * key) or if any intermediate segment is missing or not an object. */\nexport function nodeValueAt(node: AnyNode, path: string): unknown {\n const segments = path.split('.');\n if (segments.length < 2) return undefined;\n const head = segments[0];\n let cursor: unknown = head === 'pose' ? node.pose : head === 'data' ? node.data : undefined;\n for (let i = 1; i < segments.length; i++) {\n if (cursor == null || typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[segments[i]];\n }\n return cursor;\n}\n\n/** Immutably set `value` at a dotted path within `root`, cloning each level\n * on the way down so React sees new object identities. Arrays stay arrays\n * (an array intermediate is shallow-copied, not flattened into `{0: ...}`)\n * — this is the write-side twin of `nodeValueAt`, so a schema author who\n * gets a working read gets a working write. */\nexport function setAtPath(root: object, segments: readonly string[], value: unknown): object {\n const [head, ...rest] = segments;\n if (rest.length === 0) {\n return Array.isArray(root)\n ? Object.assign([...root], { [head]: value })\n : { ...root, [head]: value };\n }\n const child = (root as Record<string, unknown>)[head];\n const childObj = Array.isArray(child)\n ? [...child]\n : child != null && typeof child === 'object'\n ? { ...child }\n : {};\n const nested = setAtPath(childObj, rest, value);\n return Array.isArray(root)\n ? Object.assign([...root], { [head]: nested })\n : { ...root, [head]: nested };\n}\n\n/** Aggregate a path across nodes: the shared value, or `MIXED`. */\nexport function aggregateValue(\n nodes: readonly AnyNode[],\n path: string,\n): unknown | Mixed {\n let value: unknown;\n let first = true;\n for (const node of nodes) {\n const v = nodeValueAt(node, path);\n if (first) {\n value = v;\n first = false;\n } else if (!Object.is(v, value)) {\n return MIXED;\n }\n }\n return value;\n}\n\n/** `'rect ×2 · text'` — kinds in first-seen order with counts. */\nexport function kindBreakdown(kinds: readonly string[]): string {\n const counts = new Map<string, number>();\n for (const k of kinds) counts.set(k, (counts.get(k) ?? 0) + 1);\n return [...counts.entries()]\n .map(([k, n]) => (n > 1 ? `${k} ×${n}` : k))\n .join(' · ');\n}\n",".root {\n display: flex;\n flex-direction: column;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg);\n}\n\n.header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 8px 0;\n border-block-end: 1px solid var(--wzl-border);\n}\n\n.kind {\n font-weight: var(--wzl-font-weight-bold);\n font-size: var(--wzl-font-size);\n}\n\n.breakdown {\n font-size: var(--wzl-font-size-sm);\n opacity: 0.6;\n}\n\n.section {\n padding: 6px 0 8px;\n border-block-end: 1px solid var(--wzl-border);\n}\n\n.section:last-child {\n border-block-end: none;\n}\n\n.sectionTitle {\n margin: 0 0 6px;\n font-size: var(--wzl-font-size-xs);\n font-weight: var(--wzl-font-weight-bold);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.55;\n}\n\n.row {\n display: grid;\n grid-template-columns: 64px 1fr;\n gap: 6px;\n align-items: center;\n margin-block-end: 5px;\n}\n\n.rowLabel {\n opacity: 0.75;\n}\n\n.rowControls {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n align-items: center;\n min-inline-size: 0;\n}\n\n/* Glyph plus its control, held together so the pair row wraps between\n fields rather than between a field and the icon naming it. */\n.namedField {\n display: flex;\n align-items: center;\n gap: 4px;\n min-inline-size: 0;\n}\n\n.namedField > svg {\n flex: none;\n opacity: 0.75;\n}\n\n.blockRow {\n margin-block-end: 5px;\n}\n\n/* Fields paired into one row split it evenly, the way Position's x/y do. */\n.rowControls > .namedField {\n flex: 1 1 0;\n}\n\n/* Except around a segmented bar, which is sized by its segments: an equal\n share of the row is wider than the buttons want to be. */\n.rowControls > .namedField:has(> .toggle) {\n flex: 0 1 auto;\n}\n\n.rowControls > * {\n flex: 1;\n min-inline-size: 0;\n}\n\n/* Bars sharing a row split the leftover between them, so the gap stays fixed\n and the width lands on the segments. A bar with the row to itself is sized\n by its segments instead — a third of 240px is a plate, not a button. */\n.rowControls > .toggle {\n flex: 1 1 auto;\n}\n\n.rowControls > .toggle:only-child {\n flex: 0 1 auto;\n}\n\n/* A flag bar is sized by its glyphs rather than by a share of the row: an\n even split would fling one-letter segments to the margins. */\n.rowControls > .flagToggle {\n flex: 0 0 auto;\n}\n\n.number :global(input),\n.select {\n font-size: var(--wzl-font-size-sm);\n}\n\n.unitSuffix {\n flex: none;\n font-size: var(--wzl-font-size-sm);\n opacity: 0.6;\n}\n\n.mixedSwitch {\n opacity: 0.55;\n}\n\n.unrenderable {\n font-size: var(--wzl-font-size-sm);\n opacity: 0.5;\n font-style: italic;\n}\n\n/* A `block` leaf that renders its own rows — an object leaf's fields. */\n.objectLeaf {\n margin-block: 8px 4px;\n}\n\n.objectLeaf .sectionTitle {\n margin-block: 7px 5px;\n}\n\n/* Fields under a heading inside an object read as belonging to it. */\n.objectGroup {\n padding-inline-start: 8px;\n border-inline-start: 1px solid var(--wzl-border);\n margin-block-end: 4px;\n}\n\n/* `InlineRange` owns the track; the row only says how it shares the space. */\n.slider {\n flex: 1;\n}\n\n.sliderReadout {\n flex: none;\n min-inline-size: 3ch;\n text-align: end;\n font-size: var(--wzl-font-size-sm);\n opacity: 0.7;\n}\n","import { Fragment, useState, useSyncExternalStore, type ReactNode } from 'react';\nimport {\n asNodeId,\n isBuiltinToolPref,\n type FillStyle,\n type NodePropertiesEntry,\n type NodeRoutingEntry,\n type Scene,\n type SelectionApi,\n type ToolPrefBoolean,\n type ToolPrefColor,\n type ToolPrefEnum,\n type ToolPrefLeaf,\n type ToolPrefNumber,\n type ToolPrefPaint,\n type ToolPrefGroup,\n type ToolPrefObject,\n} from '@weasel-js/core';\nimport { prefDisplayBounds, prefUnitAccepts } from '../Prefs/schema';\nimport { ColorField } from '../ColorField';\nimport { FontFamilySelect } from '../FontFamilySelect';\nimport { PaintInput } from '../PaintInput';\nimport { InlineRange } from '../InlineRange';\nimport { Input } from '../Input';\nimport { NumberField, UnitField } from '../NumberField';\nimport { Select } from '../Select';\nimport { Switch } from '../Switch';\nimport { ToggleBar } from '../ToggleBar';\nimport { Icon } from '../../icons/Icon';\nimport { ICON_PATHS, type IconName } from '../../icons/paths';\nimport {\n MIXED,\n aggregateValue,\n classifyKind,\n effectiveSections,\n kindBreakdown,\n setAtPath,\n type AnyNode,\n type PanelLeaf,\n} from './model';\nimport s from './SelectionPanel.module.css';\n\n/** What a {@link PropertyRenderer} is given for the property it is rendering. */\nexport interface PropertyRenderContext {\n /** Dotted node path of the leaf (`pose.x`, `data.fill`). */\n path: string;\n /** The schema leaf. App renderers narrow it to their own kind shape. */\n pref: ToolPrefLeaf;\n /** Aggregated value across the selection; `undefined` when mixed or unset. */\n value: unknown;\n /** True when selected nodes disagree at this path. */\n mixed: boolean;\n /**\n * True when the nodes agree and what they agree on is nothing — the field\n * is absent, and whatever paints comes from a fallback further down.\n *\n * Distinct from `mixed`, and both leave `value` undefined. A control must\n * not substitute its schema default here: doing so shows a value the node\n * does not hold, and the next edit writes that invention back.\n */\n unset: boolean;\n /**\n * The fields of the object this leaf is a field of — `undefined` for a\n * top-level leaf, and for a field of an object the node does not hold.\n *\n * What a control needs when its own field doesn't carry the whole answer:\n * a stroke's dash lengths are multiples of the stroke's width, so reading\n * the array as a style takes both.\n */\n siblings?: Record<string, unknown>;\n /** Commit a value — fans out to every selected node in one undo step. */\n setValue: (value: unknown) => void;\n /**\n * The aggregated value at another node path — what `value` and `mixed` are\n * for this leaf's own path, for any path.\n *\n * A control whose subject spans more than one field needs it: a font picker\n * reporting which variant will actually paint has to read the node's weight\n * and style. Reading it off `value` is impossible — a leaf is handed one\n * field.\n */\n valueAt: (path: string) => { value: unknown; mixed: boolean };\n}\n\n/**\n * Renders the control for one property across the whole selection.\n * Returning `null` collapses the leaf.\n */\nexport type PropertyRenderer = (ctx: PropertyRenderContext) => ReactNode;\n\n/** Props for {@link SelectionPanel}. */\nexport interface SelectionPanelProps<TData, TLayer extends string, TPose> {\n /** The scene handle (`useScene`). The panel subscribes itself, so it\n * re-renders on scene mutations regardless of parent renders. */\n scene: Scene<TData, TLayer, TPose>;\n /** Selection handle (`useSelection`). Only `current` is read. */\n selection: Pick<SelectionApi, 'current'>;\n /** Properties-trait entries, e.g. core's `defaultNodeProperties` /\n * `inferredNodeProperties` (+ consumer extras). Memoize or hoist. */\n properties: readonly NodePropertiesEntry[];\n /** Routing-trait classifiers used to derive each node's kind — pass\n * the same list the canvas uses. Memoize or hoist. */\n routing: readonly NodeRoutingEntry[];\n /** Control overrides / app-defined kinds (PrefsForm-style). Keys are\n * leaf paths (`data.fill`, checked first) or leaf kinds (`color`).\n * A renderer returning `null` collapses its leaf (and the row, when\n * every leaf in it collapses). */\n renderers?: Record<string, PropertyRenderer>;\n /** Kind → header label. Default: capitalized kind name. */\n kindLabel?: (kind: string) => string;\n /** Rendered when the selection is empty. */\n emptyState?: ReactNode;\n className?: string;\n}\n\nconst defaultKindLabel = (kind: string): string =>\n kind.length === 0 ? kind : kind[0].toUpperCase() + kind.slice(1);\n\n/**\n * Pre-baked selection properties panel. Shows the selected nodes' kind\n * and the properties-trait schema for that kind; multi-selections show\n * the intersection of the kinds' schemas with per-field Mixed state.\n * Edits commit as one labeled `scene.batch` fan-out per gesture.\n */\nexport function SelectionPanel<TData, TLayer extends string, TPose>(\n props: SelectionPanelProps<TData, TLayer, TPose>,\n) {\n const {\n scene,\n selection,\n properties,\n routing,\n renderers,\n kindLabel = defaultKindLabel,\n emptyState = null,\n className,\n } = props;\n\n useSyncExternalStore(scene.subscribe, scene.getVersion, scene.getVersion);\n\n const nodes = selection.current\n .map((id) => scene.get(asNodeId(id)))\n .filter((n): n is NonNullable<typeof n> => n != null) as readonly AnyNode[];\n\n const kinds = nodes.map((n) => classifyKind(n, routing));\n const selectionKey = nodes.map((n) => n.id).join(',');\n const sections = effectiveSections(kinds, properties);\n\n if (nodes.length === 0) {\n return <div className={[s.root, className].filter(Boolean).join(' ')}>{emptyState}</div>;\n }\n\n const commit = (leaf: PanelLeaf, value: unknown): void => {\n const [head, ...rest] = leaf.path.split('.');\n if (rest.length === 0) return;\n const ids = selection.current.map(asNodeId);\n scene.batch(`Edit ${leaf.leaf.name}`, () => {\n for (const id of ids) {\n const node = scene.get(id);\n if (!node) continue;\n // `setAtPath` spreads plain objects/arrays down the schema path;\n // consumer `pose`/`data` are assumed plain-object-shaped along\n // that path (a class instance's prototype would be dropped by\n // the spread) — the `as TPose`/`as TData` casts below rely on it.\n if (head === 'pose') {\n scene.setPose(id, setAtPath(node.pose as object, rest, value) as TPose);\n } else if (head === 'data') {\n scene.update(id, { data: setAtPath(node.data as object, rest, value) as TData });\n }\n }\n });\n };\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n <header className={s.header}>\n {nodes.length === 1 ? (\n <span className={s.kind}>{kindLabel(kinds[0])}</span>\n ) : (\n <>\n <span className={s.kind}>{nodes.length} selected</span>\n <span className={s.breakdown}>{kindBreakdown(kinds)}</span>\n </>\n )}\n </header>\n {sections.map((section) => {\n // Render controls before emitting row chrome so a null-rendering\n // leaf (custom renderer opting out) collapses its cell — and the\n // whole row / section when nothing survives. PrefsForm precedent.\n const rows = section.rows\n .map((row) => {\n const controls = renderCells(\n row.leaves.map((panelLeaf) =>\n leafCell(\n panelLeaf,\n row.leaves.length > 1\n ? `${row.label} ${panelLeaf.leaf.name}`\n : panelLeaf.leaf.name,\n nodes,\n renderers,\n commit,\n selectionKey,\n ),\n ),\n renderers,\n );\n return { row, controls };\n })\n .filter(({ controls }) => controls.length > 0);\n if (rows.length === 0) return null;\n return (\n <section key={section.key} className={s.section}>\n {section.name !== '' && <h3 className={s.sectionTitle}>{section.name}</h3>}\n {rows.map(({ row, controls }) => (\n // A `block` leaf brings its own chrome — it spans the section\n // instead of sitting in a labeled row's control cell. Under a\n // headless section nothing else names it, so it heads itself;\n // an object leaf already renders its own heading.\n row.leaves.length === 1 && row.leaves[0].leaf.block ? (\n <div key={row.leaves[0].path}>\n {section.name === '' && row.leaves[0].leaf.kind !== 'object' && (\n <h4 className={s.sectionTitle} title={row.leaves[0].leaf.description}>\n {row.leaves[0].leaf.name}\n </h4>\n )}\n {controls[0].content}\n </div>\n ) : (\n <div key={row.leaves[0].path} className={s.row}>\n <span className={s.rowLabel} title={row.leaves[0].leaf.description}>\n {row.label}\n </span>\n <span className={s.rowControls}>\n {controls.map(({ key, content }) => (\n <Fragment key={key}>{content}</Fragment>\n ))}\n </span>\n </div>\n )\n ))}\n </section>\n );\n })}\n </div>\n );\n}\n\n/**\n * One leaf with its context built but nothing rendered yet.\n *\n * Deferring the render is what lets a run of sibling flags become one bar:\n * whether a leaf joins its neighbours is a fact about the run, which no leaf\n * rendering itself can see.\n */\ninterface LeafCell {\n key: string;\n leaf: ToolPrefLeaf;\n ctx: PropertyRenderContext;\n /** The accessible name this leaf's control carries — qualified by the row's\n * pair where the row holds more than one leaf. */\n ariaLabel: string;\n /** Renders the leaf on its own, chrome and all. */\n render: () => ReactNode;\n}\n\n/** A cell's content, once the run it belongs to has been decided. */\ninterface RenderedCell {\n key: string;\n /** The leaf that names the cell — the run's first, for a flag bar. */\n leaf: ToolPrefLeaf;\n block: boolean;\n content: ReactNode;\n}\n\nfunction leafCell(\n panelLeaf: PanelLeaf,\n ariaLabel: string,\n nodes: readonly AnyNode[],\n renderers: Record<string, PropertyRenderer> | undefined,\n commit: (leaf: PanelLeaf, value: unknown) => void,\n selectionKey: string,\n): LeafCell {\n const { path, leaf } = panelLeaf;\n const aggregated = aggregateValue(nodes, path);\n const mixed = aggregated === MIXED;\n const value = mixed ? undefined : aggregated;\n\n const ctx: PropertyRenderContext = {\n path,\n pref: leaf,\n value,\n mixed,\n unset: !mixed && aggregated === undefined,\n setValue: (v) => commit(panelLeaf, v),\n valueAt: (p) => {\n const at = aggregateValue(nodes, p);\n return at === MIXED ? { value: undefined, mixed: true } : { value: at, mixed: false };\n },\n };\n\n return {\n key: path,\n leaf,\n ctx,\n ariaLabel,\n render: () => {\n const custom = renderers?.[path] ?? renderers?.[leaf.kind];\n if (custom) return custom(ctx);\n return renderBuiltin(ctx, ariaLabel, renderers, selectionKey);\n },\n };\n}\n\n/**\n * True for a leaf that can join a flag bar: a paired toggle boolean the\n * consumer has not claimed with a renderer of its own.\n */\nfunction isFlagCell(cell: LeafCell, renderers?: Record<string, PropertyRenderer>): boolean {\n const { leaf } = cell;\n return (\n leaf.kind === 'boolean' &&\n (leaf as ToolPrefBoolean).control === 'toggle' &&\n leaf.pair !== undefined &&\n renderers?.[cell.ctx.path] === undefined &&\n renderers?.[leaf.kind] === undefined\n );\n}\n\n/**\n * Renders a row's cells, collapsing each run of adjacent same-`pair` toggle\n * booleans into one segmented bar — the idiom the enum toggle a row away\n * already uses, rather than one detached pill per flag.\n */\nfunction renderCells(\n cells: readonly LeafCell[],\n renderers?: Record<string, PropertyRenderer>,\n): RenderedCell[] {\n const out: RenderedCell[] = [];\n for (let i = 0; i < cells.length; ) {\n const cell = cells[i];\n if (!isFlagCell(cell, renderers)) {\n const content = cell.render();\n if (content != null) {\n out.push({ key: cell.key, leaf: cell.leaf, block: cell.leaf.block === true, content });\n }\n i += 1;\n continue;\n }\n let j = i + 1;\n while (\n j < cells.length &&\n isFlagCell(cells[j], renderers) &&\n cells[j].leaf.pair === cell.leaf.pair\n ) {\n j += 1;\n }\n const run = cells.slice(i, j);\n out.push({\n key: cell.key,\n leaf: cell.leaf,\n block: run.every((c) => c.leaf.block === true),\n content: <FlagBar run={run} ariaLabel={cell.leaf.pair as string} />,\n });\n i = j;\n }\n return out;\n}\n\n/**\n * A run of paired boolean flags as one multi-select bar — U / S / O, the way\n * every text editor draws them.\n */\nfunction FlagBar({ run, ariaLabel }: { run: readonly LeafCell[]; ariaLabel: string }): ReactNode {\n const on = run.filter((c) => c.ctx.value === true).map((c) => c.key);\n return (\n <ToggleBar<string>\n mode=\"multiple\"\n size=\"sm\"\n variant=\"flat\"\n className={s.flagToggle}\n ariaLabel={ariaLabel}\n items={run.map((c) => {\n const p = c.leaf as ToolPrefBoolean;\n return {\n value: c.key,\n label:\n p.icon && p.icon in ICON_PATHS ? (\n <Icon name={p.icon as IconName} size={14} />\n ) : (\n (p.short ?? p.name.slice(0, 1))\n ),\n ariaLabel: c.ariaLabel,\n };\n })}\n value={on}\n // Each flag owns its own path, so only the segment that moved is\n // written — committing the run would write two fields nobody touched,\n // and inside an object leaf would fabricate values for the other two.\n mixedValues={run.filter((c) => c.ctx.mixed).map((c) => c.key)}\n onChange={(next) => {\n for (const c of run) {\n const now = next.includes(c.key);\n if (now !== on.includes(c.key)) c.ctx.setValue(now);\n }\n }}\n />\n );\n}\n\nfunction renderBuiltin(\n ctx: PropertyRenderContext,\n ariaLabel: string,\n renderers?: Record<string, PropertyRenderer>,\n /** Identifies the current selection, so a control holding per-selection\n * scratch (the paint kind memory) is remounted rather than carried over. */\n selectionKey = '',\n /** True for a field of an object leaf. Such a field writes itself into its\n * parent, so it can only hold values its parent's type permits — which is\n * what rules \"none\" out for a nested paint. */\n nested = false,\n): ReactNode {\n const { pref, value, mixed, unset, setValue } = ctx;\n if (pref.kind === 'font-family') {\n // Not a `ToolPref` kind: its options are the live font registry, which no\n // static schema can carry. Core's own text schema still declares it, so\n // the panel ships the control rather than leaving every consumer to.\n //\n // The substitution probe runs at the node's own weight and style, so the\n // label names the variant that will actually paint. A mixed selection has\n // no single one; the probe falls back to 400/normal there.\n const weight = ctx.valueAt('data.style.fontWeight');\n const style = ctx.valueAt('data.style.fontStyle');\n return (\n <FontFamilySelect\n className={s.select}\n value={mixed || typeof value !== 'string' ? undefined : value}\n mixed={mixed}\n onChange={setValue}\n weight={typeof weight.value === 'number' ? weight.value : undefined}\n fontStyle={style.value === 'italic' ? 'italic' : undefined}\n aria-label={ariaLabel}\n />\n );\n }\n if (!isBuiltinToolPref(pref)) {\n return <span className={s.unrenderable}>({pref.kind}: no renderer)</span>;\n }\n switch (pref.kind) {\n case 'number': {\n const p = pref as ToolPrefNumber;\n const stored = typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n const display = stored !== undefined ? (p.unit ? p.unit.toDisplay(stored) : stored) : NaN;\n // `min`/`max`/`step` are declared in the stored unit, like the value, so\n // they convert with it — a leaf storing radians and showing degrees was\n // clamping typed degrees against 0..6.28. Only declared bounds convert:\n // the slider's 0..100 fallback is a display-space number with no stored\n // counterpart to put through the conversion.\n const bounds = prefDisplayBounds(p);\n if (p.control === 'slider') {\n const min = bounds.min ?? 0;\n const max = bounds.max ?? 100;\n const known = !mixed && stored !== undefined;\n return (\n <>\n <InlineRange\n min={min}\n max={max}\n step={bounds.step}\n // The thumb clamps to the track; the readout beside it does not,\n // so a value past `max` is still reported as what it is.\n value={known ? Math.min(Math.max(display, min), max) : min}\n disabled={mixed}\n className={s.slider}\n aria-label={ariaLabel}\n onChange={(e) => {\n const next = Number(e.target.value);\n setValue(p.unit ? p.unit.fromDisplay(next) : next);\n }}\n />\n <span className={s.sliderReadout} aria-hidden=\"true\">\n {known ? `${display}${p.unit?.suffix ?? ''}` : '—'}\n </span>\n </>\n );\n }\n const unit = p.unit;\n const field = unit ? (\n <UnitField\n className={s.number}\n value={mixed || stored === undefined ? NaN : display}\n placeholder={mixed ? 'Mixed' : undefined}\n minValue={bounds.min}\n maxValue={bounds.max}\n step={bounds.step}\n accepts={prefUnitAccepts(unit)}\n aria-label={ariaLabel}\n onChange={(n) => setValue(unit.fromDisplay(n))}\n />\n ) : (\n <NumberField\n className={s.number}\n value={mixed || stored === undefined ? NaN : display}\n placeholder={mixed ? 'Mixed' : undefined}\n minValue={bounds.min}\n maxValue={bounds.max}\n step={bounds.step}\n hideSteppers\n aria-label={ariaLabel}\n onChange={(n) => {\n if (Number.isNaN(n)) return;\n setValue(n);\n }}\n />\n );\n if (unit?.suffix === undefined) return field;\n return (\n <>\n {field}\n <span className={s.unitSuffix} aria-hidden=\"true\">\n {unit.suffix}\n </span>\n </>\n );\n }\n case 'string': {\n return (\n <DraftInput\n text={mixed ? undefined : typeof value === 'string' ? value : ''}\n placeholder={mixed ? 'Mixed' : undefined}\n ariaLabel={ariaLabel}\n onCommit={setValue}\n />\n );\n }\n case 'boolean': {\n const p = pref as ToolPrefBoolean;\n if (p.control === 'toggle') {\n // One segment, so the bar is the flag: `short` (else the initial of\n // `name`) is all a paired row has room for, and `name` stays the\n // accessible name.\n //\n // No dimming wrapper here. Unselected already *is* how a toggle button\n // says \"not set\", and dimming it reads as disabled; mixed has an ARIA\n // form on a toggle — `mixedValues` — which the `Switch` below has to\n // fake for want of one.\n return (\n <ToggleBar<string>\n mode=\"multiple\"\n size=\"sm\"\n variant=\"flat\"\n className={s.flagToggle}\n ariaLabel={ariaLabel}\n items={[{\n value: ctx.path,\n label:\n p.icon && p.icon in ICON_PATHS ? (\n <Icon name={p.icon as IconName} size={14} />\n ) : (\n (p.short ?? p.name.slice(0, 1))\n ),\n ariaLabel,\n }]}\n value={value === true ? [ctx.path] : []}\n mixedValues={mixed ? [ctx.path] : []}\n onChange={(next) => setValue(next.includes(ctx.path))}\n />\n );\n }\n const control = <Switch isSelected={Boolean(value)} onChange={setValue} aria-label={ariaLabel} />;\n // Neither `Switch`'s indeterminate gap nor \"unset\" has an ARIA form, so\n // a reduced-opacity wrapper with a title is the cue for both — a mixed\n // selection, and a field the node leaves to its fallback.\n if (!mixed && !unset) return control;\n return (\n <span className={s.mixedSwitch} title={mixed ? 'Mixed' : 'Not set'}>\n {control}\n </span>\n );\n }\n case 'enum': {\n const p = pref as ToolPrefEnum;\n // An encoded leaf stores something other than the option string, so the\n // option comes from the encoding rather than from the value — and an\n // absent field is one of the things it reads (no dash is `solid`), which\n // is why `unset` doesn't blank the control here.\n const option = p.encoding\n ? (mixed ? undefined : p.encoding.read(value, ctx.siblings))\n : mixed || unset\n ? undefined\n : typeof value === 'string'\n ? value\n : p.default;\n const choose = (next: string): void =>\n setValue(p.encoding ? p.encoding.write(next, ctx.siblings) : next);\n if (p.control === 'toggle') {\n // Every option visible at once, which is the point of a segmented\n // control: a glyph, else `short`, keeps it to the width a property row\n // has, and the full label stays the accessible name. A mixed selection\n // selects nothing rather than picking a winner.\n return (\n <ToggleBar<string>\n size=\"sm\"\n variant=\"flat\"\n className={s.toggle}\n ariaLabel={ariaLabel}\n items={p.options.map((o) => ({\n value: o.value,\n label:\n o.icon && o.icon in ICON_PATHS ? (\n <Icon name={o.icon as IconName} size={14} />\n ) : (\n (o.short ?? o.label)\n ),\n ariaLabel: o.label,\n disabled: o.disabled,\n }))}\n value={option ?? null}\n onChange={(next) => { if (next !== null) choose(next); }}\n />\n );\n }\n return (\n <Select<string>\n className={s.select}\n options={p.options.map((o) => ({ value: o.value, label: o.label, isDisabled: o.disabled }))}\n selectedKey={option ?? null}\n placeholder={mixed ? 'Mixed' : unset ? '—' : undefined}\n onSelectionChange={choose}\n aria-label={ariaLabel}\n />\n );\n }\n case 'color': {\n const p = pref as ToolPrefColor;\n return (\n <ColorField\n value={mixed ? undefined : typeof value === 'string' ? value : p.default}\n mixed={mixed}\n alpha={p.alpha}\n onChange={setValue}\n aria-label={ariaLabel}\n />\n );\n }\n case 'paint': {\n // The value is a whole `FillStyle`, and `PaintInput` edits it as one —\n // previewing a gradient as a gradient rather than degrading it to the\n // indeterminate chip. That leaves the checkerboard meaning `mixed` and\n // nothing else.\n const p = pref as ToolPrefPaint;\n // `??` would read an explicit `null` — \"no paint\" — as absent and show\n // the schema default over it, so the None segment could never stay lit.\n // Unset shows the fallback that is actually on the canvas, dimmed: true,\n // but not chosen, which is what the dimming says.\n const paint = (value === null\n ? null\n : (value ?? (mixed ? undefined : p.default))) as FillStyle | null | undefined;\n return (\n <PaintInput\n // Per-kind switch memory is scratch for one selection; carrying it\n // across would recall the previous node's gradient.\n key={selectionKey}\n value={paint}\n mixed={mixed}\n unset={unset}\n // A nested paint writes one field of its parent, and no kit paint\n // field is optional — `Stroke.paint` is required. Removing the\n // whole parent is a different edit than repainting it, so the\n // control must not offer one as the other. A consumer whose\n // renderer owns the parent key (WeaselDraw's `setStroke`) can.\n allowNone={!nested}\n onChange={setValue}\n aria-label={ariaLabel}\n />\n );\n }\n case 'object': {\n // One value with fields hanging off it. Each child writes the parent\n // whole, so a field is never set on a half-built object.\n return <ObjectLeaf ctx={ctx} renderers={renderers} selectionKey={selectionKey} />;\n }\n default: {\n // Not reachable while every built-in kind has an arm — and a new kind\n // that lacks one is a compile error here, never a blank cell.\n const _exhaustive: never = pref;\n throw new Error(\n `SelectionPanel: no control for built-in pref kind \"${(_exhaustive as { kind: string }).kind}\"`,\n );\n }\n }\n}\n\n/**\n * Renders an object leaf: a titled block whose rows are the object's own\n * fields. A child's edit commits the parent object, never the child's path —\n * writing into a path whose value is not an object yet would corrupt it.\n */\n/** One labeled row inside an object leaf; `pair` merges adjacent fields into\n * it, exactly as the section rows merge theirs. */\ninterface ObjectRow {\n key: string;\n pair?: string;\n label: string;\n title?: string;\n /** Every field in the row brought its own chrome, so the row drops the\n * label column and spans the block. One unlabeled field in an otherwise\n * labeled pair would leave the row named after half of itself. */\n block: boolean;\n controls: ReactNode[];\n}\n\n/**\n * Renders an object leaf: the object's own fields, as rows. A child's edit\n * commits the parent object, never the child's path — writing into a path\n * whose value is not an object yet would corrupt it.\n */\nfunction ObjectLeaf({\n ctx,\n renderers,\n selectionKey = '',\n}: {\n ctx: PropertyRenderContext;\n renderers?: Record<string, PropertyRenderer>;\n selectionKey?: string;\n}): ReactNode {\n const pref = ctx.pref as ToolPrefObject;\n const held = typeof ctx.value === 'object' && ctx.value !== null\n ? (ctx.value as Record<string, unknown>)\n : undefined;\n\n // A value whose fields are entirely grouped is titled by those groups — its\n // own heading would stack onto the first one and name nothing new.\n const allGrouped = Object.values(pref.children).every((child) => !('kind' in child));\n\n // `indent` is false when nothing visible sits above these rows: depth is\n // drawn only where a label marks it.\n const rowsOf = (\n children: Record<string, ToolPrefLeaf | ToolPrefGroup>,\n indent: boolean,\n ): ReactNode[] => {\n const out: (ReactNode | ObjectRow)[] = [];\n const isRow = (v: ReactNode | ObjectRow): v is ObjectRow =>\n typeof v === 'object' && v !== null && 'controls' in v;\n\n // Cells accumulate unrendered so a run of adjacent same-`pair` flags can\n // be seen as a run before any of them draws; a group heading between two\n // fields ends the run, exactly as it ends a paired row.\n let pending: LeafCell[] = [];\n const flush = (): void => {\n for (const { key, leaf, block, content } of renderCells(pending, renderers)) {\n const prev = out[out.length - 1];\n if (leaf.pair !== undefined && isRow(prev) && prev.pair === leaf.pair) {\n prev.controls.push(<Fragment key={key}>{content}</Fragment>);\n prev.block &&= block;\n continue;\n }\n out.push({\n key,\n pair: leaf.pair,\n label: leaf.pair ?? leaf.name,\n title: leaf.description,\n block,\n controls: [<Fragment key={key}>{content}</Fragment>],\n });\n }\n pending = [];\n };\n\n for (const [key, child] of Object.entries(children)) {\n // A group among the children organises the fields under a heading\n // without contributing to the path — the rule group keys follow at the\n // top level.\n if (!('kind' in child)) {\n const labeled = child.name !== '';\n const inner = rowsOf(child.children, labeled);\n if (inner.length === 0) continue;\n flush();\n out.push(\n <div key={`group:${key}`} className={labeled && indent ? s.objectGroup : undefined}>\n {labeled && <h5 className={s.sectionTitle}>{child.name}</h5>}\n {inner}\n </div>,\n );\n continue;\n }\n const childPath = `${ctx.path}.${key}`;\n const childCtx: PropertyRenderContext = {\n path: childPath,\n pref: child,\n value: held?.[key],\n mixed: ctx.mixed,\n // A field of an object the node does not hold is unset, and so is one\n // the object omits — `data.stroke` absent leaves every stroke field\n // with nothing behind it, not with the schema's defaults.\n unset: !ctx.mixed && held?.[key] === undefined,\n siblings: held,\n valueAt: ctx.valueAt,\n setValue: (v) => {\n // The node holds no object yet, so writing one field has to\n // materialize the rest: the leaf's `default` is what a complete\n // value looks like. Starting from `{}` instead committed the one\n // field on its own — a `data.stroke` of `{ width: 2 }` with no\n // `paint`, which the type forbids and the painter threw on, taking\n // the whole frame and the visible document with it.\n const base = held\n ?? pref.fromScalar?.(ctx.value)\n ?? (typeof pref.default === 'object' && pref.default !== null\n ? { ...(pref.default as Record<string, unknown>) }\n : {});\n if (v === undefined) {\n // A field written as absent is absent — leaving the key holding\n // `undefined` says the object has a dash of nothing.\n const { [key]: _dropped, ...rest } = base;\n ctx.setValue(rest);\n return;\n }\n ctx.setValue({ ...base, [key]: v });\n },\n };\n pending.push({\n key: childPath,\n leaf: child,\n ctx: childCtx,\n ariaLabel: child.name,\n render: () => {\n const custom = renderers?.[childPath] ?? renderers?.[child.kind];\n const rendered = custom\n ? custom(childCtx)\n : renderBuiltin(childCtx, child.name, renderers, selectionKey, true);\n if (rendered == null) return null;\n // Neither a paired row nor a block one gives a field its own label\n // column, so the glyph is all that names it on screen. The control\n // already carries `name` as its accessible name, which leaves the\n // glyph decorative.\n const unlabeled = child.pair !== undefined || child.block === true;\n const glyph = unlabeled && child.icon && child.icon in ICON_PATHS;\n if (!glyph) return rendered;\n return (\n <span className={s.namedField}>\n <Icon name={child.icon as IconName} size={14} />\n {rendered}\n </span>\n );\n },\n });\n }\n flush();\n return out.map((entry) =>\n typeof entry === 'object' && entry !== null && 'controls' in entry ? (\n entry.block ? (\n <div key={entry.key} className={`${s.rowControls} ${s.blockRow}`} title={entry.title}>\n {entry.controls}\n </div>\n ) : (\n <div key={entry.key} className={s.row}>\n <span className={s.rowLabel} title={entry.title}>{entry.label}</span>\n <span className={s.rowControls}>{entry.controls}</span>\n </div>\n )\n ) : (\n entry\n ),\n );\n };\n\n const rows = rowsOf(pref.children, !allGrouped);\n if (rows.length === 0) return null;\n return (\n <div className={s.objectLeaf}>\n {!allGrouped && <h4 className={s.sectionTitle}>{pref.name}</h4>}\n {rows}\n </div>\n );\n}\n\n/** Text input with commit-on-blur/Enter semantics — live-per-keystroke\n * writes would emit one undo step per character. */\nfunction DraftInput({\n text,\n placeholder,\n ariaLabel,\n onCommit,\n}: {\n text: string | undefined;\n placeholder?: string;\n ariaLabel: string;\n onCommit: (v: string) => void;\n}) {\n const [draft, setDraft] = useState<string | null>(null);\n return (\n <Input\n value={draft ?? text ?? ''}\n placeholder={placeholder}\n aria-label={ariaLabel}\n onChange={setDraft}\n onBlur={() => {\n if (draft !== null && draft !== text) onCommit(draft);\n setDraft(null);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter') (e.target as HTMLInputElement).blur();\n }}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar-BlUjn8lD.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\n/** Props for {@link Sidebar}. */\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":";;;;;;;;ACiCA,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-BqHfz6VK.js","names":[],"sources":["../../src/components/SidebarPanel/SidebarPanel.module.css","../../src/components/SidebarPanel/SidebarPanel.tsx"],"sourcesContent":[".panel {\n /* Vertical only: the body spans the panel's full width, and the title row's\n own controls carry the horizontal inset. */\n padding: 8px 0;\n background: var(--wzl-surface);\n color: var(--wzl-fg);\n}\n\n.title {\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-bold);\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 flex: none;\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 8px;\n font-size: var(--wzl-font-size);\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;\n}\n","import type { ReactNode } from 'react';\nimport { Focusable } from 'react-aria-components';\nimport { ChevronIcon } from '../../icons';\nimport { Tooltip, TooltipTrigger } from '../Tooltip';\nimport s from './SidebarPanel.module.css';\n\n/** Props for {@link SidebarPanel}. */\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 <ChevronIcon\n size={16}\n className={[s.chevron, collapsed && s.chevronCollapsed].filter(Boolean).join(' ')}\n />\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":";;;;;;;;;;;;;;;;;ACwCA,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,GAAD;KACE,MAAM;KACN,WAAW,CAAC,EAAE,SAAS,KAAa,EAAE,iBAAiB,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;KACjF,CAAA,EACF,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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Slider-B2ywUZH6.js","names":[],"sources":["../../src/components/Slider/Slider.module.css","../../src/components/Slider/Slider.tsx"],"sourcesContent":[".root {\n position: relative;\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n}\n\n.row {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.track {\n position: relative;\n flex: 1;\n min-width: 0;\n height: var(--rp-track-height, 24px);\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n cursor: var(--rp-track-cursor, crosshair);\n /* No `overflow: hidden` here — thumbs at extreme values would get\n * clipped at the track edges. The gradient/track-paint clip is\n * applied to `.trackInner` instead, which lets thumbs spill freely. */\n}\n\n.trackInner {\n position: absolute;\n inset: 0;\n border-radius: var(--wzl-radius-sm);\n overflow: hidden;\n}\n\n.thumb {\n position: absolute;\n /* Thumb extends 2px above + below the track for grabbability + visual\n * weight. Track itself stays its natural height; the thumb just spills. */\n top: -2px;\n bottom: -2px;\n width: var(--rp-thumb-size, 14px);\n margin-left: calc(var(--rp-thumb-size, 14px) / -2);\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--wzl-fg-muted) 70%, transparent);\n border: 1px solid var(--wzl-border-strong);\n border-radius: var(--wzl-radius-sm);\n cursor: ew-resize;\n /* Frosted-glass over the (often colorful) track. Cheap on modern GPUs;\n * gracefully degrades to just the partial-alpha background on engines\n * without backdrop-filter support. */\n backdrop-filter: blur(3px);\n -webkit-backdrop-filter: blur(3px);\n /* Subtle raise — sells the \"thumb is on top of the track\" hierarchy. */\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n font: var(--wzl-font-weight-medium) var(--wzl-font-size-xs)/var(--wzl-leading-tight) ui-sans-serif, system-ui, sans-serif;\n font-variant-numeric: tabular-nums;\n color: var(--wzl-fg-inverse);\n /* White halo so labels stay legible over wildly varying gradient\n * backgrounds (e.g. labels 'T' / 'P' / 'B' over teal, hue 200° over\n * yellow-green, etc.). */\n text-shadow: 0 0 2px rgba(255, 255, 255, 0.7);\n}\n\n/* A slim slider's thumb is round and flush with the track, matching the\n property rows. The default thumb keeps its 2px spill and rounded-rect face. */\n.slim .thumb {\n top: 50%;\n bottom: auto;\n height: var(--rp-thumb-size);\n transform: translateY(-50%);\n border-radius: 50%;\n border: none;\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n box-shadow: none;\n background: color-mix(in srgb, var(--wzl-accent) var(--wzl-slider-thumb-mix, 70%), transparent);\n}\n\n.thumb:focus-visible {\n outline: 2px solid var(--wzl-accent);\n outline-offset: 1px;\n}\n\n.thumbActive {\n /* Visual marker for the dragging/focused thumb; subclass-overridable. */\n z-index: 1;\n}\n\n/* The row holds absolutely-positioned labels, so it has to state its own\n height: the text's, not the thumb's. It said 14px — the default thumb's\n size — which left 4px of dead space under the labels at either density. */\n.readoutsBelow {\n position: relative;\n height: calc(var(--wzl-font-size-xs) * var(--wzl-leading-tight));\n margin-top: 4px;\n}\n\n.readoutBelow {\n position: absolute;\n transform: translateX(-50%);\n font: var(--wzl-font-weight-medium) var(--wzl-font-size-xs)/var(--wzl-leading-tight) ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color: var(--wzl-fg-muted);\n white-space: nowrap;\n}\n\n.readoutInline {\n display: inline-block;\n margin-left: 8px;\n font: var(--wzl-font-weight-medium) var(--wzl-font-size-sm)/var(--wzl-leading-tight) ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color: var(--wzl-fg-muted);\n vertical-align: middle;\n}\n\n.notched {\n background: var(--thumb-svg, none);\n background-size: 100% 100%;\n background-repeat: no-repeat;\n background-color: transparent;\n border: none;\n /* Notched thumbs need to disable the .thumb defaults that paint to\n * the bounding box: backdrop-filter and box-shadow both ignore the\n * SVG's polygon/notch and would render through the notch cutout.\n * Use filter: drop-shadow instead — it traces the actual rendered\n * alpha (i.e. the polygon shape including the cut). */\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n box-shadow: none;\n filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25));\n /* Default notched SVG (down-pointing pentagon, matches the perceptual-color experiment). */\n --thumb-svg: url(\"data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 30' preserveAspectRatio='none'%3E%3Cpolygon points='0,0 2.52,0 7,10.3 11.48,0 14,0 14,30 0,30' fill='rgba(255,255,255,0.62)' stroke='rgba(0,0,0,0.55)' stroke-width='0.75' stroke-linejoin='miter'/%3E%3C/svg%3E\");\n}\n\n.ticks {\n position: absolute;\n inset: 0;\n /* A mark must never eat the track click that adds a thumb, nor the\n * pointerdown that starts a drag through it. */\n pointer-events: none;\n}\n\n.tick {\n position: absolute;\n top: var(--rp-tick-inset, 20%);\n bottom: var(--rp-tick-inset, 20%);\n width: var(--rp-tick-width, 1px);\n margin-left: calc(var(--rp-tick-width, 1px) / -2);\n border-radius: var(--wzl-radius-pill);\n background: var(--rp-tick-color, var(--wzl-border-strong));\n}\n\n/* The labels hang below the track out of flow, so the row reserves their\n * height and anything after it — the below-thumb readouts — clears them.\n * Padding, not margin: a margin would collapse into whatever follows. */\n.withStopLabels .row {\n padding-bottom: calc(var(--wzl-space-xs) + var(--wzl-font-size-xs) * var(--wzl-leading-tight));\n}\n\n.stopLabels {\n position: absolute;\n left: 0;\n right: 0;\n top: calc(100% + var(--wzl-space-xs));\n height: calc(var(--wzl-font-size-xs) * var(--wzl-leading-tight));\n pointer-events: none;\n}\n\n.stopLabel {\n position: absolute;\n transform: translateX(-50%);\n white-space: nowrap;\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size-xs);\n line-height: var(--wzl-leading-tight);\n font-variant-numeric: tabular-nums;\n color: var(--wzl-fg-subtle);\n}\n\n/* The end labels are pinned inside the track rather than centered on it, so a\n * wide one can't spill past the control and misalign whatever sits beside it. */\n.stopLabel[data-edge=\"start\"] {\n transform: none;\n}\n\n.stopLabel[data-edge=\"end\"] {\n transform: translateX(-100%);\n}\n\n.stopLabel[data-selected] {\n color: var(--wzl-fg);\n font-weight: var(--wzl-font-weight-medium);\n}\n","import { useCallback, useEffect, useMemo, useRef, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactElement, type ReactNode } from 'react';\nimport { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport s from './Slider.module.css';\nimport { formatNumber } from '../../format/number';\n\n/**\n * Passed to a custom thumb renderer: the thumb box in CSS px, and whether\n * this thumb is the one being dragged.\n */\nexport type ThumbRenderCtx = {\n width: number;\n height: number;\n isActive: boolean;\n};\n\n/**\n * A thumb's appearance — one of the two built-in shapes, or a custom\n * renderer.\n */\nexport type ThumbShape =\n | 'round'\n | 'notched'\n | { render: (ctx: ThumbRenderCtx) => ReactNode };\n\n/**\n * One handle on a {@link Slider}. `bounds` narrows the range this particular\n * thumb may move within, either fixed or computed from the current thumb\n * list.\n */\nexport type Thumb = {\n value: number;\n label?: string;\n shape?: ThumbShape;\n bounds?: [number, number] | ((ctx: BoundsCtx) => [number, number]);\n /**\n * Spoken form of `value`, published as `aria-valuetext`. Required by ARIA\n * whenever `value` is not the quantity the user is choosing — an index into\n * a value list, a log-scaled position — since `aria-valuenow` alone then\n * announces a number that means nothing.\n */\n valueText?: string;\n};\n\n/**\n * Passed to a thumb's `bounds` function: the full thumb list and this thumb's\n * index in it, so a bound can be expressed relative to its neighbors.\n */\nexport type BoundsCtx = {\n thumbs: readonly Thumb[];\n index: number;\n};\n\n/**\n * Passed to `renderTrack`: the track's width in CSS px and a mapping from a\n * slider value to its 0..1 position along the track.\n */\nexport type TrackCtx = {\n trackWidth: number;\n valueToFraction: (v: number) => number;\n};\n\n/** A {@link Slider} stop that carries a label, drawn under the track at the stop. */\nexport type SliderStop = {\n value: number;\n label?: ReactNode;\n};\n\n/**\n * Props for {@link Slider}.\n *\n * `onInput` fires continuously through a drag; `onChange` fires once when it\n * ends and is the one to write to history.\n *\n * `stops` are attractors: a drag that passes within a few pixels of one lands\n * on it, and the arrow keys move stop to stop. `step` still quantizes the\n * values between them. Each one is drawn on the track as a mark; pass\n * `showStops: false` for a track whose own paint already reads as the stops.\n * A stop given as a {@link SliderStop} with a `label` gets that label drawn\n * under it; `stopLabels` narrows the row to the two ends or turns it off.\n *\n * `snap: 'strict'` makes every stop a detent the thumb must rest on: drags and\n * track presses land on the nearest one, and `step` no longer applies.\n * `spacing: 'even'` places the stops at equal intervals, runs the track from\n * the first stop to the last, and maps values linearly within each gap — so a\n * geometric list (0.25/0.5/1/2/4) gets usable room for every stop instead of\n * four crowded into the first fifth. Both need stops; with fewer than two,\n * `'even'` falls back to linear.\n *\n * `trackClick: 'move-nearest'` makes a press on bare track send the closest\n * thumb there and continue as a drag. It is off by default because on a\n * multi-thumb editor a stray click would yank a stop the user was not aiming\n * at; `onAddThumb`, where it is set, keeps the track press.\n *\n * `constraint: 'ordered'` keeps thumbs from crossing each other. Supplying\n * `onAddThumb` makes a click on empty track create a thumb, and supplying\n * `onRemoveThumb` lets a right-click or a drag off the track remove one —\n * both callbacks can decline by returning `null`/`false`. `allowShiftAll`\n * makes shift-drag translate every thumb together.\n */\nexport type SliderProps<T extends Thumb = Thumb> = {\n thumbs: readonly T[];\n onInput: (next: T[]) => void;\n onChange?: (next: T[]) => void;\n min: number;\n max: number;\n step?: number;\n stops?: readonly (number | SliderStop)[];\n showStops?: boolean;\n stopLabels?: 'all' | 'ends' | 'none';\n snap?: 'magnetic' | 'strict';\n spacing?: 'linear' | 'even';\n trackClick?: 'none' | 'move-nearest';\n constraint?: 'free' | 'ordered';\n onAddThumb?: (atValue: number) => T | null;\n onRemoveThumb?: (index: number) => boolean;\n allowShiftAll?: boolean;\n renderTrack?: (ctx: TrackCtx) => ReactNode;\n trackHeight?: number;\n /** `'slim'` drives the track and thumb from the kit's slider tokens, so a\n * Slider matches the property rows. `trackHeight` still wins if given. */\n density?: 'default' | 'slim';\n renderReadout?: (thumb: T, index: number) => ReactNode;\n readoutPlacement?: 'none' | 'inline-after' | 'below-thumb';\n ariaLabel?: string;\n className?: string;\n};\n\nfunction quantize(v: number, step: number | undefined, min: number): number {\n if (step === undefined || step <= 0) return v;\n return Math.round((v - min) / step) * step + min;\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v));\n}\n\n/** How close a drag has to come to a stop, in track pixels, to land on it. */\nconst STOP_SNAP_PX = 8;\n\n/** A custom `ThumbShape.render` may put interactive content inside the thumb,\n * and capture would retarget pointerup and kill the click on it. */\nconst NO_CAPTURE = { capture: false } as const;\n\n/** The stops that are actually reachable: inside the range, deduped, ascending.\n * A duplicate keeps the first label given for its value. */\nfunction usableStops(stops: SliderProps['stops'], min: number, max: number): SliderStop[] {\n if (!stops || stops.length === 0) return [];\n const byValue = new Map<number, SliderStop>();\n for (const stop of stops) {\n const s = typeof stop === 'number' ? { value: stop } : stop;\n if (s.value < min || s.value > max) continue;\n const seen = byValue.get(s.value);\n if (!seen || (seen.label === undefined && s.label !== undefined)) byValue.set(s.value, s);\n }\n return [...byValue.values()].sort((a, b) => a.value - b.value);\n}\n\n/** The stop nearest `v` along the track, or `v` itself when none lies within\n * `tolerance` (a track fraction). Measured on the track rather than in value\n * units so evenly spaced stops attract over the same distance. */\nfunction attract(v: number, stops: number[], tolerance: number, toFraction: (v: number) => number): number {\n const f = toFraction(v);\n let best = v;\n let bestGap = tolerance;\n for (const stop of stops) {\n const gap = Math.abs(toFraction(stop) - f);\n if (gap <= bestGap) {\n best = stop;\n bestGap = gap;\n }\n }\n return best;\n}\n\n/** The stop `count` places past `v` in `direction`, saturating at either end. */\nfunction stepStops(stops: number[], v: number, direction: 1 | -1, count: number): number {\n if (direction > 0) {\n const first = stops.findIndex(stop => stop > v);\n if (first === -1) return stops[stops.length - 1];\n return stops[Math.min(stops.length - 1, first + count - 1)];\n }\n let first = -1;\n for (let i = stops.length - 1; i >= 0; i--) {\n if (stops[i] < v) {\n first = i;\n break;\n }\n }\n if (first === -1) return stops[0];\n return stops[Math.max(0, first - (count - 1))];\n}\n\nfunction defaultStep(step: number | undefined, min: number, max: number): number {\n if (step !== undefined && step > 0) return step;\n return (max - min) / 100;\n}\n\n/** Keep a thumb inside its neighbors when `constraint` is `'ordered'`. */\nfunction clampOrdered(\n v: number,\n thumbs: readonly Thumb[],\n index: number,\n min: number,\n max: number,\n step: number | undefined,\n): number {\n const gap = step !== undefined && step > 0 ? step : (max - min) / 1000;\n const lower = index > 0 ? thumbs[index - 1].value + gap : min;\n const upper = index < thumbs.length - 1 ? thumbs[index + 1].value - gap : max;\n return clamp(v, lower, upper);\n}\n\nfunction resolveBounds(thumb: Thumb, ctx: BoundsCtx, fallbackMin: number, fallbackMax: number): [number, number] {\n if (!thumb.bounds) return [fallbackMin, fallbackMax];\n const tuple = typeof thumb.bounds === 'function' ? thumb.bounds(ctx) : thumb.bounds;\n return [tuple[0], tuple[1]];\n}\n\nfunction defaultReadout(thumb: Thumb): string {\n return formatNumber(thumb.value, { minimumFractionDigits: 3, maximumFractionDigits: 3 });\n}\n\n/**\n * Multi-thumb slider over a shared track. The thumb list is fully controlled:\n * every change, live or committed, arrives as a whole new array.\n *\n * Thumbs are draggable, and arrow/Home/End move the focused thumb — those\n * keystrokes fire `onInput` and `onChange` together, since there is no\n * in-flight state to buffer.\n */\nexport function Slider<T extends Thumb = Thumb>(props: SliderProps<T>): ReactElement {\n const { thumbs, onInput, onChange, step, constraint, trackHeight, density, ariaLabel, className } = props;\n\n const stopList = useMemo(() => usableStops(props.stops, props.min, props.max), [props.stops, props.min, props.max]);\n const stops = useMemo(() => stopList.map(stop => stop.value), [stopList]);\n const even = props.spacing === 'even' && stops.length >= 2;\n const strict = props.snap === 'strict' && stops.length > 0;\n const min = even ? stops[0] : props.min;\n const max = even ? stops[stops.length - 1] : props.max;\n\n const trackRef = useRef<HTMLDivElement | null>(null);\n // In-flight thumb buffer during a drag; null when not dragging.\n const dragBufferRef = useRef<T[] | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n\n useEffect(() => () => { sessionRef.current?.cancel(); }, []);\n\n const valueToFraction = useCallback(\n (v: number): number => {\n if (max === min) return 0;\n if (!even) return clamp((v - min) / (max - min), 0, 1);\n const gaps = stops.length - 1;\n const x = clamp(v, min, max);\n let i = 0;\n while (i < gaps - 1 && x > stops[i + 1]) i++;\n return (i + (x - stops[i]) / (stops[i + 1] - stops[i])) / gaps;\n },\n [min, max, even, stops],\n );\n\n const fractionToValue = useCallback(\n (f: number): number => {\n const c = clamp(f, 0, 1);\n if (!even) return min + c * (max - min);\n const gaps = stops.length - 1;\n const i = Math.min(gaps - 1, Math.floor(c * gaps));\n return stops[i] + (c * gaps - i) * (stops[i + 1] - stops[i]);\n },\n [min, max, even, stops],\n );\n\n /** The value a pointer at track fraction `f` chooses, before bounds. */\n const valueAt = useCallback(\n (f: number, trackWidth: number): number => {\n const v = fractionToValue(f);\n if (strict) return attract(v, stops, Infinity, valueToFraction);\n const q = clamp(quantize(v, step, min), min, max);\n return stops.length > 0 ? attract(q, stops, STOP_SNAP_PX / trackWidth, valueToFraction) : q;\n },\n [fractionToValue, valueToFraction, strict, stops, step, min, max],\n );\n\n // `seed` replaces the thumb list the drag starts from. A track press moves a\n // thumb before the drag begins, and without seeding, a release with no\n // movement would commit the stale buffer and undo that move.\n const beginThumbDrag = useCallback(\n (origin: Element, down: ReactPointerEvent, index: number, seed?: readonly T[]) => {\n sessionRef.current?.cancel();\n const buf: T[] = (seed ?? thumbs).map(t => ({ ...t }));\n dragBufferRef.current = buf;\n let droppedOff = false;\n\n const onMove = (ev: PointerEvent) => {\n const track = trackRef.current;\n const buffer = dragBufferRef.current;\n if (!track || !buffer) return;\n const rect = track.getBoundingClientRect();\n let v = valueAt((ev.clientX - rect.left) / rect.width, rect.width);\n\n const [bLo, bHi] = resolveBounds(buffer[index], { thumbs: buffer, index }, min, max);\n v = clamp(v, bLo, bHi);\n\n if (constraint === 'ordered') v = clampOrdered(v, buffer, index, min, max, step);\n\n // Drop-off detection: pointer exits the track vertically by more than trackHeight.\n const bandHeight = rect.height;\n if (props.onRemoveThumb) {\n if (ev.clientY < rect.top - bandHeight || ev.clientY > rect.bottom + bandHeight) {\n droppedOff = true;\n } else {\n droppedOff = false;\n }\n }\n\n buffer[index] = { ...buffer[index], value: v };\n onInput(buffer.map(t => ({ ...t })));\n };\n\n // A drag that ends without a release — cancelled pointer, lost capture,\n // an unmount — drops the buffer without committing.\n const onCancel = () => {\n sessionRef.current = null;\n dragBufferRef.current = null;\n };\n\n const onEnd = () => {\n sessionRef.current = null;\n const buffer = dragBufferRef.current;\n dragBufferRef.current = null;\n if (!buffer) return;\n\n if (droppedOff && props.onRemoveThumb) {\n const accepted = props.onRemoveThumb(index);\n if (accepted) {\n const next = buffer.filter((_, i) => i !== index).map(t => ({ ...t })) as T[];\n onInput(next);\n onChange?.(next);\n return;\n }\n }\n\n onChange?.(buffer.map(t => ({ ...t })));\n };\n\n sessionRef.current = openPointerSession(origin, down, { onMove, onEnd, onCancel }, NO_CAPTURE);\n },\n [thumbs, onInput, onChange, valueAt, min, max, step, constraint, props],\n );\n\n const beginShiftAllDrag = useCallback(\n (origin: Element, down: ReactPointerEvent, anchorX: number) => {\n sessionRef.current?.cancel();\n const buf: T[] = thumbs.map(t => ({ ...t }));\n const startValues = buf.map(t => t.value);\n dragBufferRef.current = buf;\n\n const onMove = (ev: PointerEvent) => {\n const track = trackRef.current;\n const buffer = dragBufferRef.current;\n if (!track || !buffer) return;\n const rect = track.getBoundingClientRect();\n const dxFraction = (ev.clientX - anchorX) / rect.width;\n // Evenly spaced gaps span different amounts of value, so equal value\n // offsets would pull the thumbs apart; move them by track distance.\n if (even) {\n const starts = startValues.map(valueToFraction);\n const df = clamp(dxFraction, -Math.min(...starts), 1 - Math.max(...starts));\n for (let i = 0; i < buffer.length; i++) {\n buffer[i] = { ...buffer[i], value: fractionToValue(starts[i] + df) };\n }\n onInput(buffer.map(t => ({ ...t })));\n return;\n }\n let dValue = dxFraction * (max - min);\n dValue = quantize(dValue, step, 0);\n\n // Clamp delta so no thumb leaves [min, max] (per-thumb bounds intentionally\n // not enforced — matches the experiment's hue-band shift-translate semantics).\n let allowedNeg = -Infinity;\n let allowedPos = Infinity;\n for (let i = 0; i < startValues.length; i++) {\n allowedNeg = Math.max(allowedNeg, min - startValues[i]);\n allowedPos = Math.min(allowedPos, max - startValues[i]);\n }\n dValue = clamp(dValue, allowedNeg, allowedPos);\n\n for (let i = 0; i < buffer.length; i++) {\n buffer[i] = { ...buffer[i], value: clamp(startValues[i] + dValue, min, max) };\n }\n onInput(buffer.map(t => ({ ...t })));\n };\n\n const onCancel = () => {\n sessionRef.current = null;\n dragBufferRef.current = null;\n };\n\n const onEnd = () => {\n sessionRef.current = null;\n const buffer = dragBufferRef.current;\n dragBufferRef.current = null;\n if (buffer) onChange?.(buffer.map(t => ({ ...t })));\n };\n\n sessionRef.current = openPointerSession(origin, down, { onMove, onEnd, onCancel }, NO_CAPTURE);\n },\n [thumbs, onInput, onChange, min, max, step, even, valueToFraction, fractionToValue],\n );\n\n const onThumbPointerDown = (index: number) => (e: ReactPointerEvent) => {\n // Only bail on explicit non-primary buttons (button > 0). jsdom's PointerEvent\n // leaves `button` undefined; treat that as primary so tests can drive drags.\n if (typeof e.button === 'number' && e.button > 0) return;\n // preventDefault below suppresses the focus the press would otherwise\n // give the thumb, and the arrow keys are on the thumb.\n (e.currentTarget as HTMLElement).focus?.();\n e.preventDefault();\n e.stopPropagation();\n const origin = e.currentTarget as HTMLElement;\n if (e.shiftKey && props.allowShiftAll) {\n beginShiftAllDrag(origin, e, e.clientX);\n } else {\n beginThumbDrag(origin, e, index);\n }\n };\n\n const onThumbContextMenu = (index: number) => (e: ReactMouseEvent) => {\n if (!props.onRemoveThumb) return;\n e.preventDefault();\n const accepted = props.onRemoveThumb(index);\n if (!accepted) return;\n const next = thumbs.filter((_, i) => i !== index).map(t => ({ ...t })) as T[];\n onInput(next);\n onChange?.(next);\n };\n\n const onTrackPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {\n if (typeof e.button === 'number' && e.button > 0) return;\n const wantsMove = (props.trackClick ?? 'none') === 'move-nearest' && thumbs.length > 0;\n if (!props.onAddThumb && !wantsMove) return;\n // If the event originated on a thumb, the thumb's own handler ran first; this is a track click.\n if ((e.target as HTMLElement).closest(`.${s.thumb}`)) return;\n e.preventDefault();\n const track = trackRef.current;\n if (!track) return;\n const rect = track.getBoundingClientRect();\n const v = valueAt((e.clientX - rect.left) / rect.width, rect.width);\n\n if (props.onAddThumb) {\n const created = props.onAddThumb(v);\n if (!created) return;\n const next = [...thumbs.map(t => ({ ...t })), created] as T[];\n onInput(next);\n onChange?.(next);\n return;\n }\n\n let index = 0;\n for (let i = 1; i < thumbs.length; i++) {\n if (Math.abs(thumbs[i].value - v) < Math.abs(thumbs[index].value - v)) index = i;\n }\n const next = thumbs.map(t => ({ ...t })) as T[];\n const [bLo, bHi] = resolveBounds(next[index], { thumbs: next, index }, min, max);\n let moved = clamp(v, bLo, bHi);\n if (constraint === 'ordered') moved = clampOrdered(moved, next, index, min, max, step);\n next[index] = { ...next[index], value: moved };\n onInput(next);\n beginThumbDrag(track, e, index, next);\n };\n\n const onThumbKeyDown = (index: number) => (e: ReactKeyboardEvent) => {\n const stepSize = defaultStep(step, min, max);\n let delta = 0;\n // With stops, a keystroke moves by whole stops rather than by value.\n let stopDelta = 0;\n let snapTo: 'home' | 'end' | null = null;\n\n switch (e.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n delta = e.shiftKey ? stepSize * 10 : stepSize;\n stopDelta = e.shiftKey ? 10 : 1;\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n delta = e.shiftKey ? -stepSize * 10 : -stepSize;\n stopDelta = e.shiftKey ? -10 : -1;\n break;\n case 'PageUp':\n delta = stepSize * 10;\n stopDelta = 10;\n break;\n case 'PageDown':\n delta = -stepSize * 10;\n stopDelta = -10;\n break;\n case 'Home':\n snapTo = 'home';\n break;\n case 'End':\n snapTo = 'end';\n break;\n default:\n return;\n }\n\n e.preventDefault();\n const next = thumbs.map(t => ({ ...t }));\n const [bLo, bHi] = resolveBounds(next[index], { thumbs: next, index }, min, max);\n const lo = Math.max(min, bLo);\n const hi = Math.min(max, bHi);\n let v: number;\n if (snapTo === 'home') v = lo;\n else if (snapTo === 'end') v = hi;\n else if (stops.length > 0) {\n v = stepStops(stops, next[index].value, stopDelta > 0 ? 1 : -1, Math.abs(stopDelta));\n } else {\n v = quantize(next[index].value + delta, step, min);\n }\n v = clamp(v, lo, hi);\n if (constraint === 'ordered') v = clampOrdered(v, next, index, lo, hi, step);\n next[index] = { ...next[index], value: v };\n onInput(next);\n onChange?.(next);\n };\n\n const placement = props.readoutPlacement ?? 'none';\n const renderReadout = props.renderReadout;\n\n const slim = density === 'slim';\n const rootVars: Record<string, string> = {};\n if (slim) {\n rootVars['--rp-track-height'] = 'var(--wzl-slider-track-h)';\n rootVars['--rp-thumb-size'] = 'var(--wzl-slider-thumb-size)';\n }\n // An explicit trackHeight wins: a caller who named a number meant it.\n if (trackHeight !== undefined) rootVars['--rp-track-height'] = `${trackHeight}px`;\n\n const labelMode = props.stopLabels ?? 'all';\n const lastStop = stopList.length - 1;\n const labeledStops = labelMode === 'none'\n ? []\n : stopList\n .map((stop, i) => ({ stop, i }))\n .filter(({ stop, i }) => stop.label !== undefined && (labelMode === 'all' || i === 0 || i === lastStop));\n // Inside the track so it shares the track's width, not the row's — an inline\n // readout beside the track would otherwise shift every label off its stop.\n const stopLabelRow = labeledStops.length > 0 && (\n <div className={s.stopLabels} data-slider-stop-labels aria-hidden=\"true\">\n {labeledStops.map(({ stop, i }) => (\n <span\n key={stop.value}\n className={s.stopLabel}\n data-slider-stop-label\n data-selected={thumbs.some(t => t.value === stop.value) ? 'true' : undefined}\n data-edge={i === 0 ? 'start' : i === lastStop ? 'end' : undefined}\n style={{ left: `${valueToFraction(stop.value) * 100}%` }}\n >\n {stop.label}\n </span>\n ))}\n </div>\n );\n const rootClass = [s.root, slim && s.slim, stopLabelRow && s.withStopLabels, className].filter(Boolean).join(' ');\n\n return (\n <div\n className={rootClass}\n style={Object.keys(rootVars).length > 0 ? (rootVars as CSSProperties) : undefined}\n >\n <div className={s.row}>\n <div className={s.track} ref={trackRef} onPointerDown={onTrackPointerDown}>\n {props.renderTrack && (\n <div className={s.trackInner}>\n {props.renderTrack({\n trackWidth: trackRef.current?.getBoundingClientRect().width ?? 0,\n valueToFraction,\n })}\n </div>\n )}\n {(props.showStops ?? true) && stops.length > 0 && (\n <div className={s.ticks} data-slider-ticks aria-hidden=\"true\">\n {stops.map(v => {\n const f = valueToFraction(v);\n return (\n <span\n key={v}\n className={s.tick}\n data-slider-tick\n data-fraction={String(f)}\n style={{ left: `${f * 100}%` }}\n />\n );\n })}\n </div>\n )}\n {stopLabelRow}\n {thumbs.map((thumb, i) => {\n const isNotched = thumb.shape === 'notched';\n const customRender = typeof thumb.shape === 'object' && thumb.shape !== null ? thumb.shape.render : null;\n const cls = `${s.thumb}${isNotched ? ` ${s.notched}` : ''}`;\n return (\n <div\n key={i}\n role=\"slider\"\n tabIndex={0}\n aria-orientation=\"horizontal\"\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={thumb.value}\n aria-valuetext={thumb.valueText}\n aria-label={[ariaLabel, thumb.label].filter(Boolean).join(' ') || undefined}\n className={cls}\n style={{ left: `${valueToFraction(thumb.value) * 100}%` }}\n onPointerDown={onThumbPointerDown(i)}\n onKeyDown={onThumbKeyDown(i)}\n onContextMenu={onThumbContextMenu(i)}\n >\n {customRender ? customRender({ width: 14, height: 24, isActive: false }) : (thumb.label ?? '')}\n </div>\n );\n })}\n </div>\n {placement === 'inline-after' && (\n <span data-readout=\"inline\" className={s.readoutInline}>\n {thumbs.map((t, i) => (\n <span key={i}>{i > 0 ? ' / ' : ''}{renderReadout ? renderReadout(t, i) : defaultReadout(t)}</span>\n ))}\n </span>\n )}\n </div>\n {placement === 'below-thumb' && (\n <div className={s.readoutsBelow}>\n {thumbs.map((t, i) => (\n <span\n key={i}\n data-readout=\"below\"\n className={s.readoutBelow}\n style={{ left: `${valueToFraction(t.value) * 100}%` }}\n >\n {renderReadout ? renderReadout(t, i) : defaultReadout(t)}\n </span>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBar-BFt7Yw5O.js","names":[],"sources":["../../src/components/StatusBar/StatusBar.module.css","../../src/components/StatusBar/StatusBar.tsx"],"sourcesContent":[".bar {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n padding: 4px 12px;\n font-family: var(--wzl-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-muted);\n background: var(--wzl-surface);\n border-top: 1px solid var(--wzl-border);\n}\n\n.item {\n white-space: nowrap;\n}\n\n.muted {\n opacity: 0.6;\n font-variant-numeric: tabular-nums;\n}\n\n.hasTitle {\n cursor: help;\n}\n\n.spacer {\n margin-left: auto;\n}\n","import type { ReactNode } from 'react';\nimport s from './StatusBar.module.css';\n\n/** Props for {@link StatusBar}. */\nexport interface StatusBarProps {\n /**\n * Accessible label for the bar. Deliberately not a live region — a status\n * bar's readouts change on every pointer move (zoom, cursor position,\n * selection count), and announcing each one would make a screen reader\n * unusable. Screen-reader users reach the same facts through the controls\n * that own them.\n */\n ariaLabel?: string;\n children?: ReactNode;\n className?: string;\n}\n\n/**\n * The thin readout strip along the bottom of an editor window: monospace,\n * one row, items separated by a fixed gap, with an optional spacer that\n * pushes everything after it to the trailing edge.\n *\n * Pure presentation — it holds whatever the app puts in it. Compose with\n * `<StatusBarItem>` and `<StatusBarSpacer>`.\n */\nexport function StatusBar(props: StatusBarProps) {\n const { ariaLabel, children, className } = props;\n return (\n <footer className={[s.bar, className].filter(Boolean).join(' ')} aria-label={ariaLabel}>\n {children}\n </footer>\n );\n}\n\n/** Props for {@link StatusBarItem}. */\nexport interface StatusBarItemProps {\n /**\n * Hover text. Items that carry one also get a `help` cursor, so the\n * affordance is discoverable rather than hidden behind a hover delay.\n */\n title?: string;\n /**\n * Dim the item and use tabular figures. For reference material that\n * shouldn't compete with live readouts — build stamps, hints, units.\n */\n muted?: boolean;\n children?: ReactNode;\n className?: string;\n}\n\n/** One readout in a `<StatusBar>`. */\nexport function StatusBarItem(props: StatusBarItemProps) {\n const { title, muted, children, className } = props;\n const cls = [s.item, muted && s.muted, title !== undefined && s.hasTitle, className]\n .filter(Boolean).join(' ');\n return <span className={cls} title={title}>{children}</span>;\n}\n\n/**\n * Splits a status bar into leading and trailing runs — everything after the\n * spacer is pushed to the trailing edge. Decorative, so it's hidden from the\n * accessibility tree.\n */\nexport function StatusBarSpacer() {\n return <span className={s.spacer} aria-hidden=\"true\" />;\n}\n"],"mappings":";;;;;;;;;;ACyBA,SAAgB,EAAU,GAAuB;CAC/C,IAAM,EAAE,cAAW,aAAU,iBAAc;AAC3C,QACE,kBAAC,UAAD;EAAQ,WAAW,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EAAE,cAAY;EAC1E;EACM,CAAA;;AAqBb,SAAgB,EAAc,GAA2B;CACvD,IAAM,EAAE,UAAO,UAAO,aAAU,iBAAc;AAG9C,QAAO,kBAAC,QAAD;EAAM,WAFD;GAAC,EAAE;GAAM,KAAS,EAAE;GAAO,MAAU,KAAA,KAAa,EAAE;GAAU;GAAU,CACjF,OAAO,QAAQ,CAAC,KAAK,IACA;EAAY;EAAQ;EAAgB,CAAA;;AAQ9D,SAAgB,IAAkB;AAChC,QAAO,kBAAC,QAAD;EAAM,WAAW,EAAE;EAAQ,eAAY;EAAS,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Switch-Om3t3ASc.js","names":[],"sources":["../../src/components/Switch/Switch.module.css","../../src/components/Switch/Switch.tsx"],"sourcesContent":[".switch {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n color: var(--wzl-fg);\n user-select: none;\n -webkit-user-select: none;\n}\n\n.switch[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.track {\n position: relative;\n flex: 0 0 auto;\n width: 28px;\n height: 16px;\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border-strong);\n border-radius: var(--wzl-radius-pill);\n transition: background-color var(--wzl-motion-fast), border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.switch[data-focus-visible] .track {\n box-shadow: 0 0 0 2px var(--wzl-focus-ring);\n}\n\n.switch[data-selected] .track {\n background: var(--wzl-accent-strong);\n border-color: var(--wzl-accent-strong);\n}\n\n.thumb {\n position: absolute;\n top: 1px;\n left: 1px;\n width: 12px;\n height: 12px;\n background: var(--wzl-fg);\n border-radius: 50%;\n transition: transform var(--wzl-motion-fast);\n}\n\n.switch[data-selected] .thumb {\n transform: translateX(12px);\n background: var(--wzl-fg-on-accent);\n}\n\n.label {\n line-height: 1.2;\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n Switch as RACSwitch,\n type SwitchProps as RACSwitchProps,\n} from 'react-aria-components';\nimport s from './Switch.module.css';\n\n/**\n * Props for {@link Switch}, on top of React Aria's `Switch` props. The label\n * is passed as children.\n */\nexport type SwitchProps = Omit<RACSwitchProps, 'children' | 'className'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * On/off toggle wrapping React Aria's Switch, skinned against the `--wzl-*`\n * tokens. Use for a setting that takes effect immediately; use `Checkbox`\n * for one that is submitted with a form.\n *\n * `ref` forwards to the underlying label element.\n */\nexport const Switch = forwardRef(function Switch(\n props: SwitchProps,\n ref: Ref<HTMLLabelElement>,\n) {\n const { children, className, ...rest } = props;\n return (\n <RACSwitch\n {...rest}\n ref={ref}\n className={[s.switch, className].filter(Boolean).join(' ')}\n >\n <span className={s.track}>\n <span className={s.thumb} />\n </span>\n {children !== undefined && <span className={s.label}>{children}</span>}\n </RACSwitch>\n );\n});\n"],"mappings":";;;;;;;;GCuBa,IAAS,EAAW,SAC/B,GACA,GACA;CACA,IAAM,EAAE,aAAU,cAAW,GAAG,MAAS;AACzC,QACE,kBAAC,GAAD;EACE,GAAI;EACC;EACL,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAH5D,CAKE,kBAAC,QAAD;GAAM,WAAW,EAAE;aACjB,kBAAC,QAAD,EAAM,WAAW,EAAE,OAAS,CAAA;GACvB,CAAA,EACN,MAAa,KAAA,KAAa,kBAAC,QAAD;GAAM,WAAW,EAAE;GAAQ;GAAgB,CAAA,CAC5D;;EAEd"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Tabs-EAvYywil.js","names":[],"sources":["../../src/components/Tabs/Tabs.module.css","../../src/components/Tabs/Tabs.tsx"],"sourcesContent":[".tabs {\n display: flex;\n flex-direction: column;\n gap: 0;\n font-family: var(--wzl-font-ui);\n color: var(--wzl-fg);\n}\n\n.tabs[data-orientation=\"horizontal\"] {\n flex-direction: column;\n}\n\n.tabs[data-orientation=\"vertical\"] {\n flex-direction: row;\n gap: 12px;\n}\n\n.list {\n display: flex;\n gap: 2px;\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.tabs[data-orientation=\"vertical\"] .list {\n flex-direction: column;\n border-bottom: none;\n border-right: 1px solid var(--wzl-border);\n padding-right: 8px;\n}\n\n.tab {\n position: relative;\n padding: 6px 10px;\n background: transparent;\n border: none;\n color: var(--wzl-fg-muted);\n font: inherit;\n font-size: var(--wzl-font-size-sm);\n cursor: pointer;\n outline: none;\n transition: color var(--wzl-motion-fast);\n}\n\n.tab[data-hovered]:not([data-disabled]) {\n color: var(--wzl-fg);\n}\n\n.tab[data-selected] {\n color: var(--wzl-fg);\n}\n\n.tab[data-selected]::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n bottom: -1px;\n height: 2px;\n background: var(--wzl-accent-strong);\n}\n\n.tabs[data-orientation=\"vertical\"] .tab[data-selected]::after {\n left: auto;\n right: -9px;\n top: 0;\n bottom: 0;\n width: 2px;\n height: auto;\n}\n\n.tab[data-focus-visible] {\n box-shadow: inset 0 0 0 2px var(--wzl-focus-ring);\n border-radius: var(--wzl-radius-sm);\n}\n\n.tab[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.panel {\n padding: 12px 0;\n outline: none;\n}\n\n.panel[data-focus-visible] {\n box-shadow: inset 0 0 0 2px var(--wzl-focus-ring);\n border-radius: var(--wzl-radius-sm);\n}\n","import type { ReactNode } from 'react';\nimport {\n Tabs as RACTabs,\n TabList as RACTabList,\n Tab as RACTab,\n TabPanel as RACTabPanel,\n type TabsProps as RACTabsProps,\n type TabListProps as RACTabListProps,\n type TabProps as RACTabProps,\n type TabPanelProps as RACTabPanelProps,\n} from 'react-aria-components';\nimport s from './Tabs.module.css';\n\n/** Props for {@link Tabs}, on top of React Aria's `Tabs` props. */\nexport type TabsProps = Omit<RACTabsProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/**\n * Tab set wrapping React Aria's Tabs, skinned against the `--wzl-*` tokens.\n * Holds a {@link TabList} and one {@link TabPanel} per tab.\n */\nexport function Tabs({ children, className, ...rest }: TabsProps) {\n return (\n <RACTabs {...rest} className={[s.tabs, className].filter(Boolean).join(' ')}>\n {children}\n </RACTabs>\n );\n}\n\n/** Props for {@link TabList}, on top of React Aria's `TabList` props. */\nexport type TabListProps<T extends object> = Omit<RACTabListProps<T>, 'className'> & {\n className?: string;\n};\n\n/** The row of tab buttons inside a {@link Tabs}. */\nexport function TabList<T extends object>({ className, ...rest }: TabListProps<T>) {\n return <RACTabList<T> {...rest} className={[s.list, className].filter(Boolean).join(' ')} />;\n}\n\n/** Props for {@link Tab}, on top of React Aria's `Tab` props. */\nexport type TabProps = Omit<RACTabProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/** One tab button. Its `id` selects the {@link TabPanel} it reveals. */\nexport function Tab({ children, className, ...rest }: TabProps) {\n return (\n <RACTab {...rest} className={[s.tab, className].filter(Boolean).join(' ')}>\n {children}\n </RACTab>\n );\n}\n\n/** Props for {@link TabPanel}, on top of React Aria's `TabPanel` props. */\nexport type TabPanelProps = Omit<RACTabPanelProps, 'className' | 'children'> & {\n children?: ReactNode;\n className?: string;\n};\n\n/** The content shown for the {@link Tab} whose `id` matches this panel's. */\nexport function TabPanel({ children, className, ...rest }: TabPanelProps) {\n return (\n <RACTabPanel {...rest} className={[s.panel, className].filter(Boolean).join(' ')}>\n {children}\n </RACTabPanel>\n );\n}\n"],"mappings":";;;;;;;;;;ACuBA,SAAgB,EAAK,EAAE,aAAU,cAAW,GAAG,KAAmB;AAChE,QACE,kBAAC,GAAD;EAAS,GAAI;EAAM,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EACxE;EACO,CAAA;;AAUd,SAAgB,EAA0B,EAAE,cAAW,GAAG,KAAyB;AACjF,QAAO,kBAAC,GAAD;EAAe,GAAI;EAAM,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EAAI,CAAA;;AAU9F,SAAgB,EAAI,EAAE,aAAU,cAAW,GAAG,KAAkB;AAC9D,QACE,kBAAC,GAAD;EAAQ,GAAI;EAAM,WAAW,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EACtE;EACM,CAAA;;AAWb,SAAgB,EAAS,EAAE,aAAU,cAAW,GAAG,KAAuB;AACxE,QACE,kBAAC,GAAD;EAAa,GAAI;EAAM,WAAW,CAAC,EAAE,OAAO,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EAC7E;EACW,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Timeline-BEyKaBXl.js","names":[],"sources":["../../src/components/Timeline/Timeline.module.css","../../src/components/Timeline/easingSpec.ts","../../src/components/Timeline/EasingPicker.tsx","../../src/components/Timeline/timeScale.ts","../../src/components/Timeline/LaneGraph.tsx","../../src/components/Timeline/Lane.tsx","../../src/components/Timeline/Ruler.tsx","../../src/components/Timeline/Transport.tsx","../../src/components/Timeline/lanes.ts","../../src/components/Timeline/keys.ts","../../src/components/Timeline/Timeline.tsx","../../src/components/Timeline/AnimatedTimeline.tsx"],"sourcesContent":[".ruler {\n position: relative;\n height: var(--wzl-control-h, 24px);\n border-bottom: 1px solid var(--wzl-border);\n background: var(--wzl-surface-raised);\n cursor: ew-resize;\n user-select: none;\n overflow: hidden;\n}\n\n.tick {\n position: absolute;\n top: 0;\n transform: translateX(2px);\n color: var(--wzl-fg-muted);\n pointer-events: none;\n white-space: nowrap;\n}\n\n.tick::before {\n content: '';\n position: absolute;\n left: -2px;\n top: 0;\n bottom: 0;\n border-left: 1px solid var(--wzl-border);\n}\n\n.playhead {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 1px;\n background: var(--wzl-accent-strong);\n pointer-events: none;\n}\n\n.lane {\n display: flex;\n align-items: stretch;\n min-height: 20px;\n border-bottom: 1px solid var(--wzl-line-subtle);\n}\n\n.laneLabel {\n /* Must match `.gutter`'s width exactly, or every key sits off the ruler. */\n box-sizing: border-box;\n flex: 0 0 var(--wzl-timeline-label-w, 120px);\n display: flex;\n align-items: center;\n gap: 4px;\n padding-inline: 6px;\n padding-inline-start: calc(6px + var(--depth, 0) * 12px);\n font-size: var(--wzl-font-size);\n color: var(--wzl-fg);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.lane[data-depth='1'] .laneLabel { --depth: 1; }\n.lane[data-depth='2'] .laneLabel { --depth: 2; }\n.lane[data-depth='3'] .laneLabel { --depth: 3; }\n\n.disclosure { display: inline-flex; flex: none; cursor: pointer; color: var(--wzl-fg-muted); }\n.disclosureIcon { transition: transform var(--wzl-motion-fast) ease; }\n.disclosureCollapsed { transform: rotate(-90deg); }\n\n.laneTrack {\n position: relative;\n flex: 1 1 auto;\n min-width: 0;\n background: var(--wzl-surface);\n}\n\n.key, .eventMark {\n position: absolute;\n top: 50%;\n width: var(--wzl-handle-size);\n height: var(--wzl-handle-size);\n margin-left: calc(var(--wzl-handle-size) / -2);\n background: var(--wzl-fg-muted);\n cursor: ew-resize;\n}\n\n.key { transform: translateY(-50%) rotate(45deg); }\n.eventMark { transform: translateY(-50%); border-radius: 50%; }\n\n.key[aria-current='true'], .eventMark[aria-current='true'] { background: var(--wzl-accent-strong); }\n.key:focus-visible, .eventMark:focus-visible { outline: 2px solid var(--wzl-focus-ring); outline-offset: 1px; }\n\n/* The key stays drawn where it was committed while its ghost tracks the\n pointer, so the drag reads as a move from somewhere to somewhere. */\n.key[data-dragging='true'], .eventMark[data-dragging='true'] { opacity: 0.4; }\n\n.keyGhost, .eventGhost {\n position: absolute;\n top: 50%;\n width: var(--wzl-handle-size);\n height: var(--wzl-handle-size);\n margin-left: calc(var(--wzl-handle-size) / -2);\n background: var(--wzl-accent-soft);\n border: 1px dashed var(--wzl-accent-strong);\n pointer-events: none;\n}\n\n.keyGhost { transform: translateY(-50%) rotate(45deg); }\n.eventGhost { transform: translateY(-50%); border-radius: 50%; }\n\n.lane[data-mode='graph'] { min-height: 72px; }\n\n/* A numeric lane in graph mode is a curve editor filling the track. */\n.graphHost {\n position: absolute;\n inset: 0;\n}\n\n/* Out-specifies the editor's own defaults: the lane keeps the track's ground\n and the timeline's accent line rather than a plot's. */\n.graphHost .graph {\n --plot-bg: transparent;\n --curve-line: var(--wzl-accent-strong);\n --curve-line-width: 1px;\n}\n\n/* A graph lane's value labels hang off the track's start edge into the label\n column, so the lane's own label ellipsizes before it reaches them. */\n.lane[data-graph='true'] .laneLabel {\n padding-inline-end: calc(6px + var(--wzl-timeline-value-axis-w, 32px));\n}\n\n.nestedBar {\n position: absolute;\n top: 4px;\n bottom: 4px;\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n}\n\n.transport {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 6px;\n border-bottom: 1px solid var(--wzl-border);\n background: var(--wzl-surface-raised);\n}\n\n.transportButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n padding: 2px 6px;\n /* Sits on `--wzl-surface-raised`, so a transparent fill and a border that\n close in value leave it reading as text rather than as a control. */\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n cursor: pointer;\n}\n\n.transportButton:hover { border-color: var(--wzl-fg-muted); }\n\n.transportButton[aria-checked='true'] {\n background: var(--wzl-accent-soft);\n border-color: var(--wzl-accent-strong);\n color: var(--wzl-accent-strong);\n}\n\n/* The bar's chrome lives on the row, so controls beside `<Transport>` sit\n inside it rather than hanging off the panel's corner. `.transport` keeps its\n own chrome for standalone use. */\n.transportRow {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-inline-end: 6px;\n border-bottom: 1px solid var(--wzl-border);\n background: var(--wzl-surface-raised);\n}\n\n.transportRow > .transport {\n flex: 1 1 auto;\n border-bottom: 0;\n background: transparent;\n}\n\n/* Last in the row, and growing leftward into the slack `margin-inline-start`\n leaves it: the playhead rewrites this every frame and gains a digit at 10s,\n so anything laid out after it would vibrate. */\n.time {\n margin-inline-start: auto;\n text-align: end;\n font-variant-numeric: tabular-nums;\n color: var(--wzl-fg-muted);\n}\n.rate { display: inline-flex; align-items: center; gap: 6px; color: var(--wzl-fg-muted); }\n.rateSlider { width: 88px; }\n\n/* The detents carry no labels in a bar this dense, so the chosen rate is said\n once, here, in a fixed-width cell so the controls beside it never shift. */\n.rateReadout {\n min-width: 3ch;\n font-variant-numeric: tabular-nums;\n color: var(--wzl-fg);\n}\n\n.root {\n display: flex;\n flex-direction: column;\n min-height: 0;\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n /* `--wzl-font-ui` is condensed, so it reads a size smaller than it measures.\n Everything below inherits this; override per rule only to go up. */\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size-sm);\n line-height: var(--wzl-leading-snug);\n}\n\n.label { padding: 4px 6px; color: var(--wzl-fg-muted); }\n\n.body { display: flex; align-items: stretch; }\n.gutter { box-sizing: border-box; flex: 0 0 var(--wzl-timeline-label-w, 120px); border-right: 1px solid var(--wzl-border); }\n.rulerWrap { flex: 1 1 auto; min-width: 0; }\n\n.lanes {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n.inspector {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 6px;\n border-top: 1px solid var(--wzl-border);\n background: var(--wzl-surface-raised);\n}\n\n.segment {\n position: absolute;\n top: 0;\n bottom: 0;\n cursor: pointer;\n}\n\n.segment[aria-current='true'] { background: var(--wzl-accent-soft); }\n.segment:focus-visible { outline: 2px solid var(--wzl-focus-ring); outline-offset: -2px; }\n\n.easingPicker { display: inline-flex; align-items: center; gap: 6px; }\n.easingLabel { font-variant-numeric: tabular-nums; color: var(--wzl-fg-muted); }\n\n.easingPreview {\n width: 32px;\n height: 32px;\n fill: none;\n stroke: var(--wzl-accent-strong);\n stroke-width: 1;\n border: 1px solid var(--wzl-border);\n}\n\n","import { EASINGS, resolveEasing, type EasingName, type EasingSpec } from '@weasel-js/core';\n\nfunction bezierOf(spec: EasingSpec): readonly [number, number, number, number] | null {\n if (typeof spec === 'object' && spec !== null && 'bezier' in spec) return spec.bezier;\n return null;\n}\n\n/** Control points, for the graph view's draggable handles. `null` means this\n * spec has no handles to drag — the view offers to convert it to a bezier. */\nexport function easingBezier(spec?: EasingSpec): readonly [number, number, number, number] | null {\n if (spec === undefined) return null;\n return bezierOf(spec);\n}\n\n/** What the picker shows for a spec.\n *\n * A function is matched against the built-ins by reference so a consumer that\n * passed `easeOutBack` directly still reads back as `'easeOutBack'`. A wrapped\n * or hand-written function cannot be named and reads as `'custom'` — which is\n * why an editor writes names and control points rather than functions. */\nexport function easingLabel(spec?: EasingSpec): string {\n if (spec === undefined) return 'linear';\n if (typeof spec === 'string') return spec;\n const b = bezierOf(spec);\n if (b) return `cubic-bezier(${b.join(', ')})`;\n for (const [name, fn] of Object.entries(EASINGS)) {\n if (fn === spec) return name;\n }\n return 'custom';\n}\n\n/** `count` evenly spaced samples of the curve over 0..1, for drawing it. */\nexport function sampleEasing(spec: EasingSpec | undefined, count: number): number[] {\n const fn = resolveEasing(spec);\n const out = new Array<number>(count);\n const last = count - 1;\n for (let i = 0; i < count; i++) out[i] = fn(last === 0 ? 0 : i / last);\n return out;\n}\n\n/** Every built-in name, for the picker's list. */\nexport const EASING_NAMES = Object.keys(EASINGS) as EasingName[];\n","import type { ReactElement } from 'react';\nimport type { EasingSpec } from '@weasel-js/core';\nimport s from './Timeline.module.css';\nimport { EASING_NAMES, easingBezier, easingLabel, sampleEasing } from './easingSpec';\n\n/** Samples drawn in the inline curve preview. */\nconst PREVIEW_SAMPLES = 24;\n\n/** Control points for the named curves the picker can convert. A name the table\n * does not cover converts to the CSS default rather than refusing. */\nconst BEZIER_OF: Partial<Record<string, readonly [number, number, number, number]>> = {\n linear: [0, 0, 1, 1],\n easeInQuad: [0.11, 0, 0.5, 0],\n easeOutQuad: [0.5, 1, 0.89, 1],\n easeInOutQuad: [0.45, 0, 0.55, 1],\n easeInCubic: [0.32, 0, 0.67, 0],\n easeOutCubic: [0.33, 1, 0.68, 1],\n easeInOutCubic: [0.65, 0, 0.35, 1],\n easeInSine: [0.12, 0, 0.39, 0],\n easeOutSine: [0.61, 1, 0.88, 1],\n easeInOutSine: [0.37, 0, 0.63, 1],\n};\n\nconst DEFAULT_BEZIER: readonly [number, number, number, number] = [0.25, 0.1, 0.25, 1];\n\nexport interface EasingPickerProps {\n value: EasingSpec | undefined;\n onChange: (next: EasingSpec | undefined) => void;\n}\n\nexport function EasingPicker(props: EasingPickerProps): ReactElement {\n const { value, onChange } = props;\n const bezier = easingBezier(value);\n const label = easingLabel(value);\n const named = bezier === null && (EASING_NAMES as string[]).includes(label);\n const selectValue = bezier !== null ? 'custom' : named ? label : 'linear';\n\n const points = sampleEasing(value, PREVIEW_SAMPLES)\n .map((v, i) => `${(i / (PREVIEW_SAMPLES - 1)) * 100},${100 - v * 100}`)\n .join(' ');\n\n return (\n <div className={s.easingPicker}>\n <label>\n Easing\n <select\n value={selectValue}\n onChange={(e) => onChange(e.target.value === 'linear' ? undefined : e.target.value as EasingSpec)}\n >\n <option value=\"linear\">linear</option>\n {EASING_NAMES.filter((n) => n !== 'linear').map((n) => <option key={n} value={n}>{n}</option>)}\n {bezier !== null ? <option value=\"custom\">custom</option> : null}\n </select>\n </label>\n\n {bezier !== null ? <span className={s.easingLabel}>{label}</span> : (\n <button\n type=\"button\"\n className={s.transportButton}\n onClick={() => onChange({ bezier: BEZIER_OF[selectValue] ?? DEFAULT_BEZIER })}\n >\n Convert to bezier\n </button>\n )}\n\n <svg className={s.easingPreview} data-testid=\"easing-preview\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"none\">\n <polyline points={points} vectorEffect=\"non-scaling-stroke\" />\n </svg>\n </div>\n );\n}\n","import { niceStep } from '../Plot2D/ticks';\n\n/** A visible span of timeline time, in ms. */\nexport interface TimeWindow {\n from: number;\n to: number;\n}\n\nexport interface TimeScale {\n window: TimeWindow;\n widthPx: number;\n toPx(ms: number): number;\n toMs(px: number): number;\n}\n\n/** Narrowest window the user can zoom to, in ms. Below this the playhead and\n * the keys it sits between stop being separable at any sane track width. */\nconst MIN_SPAN_MS = 1;\n\nexport function createTimeScale(window: TimeWindow, widthPx: number): TimeScale {\n const span = window.to - window.from;\n const perMs = span === 0 ? 0 : widthPx / span;\n const perPx = widthPx === 0 ? 0 : span / widthPx;\n return {\n window,\n widthPx,\n toPx: (ms) => (ms - window.from) * perMs,\n toMs: (px) => window.from + px * perPx,\n };\n}\n\n/** Where `ms` sits in the window, as a fraction. The width-free half of\n * `createTimeScale` — a CSS `left: %` needs no pixel width, and asking for one\n * is what drove three copies of this arithmetic into the lane and the ruler. */\nexport function toFraction(window: TimeWindow, ms: number): number {\n const span = window.to - window.from;\n return span === 0 ? 0 : (ms - window.from) / span;\n}\n\n/** `toFraction` as a CSS percentage. */\nexport function toPercent(window: TimeWindow, ms: number): string {\n return `${toFraction(window, ms) * 100}%`;\n}\n\n/** A duration as a fraction of the window. Distinct from `toFraction`, which\n * measures a position and so subtracts `window.from`. */\nexport function spanFraction(window: TimeWindow, ms: number): number {\n const span = window.to - window.from;\n return span === 0 ? 0 : ms / span;\n}\n\n/** A duration as a CSS percentage, for a width. */\nexport function spanPercent(window: TimeWindow, ms: number): string {\n return `${spanFraction(window, ms) * 100}%`;\n}\n\nfunction clampWindow(w: TimeWindow, bounds: TimeWindow): TimeWindow {\n const span = Math.min(w.to - w.from, bounds.to - bounds.from);\n let from = w.from;\n if (from < bounds.from) from = bounds.from;\n if (from + span > bounds.to) from = bounds.to - span;\n return { from, to: from + span };\n}\n\n/** Scale the window by `factor` about `atMs`, which stays at the same fraction\n * of the track. `factor < 1` zooms in. */\nexport function zoomWindow(\n w: TimeWindow, atMs: number, factor: number, bounds: TimeWindow,\n): TimeWindow {\n const span = w.to - w.from;\n const maxSpan = bounds.to - bounds.from;\n const nextSpan = Math.min(maxSpan, Math.max(MIN_SPAN_MS, span * factor));\n const frac = span === 0 ? 0.5 : (atMs - w.from) / span;\n return clampWindow({ from: atMs - frac * nextSpan, to: atMs + (1 - frac) * nextSpan }, bounds);\n}\n\n/** Shift the window by `byMs`, preserving its span. */\nexport function panWindow(w: TimeWindow, byMs: number, bounds: TimeWindow): TimeWindow {\n return clampWindow({ from: w.from + byMs, to: w.to + byMs }, bounds);\n}\n\n/** Minimum gap between time ticks, in px — the ruler's and the graph grid's. */\nexport const TICK_SPACING_PX = 64;\n\n/** Ruler tick times inside `w`, spaced at least `minSpacingPx` apart. */\nexport function tickTimes(w: TimeWindow, widthPx: number, minSpacingPx: number): number[] {\n const span = w.to - w.from;\n if (widthPx <= 0 || span <= 0) return [];\n const step = niceStep((minSpacingPx / widthPx) * span);\n const out: number[] = [];\n for (let t = Math.ceil(w.from / step) * step; t <= w.to + 1e-9; t += step) {\n out.push(Math.round(t / step) * step);\n }\n return out;\n}\n","import { useLayoutEffect, useMemo, useRef, useState, type ReactElement } from 'react';\nimport type { Keyframe } from '@weasel-js/core';\nimport { LayeredCurveEditor } from '../CurveEditor/LayeredCurveEditor';\nimport {\n applyKeyframeDrag, createKeyframeLayer, type KeyframeLayerState,\n} from '../CurveEditor/createKeyframeLayer';\nimport s from './Timeline.module.css';\nimport { TICK_SPACING_PX, tickTimes, type TimeWindow } from './timeScale';\n\n/** Keeps the extreme keys inside the lane: at the very edge a key centers on\n * the lane's border and hangs half into its neighbor. As a percentage of\n * the lane's height, applied top and bottom. */\nconst V_INSET_PCT = 8;\n\n/** One arrow-key step, in ms; shift multiplies by ten. */\nconst KEY_STEP_MS = 10;\n\nexport interface LaneGraphProps {\n keys: readonly Keyframe<number>[];\n label: string;\n window: TimeWindow;\n /** Ruler time of this track's zero. */\n offset: number;\n /** Ruler times a dragged key snaps to. */\n snapTimes: readonly number[];\n selection: number | null;\n selectedSegment: number | null;\n onSelect: (keyIndex: number) => void;\n onSelectSegment?: (keyIndex: number) => void;\n /** Live during a key drag. */\n onKeysInput?: (keys: readonly Keyframe<number>[]) => void;\n /** Once per edit: a drag's end, a handle's release, a key press. */\n onKeysCommit: (keys: readonly Keyframe<number>[]) => void;\n}\n\n/** The value range a lane plots: its keys' extent, padded so no key sits on\n * the border. A flat track gets a unit span so it still has a middle. */\nfunction valueRange(keys: readonly Keyframe<number>[]): [number, number] {\n const values = keys.map((k) => k.value);\n const lo = Math.min(...values);\n const hi = Math.max(...values);\n const span = hi - lo || 1;\n const pad = (span * V_INSET_PCT) / (100 - 2 * V_INSET_PCT);\n return [lo - pad, lo + span + pad];\n}\n\n/** A numeric lane's graph: one keyframe layer on a curve editor, sized to the\n * lane and windowed to the ruler. */\nexport function LaneGraph(props: LaneGraphProps): ReactElement {\n const {\n keys, label, window: win, offset, snapTimes, selection, selectedSegment,\n onSelect, onSelectSegment, onKeysInput, onKeysCommit,\n } = props;\n\n const hostRef = useRef<HTMLDivElement | null>(null);\n const [size, setSize] = useState({ width: 0, height: 0 });\n useLayoutEffect(() => {\n const el = hostRef.current;\n if (!el) return;\n const update = (): void => {\n const r = el.getBoundingClientRect();\n setSize((prev) => (prev.width === r.width && prev.height === r.height ? prev : { width: r.width, height: r.height }));\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const layer = useMemo(() => createKeyframeLayer({\n id: 'keys',\n label,\n formatX: (t) => `${Math.round(t)} ms`,\n // The layer works in track time; the snap candidates are ruler times.\n snapX: snapTimes.map((t) => t - offset),\n xClamp: [0, Infinity],\n step: { x: KEY_STEP_MS },\n }), [label, snapTimes, offset]);\n\n const committed = useMemo<KeyframeLayerState>(\n () => ({ keys, selectedKey: selection, selectedSegment, drag: null }),\n [keys, selection, selectedSegment],\n );\n // Held only while a gesture is in flight, so a consumer echoing live input\n // back through `keys` cannot move the keys out from under the drag.\n const [live, setLive] = useState<KeyframeLayerState | null>(null);\n const state = live ?? committed;\n const stateRef = useRef(state);\n stateRef.current = state;\n\n const layers = useMemo(() => [{ layer, state: state as unknown }], [layer, state]);\n\n const onLayerChange = (_id: string, raw: unknown): void => {\n const next = raw as KeyframeLayerState;\n const prev = stateRef.current;\n if (next.selectedKey !== null && next.selectedKey !== prev.selectedKey) onSelect(next.selectedKey);\n if (next.selectedSegment !== null && next.selectedSegment !== prev.selectedSegment) onSelectSegment?.(next.selectedSegment);\n if (!next.drag) { setLive(null); return; }\n setLive(next);\n // A handle drag writes a fresh easing spec per move; publishing those\n // would grow the consumer's easing cache by one entry per pixel.\n if (next.drag.kind !== 'key') return;\n const applied = applyKeyframeDrag(next).keys;\n if (applied !== next.keys) onKeysInput?.(applied);\n };\n\n const onLayerCommit = (_id: string, raw: unknown, rawPrev: unknown): void => {\n setLive(null);\n const next = raw as KeyframeLayerState;\n if (next.keys !== (rawPrev as KeyframeLayerState).keys) onKeysCommit(next.keys);\n };\n\n const [yLo, yHi] = valueRange(state.keys);\n const ticks = size.width > 0 ? tickTimes(win, size.width, TICK_SPACING_PX) : [];\n\n return (\n <div ref={hostRef} className={s.graphHost}>\n {size.width > 0 && size.height > 0 ? (\n <LayeredCurveEditor\n className={s.graph}\n aria-label={`${label} graph`}\n layers={layers}\n onLayerChange={onLayerChange}\n onLayerCommit={onLayerCommit}\n width={size.width}\n height={size.height}\n xRange={[win.from - offset, win.to - offset]}\n yRange={[yLo, yHi]}\n grid={false}\n axes={false}\n // The ruler's ticks, carried into track time.\n xTicks={{ values: ticks.map((t) => t - offset), labels: false }}\n // Clears a key at the track's start edge, which hangs into the gutter.\n yTicks={{ labels: 'outside', gap: 10 }}\n history={false}\n />\n ) : null}\n </div>\n );\n}\n","import { useEffect, useRef, useState, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent as ReactPointerEvent, type ReactElement } from 'react';\nimport { openPointerSession, type EventTrack, type Keyframe, type PointerSession, type SampledTrack, type TimelineTrack } from '@weasel-js/core';\nimport { ChevronIcon } from '../../icons';\nimport s from './Timeline.module.css';\nimport { createTimeScale, spanPercent, toPercent, type TimeWindow } from './timeScale';\nimport { SNAP_RADIUS_PX, snapToNearest } from '../../snap';\nimport { LaneGraph } from './LaneGraph';\nimport type { LaneRow } from './lanes';\n\n/** One arrow-key step, in ms; shift multiplies by ten. */\nconst KEY_STEP_MS = 10;\n\nexport interface LaneProps {\n row: LaneRow;\n window: TimeWindow;\n mode: 'dope' | 'graph';\n /** Index of the selected key on THIS row, or null. */\n selection: number | null;\n onSelect: (keyIndex: number) => void;\n /** Live during a dope-sheet drag. */\n onKeyInput: (keyIndex: number, toMs: number) => void;\n /** Once, at the end of a dope-sheet gesture. */\n onKeyCommit: (keyIndex: number, toMs: number) => void;\n /** Live during a graph-mode key drag: the row's whole key list. */\n onKeysInput?: (keys: readonly Keyframe<number>[]) => void;\n /** Once per graph-mode edit: the row's whole key list. */\n onKeysCommit?: (keys: readonly Keyframe<number>[]) => void;\n onInsert: (atMs: number) => void;\n onToggleExpand: () => void;\n expanded: boolean;\n /** Times a dragged key snaps to. */\n snapTimes: readonly number[];\n /** Index of the key a selected segment runs INTO, or null. */\n selectedSegment?: number | null;\n onSelectSegment?: (keyIndex: number) => void;\n}\n\nfunction entryTimes(row: LaneRow): number[] {\n if (row.kind === 'sampled') return (row.track as SampledTrack<unknown>).keys.map((k) => k.t);\n if (row.kind === 'event') return (row.track as EventTrack).events.map((e) => e.t);\n return [];\n}\n\nexport function Lane(props: LaneProps): ReactElement {\n const {\n row, window: win, mode, selection, onSelect, onKeyInput, onKeyCommit, onInsert, onToggleExpand, expanded, snapTimes,\n selectedSegment = null, onSelectSegment, onKeysInput, onKeysCommit,\n } = props;\n const trackRef = useRef<HTMLDivElement | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n useEffect(() => () => { sessionRef.current?.cancel(); }, []);\n\n // Where the dragged key would land. Owned here rather than derived from the\n // tracks prop, so a drag previews whether or not the consumer wires\n // `onKeyInput` — the committed key stays put underneath as the origin.\n const [drag, setDrag] = useState<{ keyIndex: number; t: number } | null>(null);\n\n const pct = (ms: number): string => toPercent(win, ms + row.offset);\n\n const msAt = (clientX: number): number => {\n const rect = trackRef.current?.getBoundingClientRect();\n if (!rect) return win.from;\n return createTimeScale(win, rect.width).toMs(clientX - rect.left) - row.offset;\n };\n\n const snapPxToMs = (): number => {\n const width = trackRef.current?.getBoundingClientRect().width ?? 0;\n const scale = createTimeScale(win, width);\n return scale.toMs(SNAP_RADIUS_PX) - scale.toMs(0);\n };\n\n // Only a numeric sampled row has an honest value axis to graph.\n const graph = mode === 'graph' && row.numeric;\n const sampledKeys = row.kind === 'sampled' ? (row.track as SampledTrack<number>).keys : [];\n\n const onKeyPointerDown = (i: number) => (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (e.button !== 0) return;\n e.stopPropagation();\n onSelect(i);\n\n // `snapTimes` are ruler times and `msAt` is track-local, so a nested row\n // has to cross into ruler space to snap and back out to commit.\n const at = (ev: { clientX: number; altKey: boolean }): number => {\n const raw = Math.max(0, msAt(ev.clientX));\n return ev.altKey ? raw : snapToNearest(raw + row.offset, snapTimes, snapPxToMs()) - row.offset;\n };\n\n setDrag({ keyIndex: i, t: times[i] });\n\n const end = (): void => {\n setDrag(null);\n sessionRef.current = null;\n };\n // No pointer capture — see `Ruler`, same idiom across the timeline.\n sessionRef.current?.cancel();\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n setDrag({ keyIndex: i, t: at(ev) });\n onKeyInput(i, at(ev));\n },\n onEnd: (ev) => {\n onKeyCommit(i, at(ev));\n end();\n },\n onCancel: end,\n }, { capture: false });\n };\n\n const onKeyDown = (i: number, t: number) => (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n const step = KEY_STEP_MS * (e.shiftKey ? 10 : 1);\n if (e.key === 'ArrowRight') { e.preventDefault(); onKeyCommit(i, t + step); }\n if (e.key === 'ArrowLeft') { e.preventDefault(); onKeyCommit(i, Math.max(0, t - step)); }\n };\n\n const times = entryTimes(row);\n\n return (\n <div className={s.lane} data-depth={row.depth} data-mode={mode} data-graph={graph ? 'true' : undefined}>\n <div className={s.laneLabel}>\n {row.kind === 'timeline' ? (\n <span\n role=\"button\"\n tabIndex={0}\n aria-expanded={expanded}\n data-testid=\"timeline-disclosure\"\n className={s.disclosure}\n onClick={onToggleExpand}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onToggleExpand(); } }}\n >\n <ChevronIcon\n size={14}\n className={[s.disclosureIcon, !expanded && s.disclosureCollapsed].filter(Boolean).join(' ')}\n />\n </span>\n ) : null}\n {row.label}\n </div>\n <div\n className={s.laneTrack}\n ref={trackRef}\n data-testid=\"timeline-lane-track\"\n onDoubleClick={(e) => { if (row.kind === 'sampled') onInsert(Math.max(0, msAt(e.clientX))); }}\n >\n {graph ? (\n <LaneGraph\n keys={sampledKeys}\n label={row.label}\n window={win}\n offset={row.offset}\n snapTimes={snapTimes}\n selection={selection}\n selectedSegment={selectedSegment}\n onSelect={onSelect}\n onSelectSegment={onSelectSegment}\n onKeysInput={onKeysInput}\n onKeysCommit={(keys) => onKeysCommit?.(keys)}\n />\n ) : (\n <>\n {row.kind === 'timeline' ? (\n <div\n className={s.nestedBar}\n data-testid=\"timeline-nested\"\n style={{ left: pct(0), width: spanPercent(win, (row.track as TimelineTrack).timeline.duration ?? 0) }}\n />\n ) : null}\n {row.kind === 'sampled' ? sampledKeys.slice(1).map((k, idx) => {\n const i = idx + 1;\n return (\n <div\n key={`seg-${i}`}\n role=\"button\"\n tabIndex={0}\n aria-label={`${row.label} segment into ${Math.round(k.t)} ms`}\n aria-current={selectedSegment === i ? 'true' : undefined}\n data-testid=\"timeline-segment\"\n className={s.segment}\n style={{ left: pct(sampledKeys[i - 1].t), width: spanPercent(win, k.t - sampledKeys[i - 1].t) }}\n onClick={() => onSelectSegment?.(i)}\n onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onSelectSegment?.(i); } }}\n />\n );\n }) : null}\n {times.map((t, i) => (\n <div\n key={i}\n role=\"button\"\n tabIndex={0}\n aria-label={`${row.label} key at ${Math.round(t)} ms`}\n aria-current={selection === i ? 'true' : undefined}\n data-testid={row.kind === 'event' ? 'timeline-event' : 'timeline-key'}\n data-dragging={drag?.keyIndex === i ? 'true' : undefined}\n className={row.kind === 'event' ? s.eventMark : s.key}\n style={{ left: pct(t) }}\n onPointerDown={onKeyPointerDown(i)}\n onKeyDown={onKeyDown(i, t)}\n />\n ))}\n {drag ? (\n <div\n aria-hidden=\"true\"\n data-testid=\"timeline-key-ghost\"\n className={row.kind === 'event' ? s.eventGhost : s.keyGhost}\n style={{ left: pct(drag.t) }}\n />\n ) : null}\n </>\n )}\n </div>\n </div>\n );\n}\n","import { useEffect, useLayoutEffect, useRef, useState, type PointerEvent as ReactPointerEvent, type ReactElement, type WheelEvent as ReactWheelEvent } from 'react';\nimport { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport s from './Timeline.module.css';\nimport { createTimeScale, panWindow, TICK_SPACING_PX, tickTimes, toPercent, zoomWindow, type TimeWindow } from './timeScale';\n\n/** One wheel notch's zoom factor. */\nconst ZOOM_STEP = 0.0015;\n\nexport interface RulerProps {\n window: TimeWindow;\n /** The full extent the window may cover — normally `{ from: 0, to: duration }`. */\n bounds: TimeWindow;\n playhead: number;\n onScrub: (t: number) => void;\n onWindowChange: (w: TimeWindow) => void;\n}\n\nfunction formatMs(ms: number): string {\n if (ms >= 1000) return `${(ms / 1000).toFixed(ms % 1000 === 0 ? 0 : 2)}s`;\n return `${Math.round(ms)}ms`;\n}\n\nexport function Ruler(props: RulerProps): ReactElement {\n const { window: win, bounds, playhead, onScrub, onWindowChange } = props;\n const trackRef = useRef<HTMLDivElement | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n useEffect(() => () => { sessionRef.current?.cancel(); }, []);\n\n // Re-render once the track is mounted so ticks lay out against its real\n // width — `trackRef.current` is null on the render that first attaches it.\n const [width, setWidth] = useState(0);\n useLayoutEffect(() => {\n const el = trackRef.current;\n if (!el) return;\n const update = (): void => setWidth(el.getBoundingClientRect().width);\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const widthOf = (): number => trackRef.current?.getBoundingClientRect().width ?? 0;\n const msAt = (clientX: number): number => {\n const rect = trackRef.current?.getBoundingClientRect();\n if (!rect) return win.from;\n const scale = createTimeScale(win, rect.width);\n const raw = scale.toMs(clientX - rect.left);\n return Math.min(win.to, Math.max(win.from, raw));\n };\n\n // `capture: false`: the session tracks the pointer off the ruler on its own,\n // and capture would retarget pointerup and kill the click on non-native\n // children of the scrubbed track.\n const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>): void => {\n if (e.button !== 0) return;\n onScrub(msAt(e.clientX));\n\n sessionRef.current?.cancel();\n const end = (): void => { sessionRef.current = null; };\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => { onScrub(msAt(ev.clientX)); },\n onEnd: end,\n onCancel: end,\n }, { capture: false });\n };\n\n const onWheel = (e: ReactWheelEvent<HTMLDivElement>): void => {\n if (e.ctrlKey) {\n onWindowChange(zoomWindow(win, msAt(e.clientX), Math.exp(e.deltaY * ZOOM_STEP), bounds));\n } else {\n const width = widthOf();\n const perPx = width === 0 ? 0 : (win.to - win.from) / width;\n onWindowChange(panWindow(win, e.deltaY * perPx, bounds));\n }\n };\n\n const ticks = tickTimes(win, width, TICK_SPACING_PX);\n const pct = (ms: number): string => toPercent(win, ms);\n\n return (\n <div\n className={s.ruler}\n ref={trackRef}\n data-testid=\"timeline-ruler\"\n onPointerDown={onPointerDown}\n onWheel={onWheel}\n >\n {ticks.map((t) => (\n <span\n key={t}\n className={s.tick}\n data-testid=\"timeline-tick\"\n style={{ left: pct(t) }}\n >\n {formatMs(t)}\n </span>\n ))}\n <div\n className={s.playhead}\n data-testid=\"timeline-playhead\"\n style={{ left: pct(playhead) }}\n />\n </div>\n );\n}\n","import type { ReactElement } from 'react';\nimport { DetentSlider } from '../DetentSlider';\nimport { PauseIcon, PlayIcon } from '../../icons';\nimport s from './Timeline.module.css';\n\n/** Playback rates the transport offers. */\nconst RATES = [0.25, 0.5, 1, 2, 4] as const;\n\nexport interface TransportProps {\n paused: boolean;\n loop: boolean | number;\n rate: number;\n playhead: number;\n duration: number;\n onPlay: () => void;\n onPause: () => void;\n onLoopChange: (loop: boolean | number) => void;\n onRateChange: (rate: number) => void;\n}\n\nconst seconds = (ms: number): string => `${(ms / 1000).toFixed(2)}s`;\n\nexport function Transport(props: TransportProps): ReactElement {\n const { paused, loop, rate, playhead, duration, onPlay, onPause, onLoopChange, onRateChange } = props;\n const looping = loop !== false && loop !== 0;\n // The handle's scale is whatever anything holding it set, so an off-list rate\n // gets its own detent — `DetentSlider` would otherwise round it to the\n // nearest and label the thumb with a rate the timeline is not running at.\n const rates = RATES.includes(rate as typeof RATES[number])\n ? (RATES as readonly number[])\n : [...RATES, rate].sort((a, b) => a - b);\n\n return (\n <div className={s.transport}>\n <button\n type=\"button\"\n className={s.transportButton}\n aria-label={paused ? 'Play' : 'Pause'}\n onClick={paused ? onPlay : onPause}\n >\n {paused ? <PlayIcon size={14} /> : <PauseIcon size={14} />}\n </button>\n\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={looping}\n aria-label=\"Loop\"\n className={s.transportButton}\n onClick={() => onLoopChange(!looping)}\n >\n ⟲\n </button>\n\n <div className={s.rate}>\n Rate\n <DetentSlider\n ariaLabel=\"Rate\"\n items={rates}\n value={rate}\n onChange={onRateChange}\n formatLabel={(r) => `${r}x`}\n labels=\"none\"\n className={s.rateSlider}\n />\n <span className={s.rateReadout}>{rate}x</span>\n </div>\n\n <span className={s.time} data-testid=\"timeline-time\">\n {seconds(playhead)} / {seconds(duration)}\n </span>\n </div>\n );\n}\n","import type { SampledTrack, Track } from '@weasel-js/core';\n\nexport interface LaneRow {\n /** Stable across re-renders and edits: the dotted index path. */\n key: string;\n /** Index path into the track tree, outermost first. */\n path: number[];\n track: Track;\n label: string;\n /** Nesting depth; 0 for a top-level track. */\n depth: number;\n /** Accumulated `at` from every nested parent, in ms. A key drawn on this row\n * sits at `offset + key.t` on the ruler. */\n offset: number;\n kind: Track['kind'];\n /** True only for a sampled track whose every key value is a number — the\n * sole case where a value axis has an honest meaning. */\n numeric: boolean;\n}\n\nfunction isNumeric(track: Track): boolean {\n if (track.kind !== 'sampled') return false;\n const keys = (track as SampledTrack<unknown>).keys;\n return keys.length > 0 && keys.every((k) => typeof k.value === 'number');\n}\n\n/** Flatten the track tree to rows, descending only into expanded nested\n * timelines. `expanded` holds the `key` of each open nested row. */\nexport function buildLanes(\n tracks: readonly Track[],\n expanded: ReadonlySet<string>,\n): LaneRow[] {\n const out: LaneRow[] = [];\n const walk = (list: readonly Track[], path: number[], depth: number, offset: number): void => {\n list.forEach((track, i) => {\n const nextPath = [...path, i];\n const key = nextPath.join('.');\n out.push({\n key,\n path: nextPath,\n track,\n label: track.label ?? `${track.kind} ${i}`,\n depth,\n offset,\n kind: track.kind,\n numeric: isNumeric(track),\n });\n if (track.kind === 'timeline' && expanded.has(key)) {\n walk(track.timeline.tracks, nextPath, depth + 1, offset + track.at);\n }\n });\n };\n walk(tracks, [], 0, 0);\n return out;\n}\n\n/** The track at an index path, or `undefined` when the path leaves the tree. */\nexport function trackAtPath(tracks: readonly Track[], path: readonly number[]): Track | undefined {\n let list: readonly Track[] = tracks;\n let found: Track | undefined;\n for (const i of path) {\n found = list[i];\n if (!found) return undefined;\n list = found.kind === 'timeline' ? found.timeline.tracks : [];\n }\n return found;\n}\n","import { sampleTrack, type EasingSpec, type EventTrack, type Keyframe, type SampledTrack, type TimelineTrack, type Track } from '@weasel-js/core';\nimport { trackAtPath } from './lanes';\n\nexport interface KeySelection {\n /** Index path into the track tree, outermost first — a `LaneRow.path`. A\n * top-level track is `[i]`; a track inside the nested timeline at `i` is\n * `[i, j]`. */\n trackPath: readonly number[];\n keyIndex: number;\n}\n\nexport interface KeyEdit {\n tracks: Track[];\n selection: KeySelection | null;\n}\n\n/** True when two index paths address the same track. */\nexport function samePath(a: readonly number[], b: readonly number[]): boolean {\n return a.length === b.length && a.every((n, i) => n === b[i]);\n}\n\n/** Shallow-clone one track, replacing only its key list. `onTick`, `fire`,\n * `interpolate` and `interpolator` survive as references, which is what lets a\n * running timeline take an edited track without rewiring. */\nfunction withKeys(track: Track, keys: Keyframe<unknown>[]): Track {\n return { ...(track as SampledTrack<unknown>), keys } as Track;\n}\n\nfunction withEvents(track: Track, events: EventTrack['events']): Track {\n return { ...(track as EventTrack), events } as Track;\n}\n\n/** Replace the track at `path`, cloning the timeline tracks along the way so\n * the caller's tree is untouched at every level, not just the top. */\nfunction replaceAtPath(tracks: readonly Track[], path: readonly number[], next: Track): Track[] {\n const out = tracks.slice();\n const [i, ...rest] = path;\n if (rest.length === 0) {\n out[i] = next;\n return out;\n }\n const parent = out[i];\n if (parent?.kind !== 'timeline') return out;\n const tt = parent as TimelineTrack;\n out[i] = {\n ...tt,\n timeline: { ...tt.timeline, tracks: replaceAtPath(tt.timeline.tracks, rest, next) },\n };\n return out;\n}\n\n/** Move a key or an event crossing to `toMs`, clamped at zero. `toMs` is in the\n * addressed track's own time, not the ruler's — a nested track's keys are\n * measured from its parent's `at`.\n *\n * Re-sorts, and reports the moved entry's new index. `sampleTrack` binary-\n * searches without sorting first, so a drag past a neighbour that left the list\n * unsorted would sample the wrong segment and raise nothing. */\nexport function moveKey(\n tracks: readonly Track[], sel: KeySelection, toMs: number,\n): KeyEdit {\n const track = trackAtPath(tracks, sel.trackPath);\n const t = Math.max(0, toMs);\n\n if (track?.kind === 'sampled') {\n const st = track as SampledTrack<unknown>;\n const moved = { ...st.keys[sel.keyIndex], t };\n const keys = st.keys.filter((_, i) => i !== sel.keyIndex);\n const at = keys.findIndex((k) => k.t > t);\n const keyIndex = at === -1 ? keys.length : at;\n keys.splice(keyIndex, 0, moved);\n return {\n tracks: replaceAtPath(tracks, sel.trackPath, withKeys(track, keys)),\n selection: { trackPath: sel.trackPath, keyIndex },\n };\n }\n\n if (track?.kind === 'event') {\n const et = track as EventTrack;\n const moved = { ...et.events[sel.keyIndex], t };\n const events = et.events.filter((_, i) => i !== sel.keyIndex);\n const at = events.findIndex((e) => e.t > t);\n const keyIndex = at === -1 ? events.length : at;\n events.splice(keyIndex, 0, moved);\n return {\n tracks: replaceAtPath(tracks, sel.trackPath, withEvents(track, events)),\n selection: { trackPath: sel.trackPath, keyIndex },\n };\n }\n\n return { tracks: tracks.slice(), selection: sel };\n}\n\n/** Insert a key at `atMs`, seeded with whatever the track already reads there\n * so inserting alone never changes the motion. */\nexport function insertKey(\n tracks: readonly Track[], trackPath: readonly number[], atMs: number,\n): KeyEdit {\n const track = trackAtPath(tracks, trackPath);\n if (track?.kind !== 'sampled') return { tracks: tracks.slice(), selection: null };\n\n const st = track as SampledTrack<unknown>;\n const t = Math.max(0, atMs);\n const value = st.keys.length === 0 ? 0 : sampleTrack(st, t);\n const keys = st.keys.slice();\n const at = keys.findIndex((k) => k.t > t);\n const keyIndex = at === -1 ? keys.length : at;\n keys.splice(keyIndex, 0, { t, value });\n return {\n tracks: replaceAtPath(tracks, trackPath, withKeys(track, keys)),\n selection: { trackPath, keyIndex },\n };\n}\n\n/** Remove a key, selecting the one before it. */\nexport function deleteKey(tracks: readonly Track[], sel: KeySelection): KeyEdit {\n const track = trackAtPath(tracks, sel.trackPath);\n if (track?.kind !== 'sampled') return { tracks: tracks.slice(), selection: sel };\n\n const st = track as SampledTrack<unknown>;\n const keys = st.keys.filter((_, i) => i !== sel.keyIndex);\n return {\n tracks: replaceAtPath(tracks, sel.trackPath, withKeys(track, keys)),\n selection: keys.length === 0\n ? null\n : { trackPath: sel.trackPath, keyIndex: Math.max(0, sel.keyIndex - 1) },\n };\n}\n\n/** Set the easing shaping the approach INTO the selected key. */\nexport function setKeyEasing(\n tracks: readonly Track[], sel: KeySelection, easing: EasingSpec | undefined,\n): Track[] {\n const track = trackAtPath(tracks, sel.trackPath);\n if (track?.kind !== 'sampled') return tracks.slice();\n\n const st = track as SampledTrack<unknown>;\n const keys = st.keys.slice();\n const { easing: _drop, ...rest } = keys[sel.keyIndex];\n keys[sel.keyIndex] = easing === undefined ? rest : { ...rest, easing };\n return replaceAtPath(tracks, sel.trackPath, withKeys(track, keys));\n}\n\n/** Replace a sampled track's whole key list, keeping its callbacks. */\nexport function setKeys(\n tracks: readonly Track[], trackPath: readonly number[], keys: readonly Keyframe<unknown>[],\n): Track[] {\n const track = trackAtPath(tracks, trackPath);\n if (track?.kind !== 'sampled') return tracks.slice();\n return replaceAtPath(tracks, trackPath, withKeys(track, keys.slice()));\n}\n\n/** Set the selected key's value, leaving its time alone. */\nexport function setKeyValue(\n tracks: readonly Track[], sel: KeySelection, value: unknown,\n): Track[] {\n const track = trackAtPath(tracks, sel.trackPath);\n if (track?.kind !== 'sampled') return tracks.slice();\n\n const st = track as SampledTrack<unknown>;\n const keys = st.keys.slice();\n keys[sel.keyIndex] = { ...keys[sel.keyIndex], value };\n return replaceAtPath(tracks, sel.trackPath, withKeys(track, keys));\n}\n\nexport { snapToNearest as snapTime } from '../../snap';\n","import { useMemo, useState, type KeyboardEvent as ReactKeyboardEvent, type ReactElement, type ReactNode } from 'react';\nimport type { EasingSpec, Keyframe, SampledTrack, Track } from '@weasel-js/core';\nimport s from './Timeline.module.css';\nimport { EasingPicker } from './EasingPicker';\nimport { Lane } from './Lane';\nimport { Ruler } from './Ruler';\nimport { Transport, type TransportProps } from './Transport';\nimport { buildLanes, trackAtPath } from './lanes';\nimport { deleteKey, insertKey, moveKey, samePath, setKeyEasing, setKeys, setKeyValue, type KeySelection } from './keys';\nimport type { TimeWindow } from './timeScale';\n\nexport type { KeySelection } from './keys';\nexport type { TimeWindow } from './timeScale';\n\nexport interface KeyEditorCtx<T = unknown> {\n key: Keyframe<T>;\n track: SampledTrack<T>;\n selection: KeySelection;\n /** Replace the selected key; routed through this component's `onChange`. */\n commit: (next: Keyframe<T>) => void;\n /** Set the easing shaping the approach into this key. */\n setEasing: (easing: EasingSpec | undefined) => void;\n}\n\nexport interface TimelineProps {\n tracks: readonly Track[];\n duration: number;\n playhead: number;\n\n mode?: 'dope' | 'graph';\n onModeChange?: (mode: 'dope' | 'graph') => void;\n\n /** Live during a drag — wire for preview, do not write to history. */\n onInput?: (next: Track[]) => void;\n /** Committed at gesture end: one call per gesture. */\n onChange: (next: Track[]) => void;\n onScrub: (t: number) => void;\n\n /** `false` hides the transport. */\n transport?: Omit<TransportProps, 'playhead' | 'duration'> | false;\n selection?: KeySelection | null;\n onSelect?: (sel: KeySelection | null) => void;\n renderKeyEditor?: (ctx: KeyEditorCtx) => ReactNode;\n\n window?: TimeWindow;\n onWindowChange?: (w: TimeWindow) => void;\n\n label?: ReactNode;\n className?: string;\n}\n\nexport function Timeline(props: TimelineProps): ReactElement {\n const {\n tracks, duration, playhead,\n onInput, onChange, onScrub,\n renderKeyEditor, label, className,\n } = props;\n\n const [expanded, setExpanded] = useState<ReadonlySet<string>>(() => new Set());\n const [ownWindow, setOwnWindow] = useState<TimeWindow>({ from: 0, to: duration });\n const [ownSelection, setOwnSelection] = useState<KeySelection | null>(null);\n const [segmentSelection, setSegmentSelection] = useState<KeySelection | null>(null);\n\n const win = props.window ?? ownWindow;\n const setWindow = props.onWindowChange ?? setOwnWindow;\n const selection = props.selection !== undefined ? props.selection : ownSelection;\n const setSelection = props.onSelect ?? setOwnSelection;\n\n const bounds: TimeWindow = { from: 0, to: duration };\n const rows = useMemo(() => buildLanes(tracks, expanded), [tracks, expanded]);\n\n /** Every key time on every row — what a dragged key snaps to. */\n const snapTimes = useMemo(() => {\n const out = new Set<number>([0, duration]);\n for (const row of rows) {\n if (row.kind === 'sampled') for (const k of (row.track as SampledTrack<unknown>).keys) out.add(k.t + row.offset);\n }\n return [...out];\n }, [rows, duration]);\n\n const toggle = (key: string): void => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key); else next.add(key);\n return next;\n });\n };\n\n const onRootKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>): void => {\n if ((e.key === 'Delete' || e.key === 'Backspace') && selection) {\n e.preventDefault();\n const r = deleteKey(tracks, selection);\n onChange(r.tracks);\n setSelection(r.selection);\n }\n };\n\n const selectedTrack = selection ? trackAtPath(tracks, selection.trackPath) : undefined;\n const selectedKey = selectedTrack?.kind === 'sampled'\n ? (selectedTrack as SampledTrack<unknown>).keys[selection!.keyIndex]\n : undefined;\n\n const segmentTrack = segmentSelection ? trackAtPath(tracks, segmentSelection.trackPath) : undefined;\n const segmentKey = segmentTrack?.kind === 'sampled'\n ? (segmentTrack as SampledTrack<unknown>).keys[segmentSelection!.keyIndex]\n : undefined;\n\n return (\n <div\n className={[s.root, className].filter(Boolean).join(' ')}\n data-testid=\"timeline-root\"\n tabIndex={-1}\n onKeyDown={onRootKeyDown}\n >\n {label ? <div className={s.label}>{label}</div> : null}\n\n {props.transport === false ? null : (\n <div className={s.transportRow}>\n <Transport\n {...(props.transport ?? {\n paused: true, loop: false, rate: 1,\n onPlay: () => {}, onPause: () => {},\n onLoopChange: () => {}, onRateChange: () => {},\n })}\n playhead={playhead}\n duration={duration}\n />\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={(props.mode ?? 'dope') === 'graph'}\n aria-label=\"Graph mode\"\n className={s.transportButton}\n onClick={() => props.onModeChange?.((props.mode ?? 'dope') === 'graph' ? 'dope' : 'graph')}\n >\n Graph\n </button>\n </div>\n )}\n\n <div className={s.body}>\n <div className={s.gutter} />\n <div className={s.rulerWrap}>\n <Ruler\n window={win}\n bounds={bounds}\n playhead={playhead}\n onScrub={onScrub}\n onWindowChange={setWindow}\n />\n </div>\n </div>\n\n <div className={s.lanes}>\n {rows.map((row) => {\n const trackPath = row.path;\n return (\n <Lane\n key={row.key}\n row={row}\n window={win}\n mode={props.mode ?? 'dope'}\n selection={selection && samePath(selection.trackPath, trackPath) ? selection.keyIndex : null}\n expanded={expanded.has(row.key)}\n snapTimes={snapTimes}\n onToggleExpand={() => toggle(row.key)}\n onSelect={(keyIndex) => setSelection({ trackPath, keyIndex })}\n onKeyInput={(keyIndex, toMs) => {\n onInput?.(moveKey(tracks, { trackPath, keyIndex }, toMs).tracks);\n }}\n onKeyCommit={(keyIndex, toMs) => {\n const r = moveKey(tracks, { trackPath, keyIndex }, toMs);\n onChange(r.tracks);\n setSelection(r.selection);\n }}\n onKeysInput={(keys) => onInput?.(setKeys(tracks, trackPath, keys))}\n onKeysCommit={(keys) => onChange(setKeys(tracks, trackPath, keys))}\n onInsert={(atMs) => {\n const r = insertKey(tracks, trackPath, atMs);\n onChange(r.tracks);\n setSelection(r.selection);\n }}\n selectedSegment={segmentSelection && samePath(segmentSelection.trackPath, trackPath) ? segmentSelection.keyIndex : null}\n onSelectSegment={(keyIndex) => setSegmentSelection({ trackPath, keyIndex })}\n />\n );\n })}\n </div>\n\n {renderKeyEditor && selection && selectedKey && selectedTrack?.kind === 'sampled' ? (\n <div className={s.inspector}>\n {renderKeyEditor({\n key: selectedKey,\n track: selectedTrack as SampledTrack<unknown>,\n selection,\n commit: (next) => onChange(setKeyValue(tracks, selection, next.value)),\n setEasing: (easing) => onChange(setKeyEasing(tracks, selection, easing)),\n })}\n </div>\n ) : null}\n\n {segmentSelection && segmentKey ? (\n <div className={s.inspector}>\n <EasingPicker\n value={segmentKey.easing}\n onChange={(next) => onChange(setKeyEasing(tracks, segmentSelection, next))}\n />\n </div>\n ) : null}\n </div>\n );\n}\n","import { useCallback, useEffect, useReducer, useRef, type ReactElement } from 'react';\nimport { useVisibleRaf, type TimelineHandle, type Track } from '@weasel-js/core';\nimport { Timeline, type TimelineProps } from './Timeline';\n\nexport interface AnimatedTimelineProps\n extends Omit<TimelineProps, 'tracks' | 'duration' | 'playhead' | 'onChange' | 'onScrub' | 'transport'> {\n handle: TimelineHandle;\n /** `false` hides the transport, as on `<Timeline>`. */\n transport?: false;\n}\n\n/** Binds `<Timeline>` to a live handle.\n *\n * `handle.tracks()` returns the timeline's own array, not a copy, so an edit\n * splices it in place inside `edit()`. Assigning a replacement array would\n * update nothing and raise nothing. */\nexport function AnimatedTimeline(props: AnimatedTimelineProps): ReactElement {\n const { handle, ...rest } = props;\n const [, bump] = useReducer((n: number) => n + 1, 0);\n\n // `subscribe` fires on `edit` only, so the playhead comes from the frame loop.\n useEffect(() => handle.subscribe(bump), [handle]);\n\n // `useVisibleRaf` returns a controller; a continuous loop re-requests from\n // inside its own frame, and nothing runs until something calls `request`.\n // No `onResume` here: this loop measures no elapsed time of its own, it only\n // reads the playhead the animator already advanced.\n const rafRef = useRef<{ request(): void } | null>(null);\n const raf = useVisibleRaf(() => {\n bump();\n if (!handle.isPaused()) rafRef.current?.request();\n });\n rafRef.current = raf;\n useEffect(() => {\n if (!handle.isPaused()) raf.request();\n return () => raf.cancel();\n }, [handle, raf]);\n\n const onChange = useCallback((next: Track[]) => {\n handle.edit(() => {\n const live = handle.tracks() as Track[];\n live.splice(0, live.length, ...next);\n });\n }, [handle]);\n\n const playhead = handle.time();\n const duration = handle.duration();\n\n return (\n <Timeline\n {...rest}\n tracks={handle.tracks()}\n duration={duration}\n playhead={playhead}\n onChange={onChange}\n onScrub={handle.seek}\n transport={props.transport === false ? false : {\n paused: handle.isPaused(),\n loop: handle.loop(),\n rate: handle.timeScale(),\n onPlay: () => {\n // `rearm` declines to revive a timeline at its duration, so resume\n // alone would do nothing. Play-at-end rewinds first.\n if (handle.time() >= duration) handle.seek(0);\n handle.resume();\n raf.request();\n bump();\n },\n onPause: () => { handle.pause(); bump(); },\n onLoopChange: (next) => { handle.setLoop(next); bump(); },\n onRateChange: (next) => { handle.setTimeScale(next); bump(); },\n }}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Toast-iqLDD_XZ.js","names":[],"sources":["../../src/components/Toast/queue.ts","../../src/components/Toast/Toast.module.css","../../src/components/Toast/Toast.tsx"],"sourcesContent":["import { UNSTABLE_ToastQueue } from 'react-aria-components';\nimport { flushSync } from 'react-dom';\n\n/** Severity of a toast, driving its accent color and icon. */\nexport type ToastTone = 'info' | 'success' | 'warning' | 'error';\n\n/** Kit-owned payload rendered inside each toast. */\nexport interface ToastContent {\n title: string;\n description?: string;\n tone: ToastTone;\n}\n\n/** Options for a single toast. */\nexport interface ToastOptions {\n description?: string;\n /**\n * Auto-dismiss delay in ms. Default 8000. `null` — sticky until dismissed.\n * Must be > 0; RAC treats 0 as no timer (sticky).\n */\n ttlMs?: number | null;\n /** Stable identity: re-adding with the same id replaces the earlier toast. */\n id?: string;\n}\n\nconst DEFAULT_TTL_MS = 8000;\n\n// Tracks overlapping toast-triggered view transitions so the `:root`\n// marker class survives until the *last* one settles.\nlet activeToastTransitions = 0;\n\n/**\n * RAC's sanctioned toast-animation hook: wrap every queue mutation in a\n * view transition so entry, exit, and the surviving stack's reflow all\n * animate (choreography lives in `toastViewTransitions.css`). Falls back\n * to a plain synchronous update when the browser lacks the API or the\n * user prefers reduced motion — i.e. exactly the pre-animation behavior.\n * The jsdom test environment always takes the fallback path.\n */\nfunction viewTransitionWrapUpdate(fn: () => void): void {\n const reduceMotion =\n typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n if (typeof document === 'undefined' || !('startViewTransition' in document) || reduceMotion) {\n fn();\n return;\n }\n // The marker class scopes the CSS that un-names `:root` (keeps the rest\n // of the page live and interactive during the transition) to\n // toast-owned transitions only.\n const root = document.documentElement;\n activeToastTransitions++;\n root.classList.add('wzl-toast-vt');\n // flushSync: the DOM must reflect the queue change inside the\n // transition callback, before the browser snapshots the new state.\n const vt = document.startViewTransition(() => flushSync(fn));\n // `ready` rejects when the browser skips the transition — routine under\n // a rapid burst of toasts, where each new transition supersedes the\n // in-flight one. The DOM update has still applied by then, but an\n // untouched `ready` surfaces the rejection as a console error, so\n // consume it.\n vt.ready.catch(() => {});\n void vt.finished.finally(() => {\n if (--activeToastTransitions === 0) root.classList.remove('wzl-toast-vt');\n });\n}\n\n// The wrapped RAC queue lives in a module-scoped WeakMap so the public\n// class surface carries no RAC (UNSTABLE_) types — see the design spec's\n// containment rule. `racQueueOf` below is the folder-internal accessor.\nconst RAC_QUEUES = new WeakMap<ToastQueue, UNSTABLE_ToastQueue<ToastContent>>();\n\n/**\n * Kit-owned toast queue. Create isolated instances with\n * `createToastQueue()` (tests, secondary roots); most apps use the\n * module-level `defaultToastQueue` via `toast()`.\n *\n * At most 5 toasts are visible at once; adding a 6th shows immediately\n * and hides the oldest (it returns when a slot frees, with its ttl\n * restarted).\n */\nexport class ToastQueue {\n private keysById = new Map<string, string>();\n\n constructor() {\n RAC_QUEUES.set(\n this,\n new UNSTABLE_ToastQueue<ToastContent>({\n maxVisibleToasts: 5,\n wrapUpdate: viewTransitionWrapUpdate,\n }),\n );\n }\n\n add(tone: ToastTone, title: string, options: ToastOptions = {}): void {\n const { description, ttlMs = DEFAULT_TTL_MS, id } = options;\n const rac = RAC_QUEUES.get(this)!;\n if (id !== undefined) {\n const existing = this.keysById.get(id);\n if (existing !== undefined) rac.close(existing);\n }\n let key: string;\n const racOptions: { timeout?: number; onClose?: () => void } =\n ttlMs === null ? {} : { timeout: ttlMs };\n if (id !== undefined) {\n racOptions.onClose = () => {\n if (this.keysById.get(id) === key) this.keysById.delete(id);\n };\n }\n key = rac.add({ title, description, tone }, racOptions);\n if (id !== undefined) this.keysById.set(id, key);\n }\n\n /** Dismiss the toast previously added with this `id`. No-op if none is live. */\n dismiss(id: string): void {\n const key = this.keysById.get(id);\n if (key !== undefined) RAC_QUEUES.get(this)!.close(key);\n }\n\n /** Dismiss every queued toast. */\n clear(): void {\n RAC_QUEUES.get(this)!.clear();\n this.keysById.clear();\n }\n}\n\n/**\n * Creates an independent toast queue. Most apps use the module-level\n * `defaultToastQueue` that `toast()` writes to; make your own when a surface\n * needs its own stack, and pass it to `ToastRegion`.\n */\nexport function createToastQueue(): ToastQueue {\n return new ToastQueue();\n}\n\n/**\n * Folder-internal bridge to the wrapped RAC queue for `ToastRegion`.\n * Deliberately NOT exported from the Toast folder's `index.ts` — the\n * RAC toast surface is unstable and must not leak past this folder.\n */\nexport function racQueueOf(queue: ToastQueue): UNSTABLE_ToastQueue<ToastContent> {\n return RAC_QUEUES.get(queue)!;\n}\n\n/** Module-level default queue used by `toast()` and `<ToastRegion>`. */\nexport const defaultToastQueue = createToastQueue();\n\ntype ToastFn = {\n (title: string, options?: ToastOptions): void;\n info(title: string, options?: ToastOptions): void;\n success(title: string, options?: ToastOptions): void;\n warning(title: string, options?: ToastOptions): void;\n error(title: string, options?: ToastOptions): void;\n};\n\nfunction tonedAdd(tone: ToastTone) {\n return (title: string, options?: ToastOptions) => defaultToastQueue.add(tone, title, options);\n}\n\n/** Imperative convenience over `defaultToastQueue`. Bare `toast(...)` is `info`. */\nexport const toast: ToastFn = Object.assign(tonedAdd('info'), {\n info: tonedAdd('info'),\n success: tonedAdd('success'),\n warning: tonedAdd('warning'),\n error: tonedAdd('error'),\n});\n",".region {\n position: fixed;\n z-index: 1100;\n display: flex;\n /* RAC unshifts new toasts to the front of the DOM (newest-first), and the\n * ol/li wrappers are display:contents, so the flex direction alone\n * decides which end of the stack the newest toast lands on. Base\n * (bottom corners): column-reverse puts DOM-first (newest) at the\n * bottom, hugging the corner. Top corners override back to column so\n * DOM-first still hugs the top edge. */\n flex-direction: column-reverse;\n gap: 8px;\n max-width: 360px;\n outline: none;\n}\n\n.bottomRight { bottom: 16px; right: 16px; }\n.bottomLeft { bottom: 16px; left: 16px; }\n.topRight { top: 16px; right: 16px; flex-direction: column; }\n.topLeft { top: 16px; left: 16px; flex-direction: column; }\n\n.toast {\n --toast-accent: var(--wzl-accent-strong);\n /* Groups every toast under one ident so toastViewTransitions.css can\n * target `::view-transition-new/old(.wzl-toast)` without knowing the\n * per-toast view-transition-name (set inline in Toast.tsx). */\n view-transition-class: wzl-toast;\n display: flex;\n align-items: flex-start;\n gap: 8px;\n background: var(--wzl-surface);\n border: 1px solid var(--wzl-border);\n border-left: 3px solid var(--toast-accent);\n border-radius: var(--wzl-radius-md);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n padding: 10px 12px;\n outline: none;\n}\n\n.region[data-focus-visible],\n.toast[data-focus-visible] {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 1px;\n}\n\n.toneInfo { /* default accent — uses --toast-accent fallback */ }\n.toneSuccess { --toast-accent: var(--wzl-success, #2ec27e); }\n.toneWarning { --toast-accent: var(--wzl-warning, #d99a3f); }\n.toneError { --toast-accent: var(--wzl-danger); }\n\n.content {\n flex: 1;\n min-width: 0;\n}\n\n.title {\n display: block;\n font-size: var(--wzl-font-size);\n font-weight: var(--wzl-font-weight-bold);\n}\n\n.description {\n display: block;\n margin-top: 2px;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-muted);\n}\n\n.close {\n flex: 0 0 auto;\n background: none;\n border: none;\n color: var(--wzl-fg-muted);\n font-size: var(--wzl-font-size-lg);\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n border-radius: var(--wzl-radius-sm);\n}\n\n.close:hover {\n color: var(--wzl-fg);\n}\n","import {\n UNSTABLE_ToastRegion as RACToastRegion,\n UNSTABLE_Toast as RACToast,\n UNSTABLE_ToastContent as RACToastContent,\n Text,\n Button,\n} from 'react-aria-components';\nimport { defaultToastQueue, racQueueOf, type ToastQueue, type ToastTone } from './queue';\nimport s from './Toast.module.css';\nimport './toastViewTransitions.css';\n\n/** Screen corner a toast stack anchors to. */\nexport type ToastPlacement = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n\n/** Props for {@link ToastRegion}. */\nexport interface ToastRegionProps {\n /** Queue to render. Defaults to the module-level `defaultToastQueue`. */\n queue?: ToastQueue;\n /** Screen corner for the stack. Default `bottom-right`. */\n placement?: ToastPlacement;\n className?: string;\n}\n\nconst placementClass: Record<ToastPlacement, string> = {\n 'bottom-right': s.bottomRight,\n 'bottom-left': s.bottomLeft,\n 'top-right': s.topRight,\n 'top-left': s.topLeft,\n};\n\nconst toneClass: Record<ToastTone, string> = {\n info: s.toneInfo,\n success: s.toneSuccess,\n warning: s.toneWarning,\n error: s.toneError,\n};\n\n/**\n * Renders the toast stack for a queue. Mount once, near the app root.\n * From the underlying React Aria region: landmark semantics (keyboard /\n * F6 reachable), screen-reader announcements, and hover pausing the\n * auto-dismiss timers.\n */\nexport function ToastRegion(props: ToastRegionProps) {\n const { queue = defaultToastQueue, placement = 'bottom-right', className } = props;\n return (\n <RACToastRegion\n queue={racQueueOf(queue)}\n aria-label=\"Notifications\"\n className={[s.region, placementClass[placement], className].filter(Boolean).join(' ')}\n >\n {({ toast: t }) => (\n <RACToast\n toast={t}\n className={[s.toast, toneClass[t.content.tone]].filter(Boolean).join(' ')}\n // Inline style is the one sanctioned exception here: each toast\n // needs a *unique* view-transition-name (so enter/exit/reflow\n // track per-toast), and a class can't mint unique idents. The\n // prefix keeps the ident valid (RAC keys can start with digits).\n style={{ viewTransitionName: `wzl-toast-${t.key.replace(/[^a-zA-Z0-9_-]/g, '_')}` }}\n >\n <RACToastContent className={s.content}>\n <Text slot=\"title\" className={s.title}>{t.content.title}</Text>\n {t.content.description !== undefined && (\n <Text slot=\"description\" className={s.description}>{t.content.description}</Text>\n )}\n </RACToastContent>\n <Button slot=\"close\" className={s.close} aria-label=\"Dismiss notification\">\n ×\n </Button>\n </RACToast>\n )}\n </RACToastRegion>\n );\n}\n"],"mappings":"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"}
|