@weasel-js/ui 1.6.0 → 1.6.1
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 → ActionBar-DCq16yFA.js} +9 -9
- package/dist/chunks/ActionBar-DCq16yFA.js.map +1 -0
- package/dist/chunks/{Badge-DZ0lFs14.js → Badge-CyAIZSDo.js} +151 -151
- package/dist/chunks/Badge-CyAIZSDo.js.map +1 -0
- package/dist/chunks/{BandEditor-D-nVrVO4.js → BandEditor-Dhm_Xrlr.js} +5 -4
- package/dist/chunks/BandEditor-Dhm_Xrlr.js.map +1 -0
- package/dist/chunks/{Button-Dyp31jMt.js → Button-BIdXHQOx.js} +3 -3
- package/dist/chunks/Button-BIdXHQOx.js.map +1 -0
- package/dist/chunks/{ButtonBar-smZzFuEo.js → ButtonBar-D5Vi0aj_.js} +4 -4
- package/dist/chunks/ButtonBar-D5Vi0aj_.js.map +1 -0
- package/dist/chunks/{Callout-xLjnx7Mp.js → Callout-yEn9iPlh.js} +4 -4
- package/dist/chunks/Callout-yEn9iPlh.js.map +1 -0
- package/dist/chunks/{Checkbox-ChZz30Ds.js → Checkbox-DHXTUHO9.js} +1 -1
- package/dist/chunks/Checkbox-DHXTUHO9.js.map +1 -0
- package/dist/chunks/{Code-v4tDWuuN.js → Code-AsKpgnv6.js} +3 -3
- package/dist/chunks/Code-AsKpgnv6.js.map +1 -0
- package/dist/chunks/{ColorField-DJSCuSJv.js → ColorField-BcMcW8Sa.js} +3 -3
- package/dist/chunks/ColorField-BcMcW8Sa.js.map +1 -0
- package/dist/chunks/{ColorModeControl-JOEiqtE0.js → ColorModeControl-ke5PXbBa.js} +3 -3
- package/dist/chunks/ColorModeControl-ke5PXbBa.js.map +1 -0
- package/dist/chunks/{ComboBox-C1JgIpML.js → ComboBox-DpT8lc3p.js} +3 -3
- package/dist/chunks/ComboBox-DpT8lc3p.js.map +1 -0
- package/dist/chunks/{CurveEditor-f1R4kSbD.js → CurveEditor-BDJXjEvK.js} +35 -35
- package/dist/chunks/CurveEditor-BDJXjEvK.js.map +1 -0
- package/dist/chunks/{DataGrid-CZPrB_3a.js → DataGrid-D-dZj1BD.js} +13 -13
- package/dist/chunks/DataGrid-D-dZj1BD.js.map +1 -0
- package/dist/chunks/{DetailList-DHaXX4TG.js → DetailList-BMER1LUZ.js} +1 -1
- package/dist/chunks/DetailList-BMER1LUZ.js.map +1 -0
- package/dist/chunks/{DetentSlider-BYyaZha6.js → DetentSlider-CL8TlX3S.js} +2 -2
- package/dist/chunks/DetentSlider-CL8TlX3S.js.map +1 -0
- package/dist/chunks/{Dialog-GIvjoDQj.js → Dialog-BWyNMgpf.js} +3 -3
- package/dist/chunks/Dialog-BWyNMgpf.js.map +1 -0
- package/dist/chunks/{Disclosure-D79jhmh2.js → Disclosure-0G0uh2fa.js} +1 -1
- package/dist/chunks/Disclosure-0G0uh2fa.js.map +1 -0
- package/dist/chunks/{DragGhost-DcU7cOwy.js → DragGhost-B5_hb9W9.js} +2 -2
- package/dist/chunks/DragGhost-B5_hb9W9.js.map +1 -0
- package/dist/chunks/{DragHandleGlyph-BtBxHjZS.js → DragHandleGlyph-Dr7Fgo0I.js} +1 -1
- package/dist/chunks/DragHandleGlyph-Dr7Fgo0I.js.map +1 -0
- package/dist/chunks/{Field-xbqRmjsI.js → Field--onYxXTF.js} +7 -6
- package/dist/chunks/Field--onYxXTF.js.map +1 -0
- package/dist/chunks/{FillStrokeSwatch-BViKXIPx.js → FillStrokeSwatch-DNb0KXPk.js} +11 -11
- package/dist/chunks/FillStrokeSwatch-DNb0KXPk.js.map +1 -0
- package/dist/chunks/{FontFamilySelect-r9TV_xIH.js → FontFamilySelect-DXlMKWfK.js} +2 -2
- package/dist/chunks/FontFamilySelect-DXlMKWfK.js.map +1 -0
- package/dist/chunks/{GradientEditor-BRTxHG-W.js → GradientEditor-B3a-MsVJ.js} +8 -6
- package/dist/chunks/GradientEditor-B3a-MsVJ.js.map +1 -0
- package/dist/chunks/{Icon-BW0qv_U7.js → Icon-Cgzy8VgC.js} +2 -2
- package/dist/chunks/Icon-Cgzy8VgC.js.map +1 -0
- package/dist/chunks/{InlineRange-BwGFDhpT.js → InlineRange-eOj6JpJl.js} +1 -1
- package/dist/chunks/InlineRange-eOj6JpJl.js.map +1 -0
- package/dist/chunks/{Input-Bx2cuUbS.js → Input-CHmkfYaD.js} +2 -2
- package/dist/chunks/Input-CHmkfYaD.js.map +1 -0
- package/dist/chunks/{ItemList-DzJ2lK0R.js → ItemList-Cm-HUWCi.js} +6 -7
- package/dist/chunks/ItemList-Cm-HUWCi.js.map +1 -0
- package/dist/chunks/{ItemList.module-DDUXfuZL.js → ItemList.module-BSpCSxan.js} +1 -1
- package/dist/chunks/ItemList.module-BSpCSxan.js.map +1 -0
- package/dist/chunks/{Jog-LPoac9s4.js → Jog-DESxZ44P.js} +4 -4
- package/dist/chunks/Jog-DESxZ44P.js.map +1 -0
- package/dist/chunks/{Keycaps-CKiKOJnJ.js → Keycaps-C0H_v2gO.js} +2 -2
- package/dist/chunks/Keycaps-C0H_v2gO.js.map +1 -0
- package/dist/chunks/{LayerList-BeGrkLb9.js → LayerList-nz-QYvTF.js} +14 -14
- package/dist/chunks/LayerList-nz-QYvTF.js.map +1 -0
- package/dist/chunks/{ListEditor-C6zLzCsJ.js → ListEditor-BQ6AV3Ac.js} +2 -2
- package/dist/chunks/ListEditor-BQ6AV3Ac.js.map +1 -0
- package/dist/chunks/{MenuButton-BcdhrNGh.js → MenuButton-DtaGenKf.js} +3 -3
- package/dist/chunks/MenuButton-DtaGenKf.js.map +1 -0
- package/dist/chunks/{MeshEditor-BOOIFWnz.js → MeshEditor-qqGc6hlD.js} +4 -4
- package/dist/chunks/MeshEditor-qqGc6hlD.js.map +1 -0
- package/dist/chunks/{NumberField-C1Y7zP-a.js → NumberField-CwJOMmQR.js} +3 -3
- package/dist/chunks/NumberField-CwJOMmQR.js.map +1 -0
- package/dist/chunks/{OptionsBar-DlcagPYK.js → OptionsBar-DbzAgIln.js} +3 -3
- package/dist/chunks/OptionsBar-DbzAgIln.js.map +1 -0
- package/dist/chunks/{PaintField-B2mLRZa4.js → PaintField-DaJukRc6.js} +4 -4
- package/dist/chunks/PaintField-DaJukRc6.js.map +1 -0
- package/dist/chunks/{PaintInput-BFKhd83I.js → PaintInput-Byhna3H-.js} +7 -7
- package/dist/chunks/PaintInput-Byhna3H-.js.map +1 -0
- package/dist/chunks/{PatternPicker-DdfpxoQy.js → PatternPicker-B-cUt-JT.js} +2 -2
- package/dist/chunks/PatternPicker-B-cUt-JT.js.map +1 -0
- package/dist/chunks/{Plot2D-lyO7waPQ.js → Plot2D-CTXHkL5p.js} +13 -13
- package/dist/chunks/Plot2D-CTXHkL5p.js.map +1 -0
- package/dist/chunks/{PointPlotter-DGs9i5P-.js → PointPlotter-Bxo6haKe.js} +5 -4
- package/dist/chunks/PointPlotter-Bxo6haKe.js.map +1 -0
- package/dist/chunks/Powerline-ExO5GwEr.js +42 -0
- package/dist/chunks/Powerline-ExO5GwEr.js.map +1 -0
- package/dist/chunks/{Prefs-Ctu0H4-b.js → Prefs-DpqFKiwY.js} +18 -17
- package/dist/chunks/Prefs-DpqFKiwY.js.map +1 -0
- package/dist/chunks/{Properties-Bbneh1ws.js → Properties-D_KzDN9K.js} +13 -12
- package/dist/chunks/Properties-D_KzDN9K.js.map +1 -0
- package/dist/chunks/{PropertyField-CDb19d3F.js → PropertyField-DGCecAnV.js} +22 -20
- package/dist/chunks/PropertyField-DGCecAnV.js.map +1 -0
- package/dist/chunks/{RadioGroup-BK2mD6Q0.js → RadioGroup-Bh4-12e-.js} +2 -2
- package/dist/chunks/RadioGroup-Bh4-12e-.js.map +1 -0
- package/dist/chunks/{RangeSlider-BiRAOsTh.js → RangeSlider-BKRRTPTZ.js} +1 -1
- package/dist/chunks/RangeSlider-BKRRTPTZ.js.map +1 -0
- package/dist/chunks/{ResizeHandle-Cc6EOehs.js → ResizeHandle-BKT0XYpx.js} +7 -7
- package/dist/chunks/ResizeHandle-BKT0XYpx.js.map +1 -0
- package/dist/chunks/{Select-DVValBe1.js → Select-CzyfaXzp.js} +4 -4
- package/dist/chunks/Select-CzyfaXzp.js.map +1 -0
- package/dist/chunks/{SelectionPanel-C5LmZA7b.js → SelectionPanel-BgUM1h03.js} +2 -2
- package/dist/chunks/SelectionPanel-BgUM1h03.js.map +1 -0
- package/dist/chunks/{Sidebar-DKKd4R-x.js → Sidebar-BZ0m4afn.js} +8 -8
- package/dist/chunks/Sidebar-BZ0m4afn.js.map +1 -0
- package/dist/chunks/{SidebarPanel-u5NsuAZY.js → SidebarPanel-aoIqxbtq.js} +3 -3
- package/dist/chunks/SidebarPanel-aoIqxbtq.js.map +1 -0
- package/dist/chunks/{Slider-BwRrWHFW.js → Slider-CFwQYQ10.js} +60 -58
- package/dist/chunks/Slider-CFwQYQ10.js.map +1 -0
- package/dist/chunks/{StatusBar-CdPdmmda.js → StatusBar-DzoPDY4k.js} +8 -8
- package/dist/chunks/StatusBar-DzoPDY4k.js.map +1 -0
- package/dist/chunks/{SwatchGrid-C-Zx2Exd.js → SwatchGrid-BARC3rnF.js} +1 -1
- package/dist/chunks/SwatchGrid-BARC3rnF.js.map +1 -0
- package/dist/chunks/{Switch-DxF7TsEu.js → Switch-CSYmcM8y.js} +1 -1
- package/dist/chunks/Switch-CSYmcM8y.js.map +1 -0
- package/dist/chunks/{Tabs-GXEfoe3o.js → Tabs-BbpzLLdY.js} +1 -1
- package/dist/chunks/Tabs-BbpzLLdY.js.map +1 -0
- package/dist/chunks/{Timeline-SgmpQtUV.js → Timeline--58F7MED.js} +21 -21
- package/dist/chunks/Timeline--58F7MED.js.map +1 -0
- package/dist/chunks/{Toast-BfcljZz6.js → Toast-DNBb-24H.js} +1 -1
- package/dist/chunks/Toast-DNBb-24H.js.map +1 -0
- package/dist/chunks/{ToggleBar-Btnl5r7F.js → ToggleBar-DTjGeS0Q.js} +13 -11
- package/dist/chunks/ToggleBar-DTjGeS0Q.js.map +1 -0
- package/dist/chunks/{TokenPanel-5ajHEHha.js → TokenPanel-CMq7uydV.js} +21 -18
- package/dist/chunks/TokenPanel-CMq7uydV.js.map +1 -0
- package/dist/chunks/{ToolButton-rzqlFlAc.js → ToolButton-CR4sQlxF.js} +2 -2
- package/dist/chunks/ToolButton-CR4sQlxF.js.map +1 -0
- package/dist/chunks/{ToolGroup-4bUBV5k6.js → ToolGroup-Dpg1LN0l.js} +7 -7
- package/dist/chunks/ToolGroup-Dpg1LN0l.js.map +1 -0
- package/dist/chunks/{ToolOptionsBar-BF3TNXA3.js → ToolOptionsBar-CtzTlaN2.js} +6 -5
- package/dist/chunks/ToolOptionsBar-CtzTlaN2.js.map +1 -0
- package/dist/chunks/{ToolPalette-Uw4PvWwa.js → ToolPalette-CKG9qY6P.js} +6 -6
- package/dist/chunks/ToolPalette-CKG9qY6P.js.map +1 -0
- package/dist/chunks/{Tooltip-9XiJF937.js → Tooltip-Clg2mhSt.js} +2 -2
- package/dist/chunks/Tooltip-Clg2mhSt.js.map +1 -0
- package/dist/chunks/{Tree-BGt6P8tI.js → Tree-DVOtc9R5.js} +3 -3
- package/dist/chunks/Tree-DVOtc9R5.js.map +1 -0
- package/dist/chunks/{createKeyframeLayer-BzDRCrrh.js → createKeyframeLayer-Czj6Y235.js} +32 -32
- package/dist/chunks/createKeyframeLayer-Czj6Y235.js.map +1 -0
- package/dist/chunks/{dlog-CMYiRF3Q.js → dlog-F0I8KsIW.js} +2 -2
- package/dist/chunks/dlog-F0I8KsIW.js.map +1 -0
- package/dist/chunks/{formatShortcut-BoGqUoWi.js → formatShortcut-DRQrVThu.js} +2 -2
- package/dist/chunks/formatShortcut-DRQrVThu.js.map +1 -0
- package/dist/chunks/icons-Dk8SRS8g.js +162 -0
- package/dist/chunks/icons-Dk8SRS8g.js.map +1 -0
- package/dist/chunks/{interactiveTarget-C7QUBaJN.js → interactiveTarget-BNZmTow2.js} +1 -1
- package/dist/chunks/{interactiveTarget-C7QUBaJN.js.map → interactiveTarget-BNZmTow2.js.map} +1 -1
- package/dist/chunks/{keyGlyph-CLvUHU8c.js → keyGlyph-C0qTakG-.js} +1 -1
- package/dist/chunks/keyGlyph-C0qTakG-.js.map +1 -0
- package/dist/chunks/{model-BQMvTLnW.js → model-Du7Lta4C.js} +5 -5
- package/dist/chunks/model-Du7Lta4C.js.map +1 -0
- package/dist/chunks/{number-6S8IzXVt.js → number-D3NEx_8r.js} +1 -1
- package/dist/chunks/number-D3NEx_8r.js.map +1 -0
- package/dist/chunks/{paintPreview-DPtZS4yU.js → paintPreview-CuSZiHpR.js} +1 -1
- package/dist/chunks/paintPreview-CuSZiHpR.js.map +1 -0
- package/dist/chunks/{paintValue-Bz6AVuh3.js → paintValue-BpaGPjV3.js} +3 -3
- package/dist/chunks/paintValue-BpaGPjV3.js.map +1 -0
- package/dist/chunks/{portalHost-CjpCnuGU.js → portalHost-yTOVuCej.js} +1 -1
- package/dist/chunks/portalHost-yTOVuCej.js.map +1 -0
- package/dist/chunks/{prefField-CLkhrvYl.js → prefField-Dnl7nmjQ.js} +3 -3
- package/dist/chunks/prefField-Dnl7nmjQ.js.map +1 -0
- package/dist/chunks/{segmentTooltip-CAkZlW-t.js → segmentTooltip-CStYHKzd.js} +2 -2
- package/dist/chunks/segmentTooltip-CStYHKzd.js.map +1 -0
- package/dist/chunks/{segmentedControl.module-D2nw68Yo.js → segmentedControl.module-Bv7DEg8f.js} +1 -1
- package/dist/chunks/segmentedControl.module-Bv7DEg8f.js.map +1 -0
- package/dist/chunks/{snap-DC4Km8xG.js → snap-DRjbOiG9.js} +1 -1
- package/dist/chunks/snap-DRjbOiG9.js.map +1 -0
- package/dist/chunks/{stance-CarrcbbP.js → stance-BFQpS_fL.js} +1 -1
- package/dist/chunks/stance-BFQpS_fL.js.map +1 -0
- package/dist/chunks/{useReorderDragList-Dcezzgix.js → useReorderDragList-B_-5L5Gu.js} +2 -2
- package/dist/chunks/useReorderDragList-B_-5L5Gu.js.map +1 -0
- package/dist/chunks/{useRovingTabIndex-B4uJqiKP.js → useRovingTabIndex-Bp_hYm_h.js} +1 -1
- package/dist/chunks/useRovingTabIndex-Bp_hYm_h.js.map +1 -0
- package/dist/components/ActionBar/ActionBar.d.ts +1 -1
- package/dist/components/ActionBar/ActionBar.d.ts.map +1 -1
- package/dist/components/ActionBar/index.js +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/BandEditor/index.js +1 -1
- package/dist/components/Button/index.js +1 -1
- package/dist/components/ButtonBar/index.js +1 -1
- package/dist/components/Callout/Callout.d.ts +1 -1
- package/dist/components/Callout/Callout.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 +1 -1
- package/dist/components/Code/Code.d.ts.map +1 -1
- package/dist/components/Code/index.js +1 -1
- package/dist/components/ColorField/ColorField.d.ts +1 -1
- package/dist/components/ColorField/ColorField.d.ts.map +1 -1
- package/dist/components/ColorField/index.js +1 -1
- package/dist/components/ColorModeControl/index.js +1 -1
- package/dist/components/ComboBox/ComboBox.d.ts +2 -2
- package/dist/components/ComboBox/ComboBox.d.ts.map +1 -1
- package/dist/components/ComboBox/index.js +1 -1
- package/dist/components/CurveEditor/CurveEditor.d.ts +1 -1
- package/dist/components/CurveEditor/CurveEditor.d.ts.map +1 -1
- package/dist/components/CurveEditor/LayeredCurveEditor.d.ts +1 -1
- package/dist/components/CurveEditor/LayeredCurveEditor.d.ts.map +1 -1
- package/dist/components/CurveEditor/index.js +3 -3
- package/dist/components/DataGrid/DataGrid.d.ts +1 -1
- 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 +2 -2
- package/dist/components/DetailList/DetailList.d.ts.map +1 -1
- package/dist/components/DetailList/index.js +1 -1
- package/dist/components/DetentSlider/index.js +1 -1
- package/dist/components/Dialog/Dialog.d.ts +1 -1
- 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 +3 -3
- package/dist/components/Disclosure/Disclosure.d.ts.map +1 -1
- package/dist/components/Disclosure/index.js +1 -1
- package/dist/components/DragGhost/index.js +1 -1
- package/dist/components/DragHandleGlyph/DragHandleGlyph.d.ts +1 -1
- package/dist/components/DragHandleGlyph/DragHandleGlyph.d.ts.map +1 -1
- package/dist/components/DragHandleGlyph/index.js +1 -1
- package/dist/components/Field/Field.d.ts +1 -1
- package/dist/components/Field/Field.d.ts.map +1 -1
- package/dist/components/Field/index.js +1 -1
- package/dist/components/FillStrokeSwatch/index.js +1 -1
- package/dist/components/FontFamilySelect/FontFamilySelect.d.ts +1 -1
- package/dist/components/FontFamilySelect/FontFamilySelect.d.ts.map +1 -1
- package/dist/components/FontFamilySelect/index.js +1 -1
- package/dist/components/GradientEditor/index.js +1 -1
- package/dist/components/InlineRange/index.js +1 -1
- package/dist/components/Input/index.js +1 -1
- package/dist/components/ItemList/index.js +1 -1
- package/dist/components/Jog/Jog.d.ts +1 -1
- package/dist/components/Jog/Jog.d.ts.map +1 -1
- package/dist/components/Jog/index.js +1 -1
- package/dist/components/Keycaps/Keycap.d.ts +1 -1
- package/dist/components/Keycaps/Keycap.d.ts.map +1 -1
- package/dist/components/Keycaps/Keycaps.d.ts +1 -1
- package/dist/components/Keycaps/Keycaps.d.ts.map +1 -1
- package/dist/components/Keycaps/index.js +2 -2
- package/dist/components/LayerList/LayerList.d.ts +1 -1
- package/dist/components/LayerList/LayerList.d.ts.map +1 -1
- package/dist/components/LayerList/index.js +1 -1
- package/dist/components/ListEditor/ListEditor.d.ts +1 -1
- package/dist/components/ListEditor/ListEditor.d.ts.map +1 -1
- package/dist/components/ListEditor/index.js +1 -1
- package/dist/components/MenuButton/MenuButton.d.ts +1 -1
- 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/UnitField.d.ts +1 -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/PointPlotter.d.ts +1 -1
- package/dist/components/PointPlotter/PointPlotter.d.ts.map +1 -1
- package/dist/components/PointPlotter/index.js +1 -1
- package/dist/components/Powerline/Powerline.d.ts +1 -1
- 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 +1 -1
- package/dist/components/Prefs/PrefsDialog.d.ts.map +1 -1
- package/dist/components/Prefs/PrefsForm.d.ts +1 -1
- package/dist/components/Prefs/PrefsForm.d.ts.map +1 -1
- package/dist/components/Prefs/PrefsPane.d.ts +1 -1
- package/dist/components/Prefs/PrefsPane.d.ts.map +1 -1
- package/dist/components/Prefs/PrefsRail.d.ts +1 -1
- package/dist/components/Prefs/PrefsRail.d.ts.map +1 -1
- package/dist/components/Prefs/PrefsRow.d.ts +1 -1
- package/dist/components/Prefs/PrefsRow.d.ts.map +1 -1
- package/dist/components/Prefs/index.js +2 -2
- package/dist/components/Properties/CurveField.d.ts +1 -1
- package/dist/components/Properties/CurveField.d.ts.map +1 -1
- package/dist/components/Properties/DialogRow.d.ts +1 -1
- package/dist/components/Properties/DialogRow.d.ts.map +1 -1
- package/dist/components/Properties/PropertyField.d.ts +2 -2
- package/dist/components/Properties/PropertyField.d.ts.map +1 -1
- package/dist/components/Properties/PropertyGroup.d.ts +1 -1
- package/dist/components/Properties/PropertyGroup.d.ts.map +1 -1
- package/dist/components/Properties/PropertyPanel.d.ts +4 -4
- package/dist/components/Properties/PropertyPanel.d.ts.map +1 -1
- package/dist/components/Properties/Subpanel.d.ts +1 -1
- package/dist/components/Properties/Subpanel.d.ts.map +1 -1
- package/dist/components/Properties/index.js +2 -2
- package/dist/components/Properties/storyLayouts.d.ts +1 -1
- package/dist/components/Properties/storyLayouts.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +2 -2
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
- package/dist/components/RadioGroup/index.js +1 -1
- package/dist/components/RangeSlider/RangeSlider.d.ts +1 -1
- package/dist/components/RangeSlider/RangeSlider.d.ts.map +1 -1
- package/dist/components/RangeSlider/index.js +1 -1
- package/dist/components/ResizeHandle/ResizeHandle.d.ts +1 -1
- package/dist/components/ResizeHandle/ResizeHandle.d.ts.map +1 -1
- package/dist/components/ResizeHandle/index.js +1 -1
- package/dist/components/Select/Select.d.ts +3 -3
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectionPanel/SelectionPanel.d.ts +1 -1
- package/dist/components/SelectionPanel/SelectionPanel.d.ts.map +1 -1
- package/dist/components/SelectionPanel/index.js +2 -2
- package/dist/components/Sidebar/Sidebar.d.ts +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +1 -1
- package/dist/components/SidebarPanel/SidebarPanel.d.ts +1 -1
- package/dist/components/SidebarPanel/SidebarPanel.d.ts.map +1 -1
- package/dist/components/SidebarPanel/index.js +1 -1
- package/dist/components/Slider/Slider.d.ts +2 -1
- package/dist/components/Slider/Slider.d.ts.map +1 -1
- package/dist/components/Slider/index.js +1 -1
- package/dist/components/StatusBar/StatusBar.d.ts +3 -3
- package/dist/components/StatusBar/StatusBar.d.ts.map +1 -1
- package/dist/components/StatusBar/index.js +1 -1
- package/dist/components/SwatchGrid/index.js +1 -1
- package/dist/components/Switch/index.js +1 -1
- package/dist/components/Tabs/Tabs.d.ts +4 -4
- package/dist/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs/index.js +1 -1
- package/dist/components/Timeline/index.js +1 -1
- package/dist/components/Toast/Toast.d.ts +1 -1
- package/dist/components/Toast/Toast.d.ts.map +1 -1
- package/dist/components/Toast/index.js +1 -1
- package/dist/components/ToggleBar/index.js +1 -1
- package/dist/components/TokenPanel/TokenPanel.d.ts +1 -1
- package/dist/components/TokenPanel/TokenPanel.d.ts.map +1 -1
- package/dist/components/TokenPanel/index.js +1 -1
- package/dist/components/ToolButton/ToolButton.d.ts +1 -1
- package/dist/components/ToolButton/ToolButton.d.ts.map +1 -1
- package/dist/components/ToolButton/index.js +1 -1
- package/dist/components/ToolGroup/ToolGroup.d.ts +1 -1
- package/dist/components/ToolGroup/ToolGroup.d.ts.map +1 -1
- package/dist/components/ToolGroup/index.js +1 -1
- package/dist/components/ToolOptionsBar/ToolOptionsBar.d.ts +1 -1
- 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 +1 -1
- package/dist/components/ToolPalette/ToolPalette.d.ts.map +1 -1
- package/dist/components/ToolPalette/index.js +2 -2
- package/dist/components/Tooltip/Tooltip.d.ts +2 -2
- package/dist/components/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/components/Tooltip/index.js +1 -1
- package/dist/components/Tree/index.js +1 -1
- package/dist/icons/Icon.d.ts +1 -1
- package/dist/icons/Icon.d.ts.map +1 -1
- package/dist/icons/ShapeKindIcon.d.ts +1 -1
- package/dist/icons/ShapeKindIcon.d.ts.map +1 -1
- package/dist/icons/index.d.ts +52 -52
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +108 -89
- package/dist/index.js.map +1 -1
- package/dist/overlays/portalHost.d.ts +1 -1
- package/dist/overlays/portalHost.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +8 -8
- package/dist/chunks/ActionBar-CYvfmEz0.js.map +0 -1
- package/dist/chunks/Badge-DZ0lFs14.js.map +0 -1
- package/dist/chunks/BandEditor-D-nVrVO4.js.map +0 -1
- package/dist/chunks/Button-Dyp31jMt.js.map +0 -1
- package/dist/chunks/ButtonBar-smZzFuEo.js.map +0 -1
- package/dist/chunks/Callout-xLjnx7Mp.js.map +0 -1
- package/dist/chunks/Checkbox-ChZz30Ds.js.map +0 -1
- package/dist/chunks/Code-v4tDWuuN.js.map +0 -1
- package/dist/chunks/ColorField-DJSCuSJv.js.map +0 -1
- package/dist/chunks/ColorModeControl-JOEiqtE0.js.map +0 -1
- package/dist/chunks/ComboBox-C1JgIpML.js.map +0 -1
- package/dist/chunks/CurveEditor-f1R4kSbD.js.map +0 -1
- package/dist/chunks/DataGrid-CZPrB_3a.js.map +0 -1
- package/dist/chunks/DetailList-DHaXX4TG.js.map +0 -1
- package/dist/chunks/DetentSlider-BYyaZha6.js.map +0 -1
- package/dist/chunks/Dialog-GIvjoDQj.js.map +0 -1
- package/dist/chunks/Disclosure-D79jhmh2.js.map +0 -1
- package/dist/chunks/DragGhost-DcU7cOwy.js.map +0 -1
- package/dist/chunks/DragHandleGlyph-BtBxHjZS.js.map +0 -1
- package/dist/chunks/Field-xbqRmjsI.js.map +0 -1
- package/dist/chunks/FillStrokeSwatch-BViKXIPx.js.map +0 -1
- package/dist/chunks/FontFamilySelect-r9TV_xIH.js.map +0 -1
- package/dist/chunks/GradientEditor-BRTxHG-W.js.map +0 -1
- package/dist/chunks/Icon-BW0qv_U7.js.map +0 -1
- package/dist/chunks/InlineRange-BwGFDhpT.js.map +0 -1
- package/dist/chunks/Input-Bx2cuUbS.js.map +0 -1
- package/dist/chunks/ItemList-DzJ2lK0R.js.map +0 -1
- package/dist/chunks/ItemList.module-DDUXfuZL.js.map +0 -1
- package/dist/chunks/Jog-LPoac9s4.js.map +0 -1
- package/dist/chunks/Keycaps-CKiKOJnJ.js.map +0 -1
- package/dist/chunks/LayerList-BeGrkLb9.js.map +0 -1
- package/dist/chunks/ListEditor-C6zLzCsJ.js.map +0 -1
- package/dist/chunks/MenuButton-BcdhrNGh.js.map +0 -1
- package/dist/chunks/MeshEditor-BOOIFWnz.js.map +0 -1
- package/dist/chunks/NumberField-C1Y7zP-a.js.map +0 -1
- package/dist/chunks/OptionsBar-DlcagPYK.js.map +0 -1
- package/dist/chunks/PaintField-B2mLRZa4.js.map +0 -1
- package/dist/chunks/PaintInput-BFKhd83I.js.map +0 -1
- package/dist/chunks/PatternPicker-DdfpxoQy.js.map +0 -1
- package/dist/chunks/Plot2D-lyO7waPQ.js.map +0 -1
- package/dist/chunks/PointPlotter-DGs9i5P-.js.map +0 -1
- package/dist/chunks/Powerline-DBEPceLE.js +0 -38
- package/dist/chunks/Powerline-DBEPceLE.js.map +0 -1
- package/dist/chunks/Prefs-Ctu0H4-b.js.map +0 -1
- package/dist/chunks/Properties-Bbneh1ws.js.map +0 -1
- package/dist/chunks/PropertyField-CDb19d3F.js.map +0 -1
- package/dist/chunks/RadioGroup-BK2mD6Q0.js.map +0 -1
- package/dist/chunks/RangeSlider-BiRAOsTh.js.map +0 -1
- package/dist/chunks/ResizeHandle-Cc6EOehs.js.map +0 -1
- package/dist/chunks/Select-DVValBe1.js.map +0 -1
- package/dist/chunks/SelectionPanel-C5LmZA7b.js.map +0 -1
- package/dist/chunks/Sidebar-DKKd4R-x.js.map +0 -1
- package/dist/chunks/SidebarPanel-u5NsuAZY.js.map +0 -1
- package/dist/chunks/Slider-BwRrWHFW.js.map +0 -1
- package/dist/chunks/StatusBar-CdPdmmda.js.map +0 -1
- package/dist/chunks/SwatchGrid-C-Zx2Exd.js.map +0 -1
- package/dist/chunks/Switch-DxF7TsEu.js.map +0 -1
- package/dist/chunks/Tabs-GXEfoe3o.js.map +0 -1
- package/dist/chunks/Timeline-SgmpQtUV.js.map +0 -1
- package/dist/chunks/Toast-BfcljZz6.js.map +0 -1
- package/dist/chunks/ToggleBar-Btnl5r7F.js.map +0 -1
- package/dist/chunks/TokenPanel-5ajHEHha.js.map +0 -1
- package/dist/chunks/ToolButton-rzqlFlAc.js.map +0 -1
- package/dist/chunks/ToolGroup-4bUBV5k6.js.map +0 -1
- package/dist/chunks/ToolOptionsBar-BF3TNXA3.js.map +0 -1
- package/dist/chunks/ToolPalette-Uw4PvWwa.js.map +0 -1
- package/dist/chunks/Tooltip-9XiJF937.js.map +0 -1
- package/dist/chunks/Tree-BGt6P8tI.js.map +0 -1
- package/dist/chunks/createKeyframeLayer-BzDRCrrh.js.map +0 -1
- package/dist/chunks/dlog-CMYiRF3Q.js.map +0 -1
- package/dist/chunks/formatShortcut-BoGqUoWi.js.map +0 -1
- package/dist/chunks/icons-DbScMMqX.js +0 -179
- package/dist/chunks/icons-DbScMMqX.js.map +0 -1
- package/dist/chunks/keyGlyph-CLvUHU8c.js.map +0 -1
- package/dist/chunks/model-BQMvTLnW.js.map +0 -1
- package/dist/chunks/number-6S8IzXVt.js.map +0 -1
- package/dist/chunks/paintPreview-DPtZS4yU.js.map +0 -1
- package/dist/chunks/paintValue-Bz6AVuh3.js.map +0 -1
- package/dist/chunks/portalHost-CjpCnuGU.js.map +0 -1
- package/dist/chunks/prefField-CLkhrvYl.js.map +0 -1
- package/dist/chunks/segmentTooltip-CAkZlW-t.js.map +0 -1
- package/dist/chunks/segmentedControl.module-D2nw68Yo.js.map +0 -1
- package/dist/chunks/snap-DC4Km8xG.js.map +0 -1
- package/dist/chunks/stance-CarrcbbP.js.map +0 -1
- package/dist/chunks/useReorderDragList-Dcezzgix.js.map +0 -1
- package/dist/chunks/useRovingTabIndex-B4uJqiKP.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LayerList-BeGrkLb9.js","names":[],"sources":["../../src/components/LayerList/LayerList.module.css","../../src/components/LayerList/LayerList.tsx","../../src/components/LayerList/useSceneLayerList.ts"],"sourcesContent":["/* Row box, hover and selection are `ItemList`'s, so a layer list and the\n history list stacked under it agree. What is here is only what a layer has. */\n.root,\n.root * {\n box-sizing: border-box;\n}\n\n.root {\n display: grid;\n gap: var(--wzl-space-4);\n}\n\n.head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--wzl-space-4);\n}\n\n.title {\n margin: 0;\n font-family: var(--wzl-font-display);\n font-weight: var(--wzl-font-weight-light);\n font-size: var(--wzl-font-size-lg);\n color: var(--wzl-fg);\n}\n\n.palette {\n display: flex;\n gap: var(--wzl-space-2);\n}\n\n.add {\n font: inherit;\n height: var(--wzl-control-h);\n background: transparent;\n border: 1px dashed var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n padding: var(--wzl-space-1) var(--wzl-space-4);\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n cursor: pointer;\n}\n\n.add:hover {\n background: var(--wzl-surface-raised);\n color: var(--wzl-fg);\n}\n\n.level {\n display: flex;\n flex-direction: column;\n gap: 1px;\n position: relative;\n width: 100%;\n}\n\n/* A level of cards is a `PropertyList`, so the controls in each body take the\n panel's density; its rows are spaced like cards rather than lines. */\n.level.cards {\n display: grid;\n row-gap: var(--wzl-space-3);\n}\n\n.entry {\n min-width: 0;\n}\n\n.row {\n user-select: none;\n}\n\n.row:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: -2px;\n}\n\n/* A child's twisty sits under its parent's grip. */\n.children {\n padding-left: calc(var(--wzl-disclosure-target, var(--wzl-control-h-sm)) + var(--wzl-space-4));\n}\n\n/* The layer the drag picked up holds its place, faded, while its ghost follows the pointer. */\n.dragging {\n opacity: 0.5;\n}\n\n/* Where the drop would land: a line on the edge of the layer it would land against. */\n.dropBefore {\n box-shadow: 0 -2px 0 var(--wzl-accent);\n}\n\n.dropAfter {\n box-shadow: 0 2px 0 var(--wzl-accent);\n}\n\n/* One width for the grip and the lock, so a locked row's label lines up. */\n.grip,\n.lock {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n min-width: 13px;\n color: var(--wzl-fg-muted);\n}\n\n.row:not([data-locked]) {\n cursor: grab;\n}\n\n.row:not([data-locked]):active {\n cursor: grabbing;\n}\n\n.stop {\n display: inline-flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n/* Holds the twisty column open on rows with nothing to expand, so labels line up down a level. */\n.twistyGap {\n flex: 0 0 auto;\n width: var(--wzl-disclosure-target, var(--wzl-control-h-sm));\n}\n\n/* Sized, so the empty column a locked row keeps is the checkbox's width. */\n.check {\n flex: 0 0 auto;\n width: 13px;\n height: 13px;\n margin: 0;\n accent-color: var(--wzl-accent);\n}\n\n.handle {\n font: inherit;\n background: transparent;\n border: 0;\n color: var(--wzl-fg-subtle);\n padding: var(--wzl-space-1) var(--wzl-space-2);\n cursor: grab;\n touch-action: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: var(--wzl-icon-button-size);\n height: var(--wzl-icon-button-size);\n border-radius: var(--wzl-radius-sm);\n font-weight: var(--wzl-font-weight-bold);\n font-size: var(--wzl-font-size);\n}\n\n.card[data-tone] .handle {\n background: var(--wzl-tone);\n color: var(--wzl-fg-on-accent);\n}\n\n.handle:active {\n cursor: grabbing;\n}\n\n.entry[data-selected] > .card {\n outline: 2px solid var(--wzl-accent);\n outline-offset: -1px;\n}\n","import {\n type KeyboardEvent,\n type ReactNode,\n type RefCallback,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\nimport { dlog } from '../../dlog';\nimport { CloseIcon, LockIcon } from '../../icons';\nimport { type PressModifiers, useReorderDragList } from '../../useReorderDragList';\nimport { Button } from '../Button';\nimport { Disclosure } from '../Disclosure';\nimport { DragGhost } from '../DragGhost';\nimport { DragHandleGlyph } from '../DragHandleGlyph';\nimport rows from '../ItemList/ItemList.module.css';\nimport { PropertyGroup, PropertyList } from '../Properties';\nimport type { StanceProps } from '../stance';\nimport s from './LayerList.module.css';\n\n/** One layer. With a body (see `renderBody`) it draws as a card that folds its\n * body away; without one, as a one-line row. Either can hold child layers. */\nexport interface LayerListItem {\n id: string;\n /** The layer's name. Also what its controls are labeled by. */\n label: string;\n /** Shown in the header in place of `label` — a select that switches the\n * layer's kind, say. */\n title?: ReactNode;\n /** Fixed-size content before the label: a color swatch, a kind icon. */\n leading?: ReactNode;\n /** Drawn in the drag handle in place of the grip — an ordinal. */\n badge?: ReactNode;\n /** Which of its list this layer is: an index into the theme's tone list, or\n * a color. A card draws its edge and handle in it and recolors its controls. */\n tone?: StanceProps['tone'];\n /** Cannot be dragged, hidden or removed, walls off drops on either side, and\n * selects alone rather than joining a multi-selection. */\n locked?: boolean;\n /** Shown or not, when the list has `onVisibilityChange`. Default true. */\n visible?: boolean;\n children?: LayerListItem[];\n /** Folds the body and children away until the user opens them.\n * `collapsedIds` overrides it. */\n defaultCollapsed?: boolean;\n}\n\n/**\n * A reorder: `ids`, siblings under `parentId` (`null` at the top level), now\n * sit as one block in list order starting at `index` of the parent's children.\n * A drag never moves a layer to another parent.\n */\nexport interface LayerMove {\n ids: string[];\n parentId: string | null;\n index: number;\n}\n\n/** Props for `<LayerList>`. */\nexport interface LayerListProps {\n items: LayerListItem[];\n /** Omit for a list the user cannot reorder. */\n onReorder?: (move: LayerMove) => void;\n /** Giving both makes rows selectable: a click selects one, shift-click adds\n * or removes one, and dragging a selected row drags its selected siblings. */\n selectedIds?: readonly string[];\n onSelect?: (ids: string[]) => void;\n /** Adds a visibility checkbox to every unlocked layer. */\n onVisibilityChange?: (id: string, visible: boolean) => void;\n /** Adds a ✕ to every unlocked layer. */\n onRemove?: (id: string) => void;\n /** A layer's controls. Returning `null` leaves that layer a one-line row. */\n renderBody?: (item: LayerListItem) => ReactNode;\n /** Ids whose body and children are folded away. Giving it makes folding\n * controlled; `onCollapsedChange` fires either way. */\n collapsedIds?: readonly string[];\n onCollapsedChange?: (ids: string[]) => void;\n /** Heading above the list. */\n title?: string;\n /** Names the list for assistive tech when it has no `title`. */\n label?: string;\n /** Kinds the user can add, as buttons beside the title. Shown only with `onAdd`. */\n addKinds?: string[];\n onAdd?: (kind: string) => void;\n /** Shown in place of the list when `items` is empty. */\n empty?: ReactNode;\n /** Appended to the root's class list — the module's own names are hashed. */\n className?: string;\n}\n\n/** Returns `items` with `move` applied, for a consumer that holds the tree as state. */\nexport function moveLayers(items: LayerListItem[], move: LayerMove): LayerListItem[] {\n if (move.parentId === null) return reorder(items, move);\n return items.map((item) => {\n if (item.id === move.parentId) return { ...item, children: reorder(item.children ?? [], move) };\n return item.children ? { ...item, children: moveLayers(item.children, move) } : item;\n });\n}\n\nfunction reorder(siblings: LayerListItem[], { ids, index }: LayerMove): LayerListItem[] {\n const moving = new Set(ids);\n const kept = siblings.filter((it) => !moving.has(it.id));\n const moved = siblings.filter((it) => moving.has(it.id));\n const at = Math.max(0, Math.min(kept.length, index));\n return [...kept.slice(0, at), ...moved, ...kept.slice(at)];\n}\n\nfunction collectCollapsed(\n items: LayerListItem[],\n isCollapsed: (item: LayerListItem) => boolean,\n into = new Set<string>(),\n): Set<string> {\n for (const item of items) {\n if (isCollapsed(item)) into.add(item.id);\n if (item.children) collectCollapsed(item.children, isCollapsed, into);\n }\n return into;\n}\n\nfunction anyNested(items: LayerListItem[]): boolean {\n return items.some((it) => (it.children?.length ?? 0) > 0);\n}\n\n/** What every level of the tree reads from the list. */\ninterface Shared {\n props: LayerListProps;\n collapsed: ReadonlySet<string>;\n toggle: (id: string) => void;\n press: (item: LayerListItem, mods: PressModifiers) => void;\n uid: string;\n /** Some layer has children, so every row keeps a twisty column. */\n nested: boolean;\n /** Each layer's body, rendered once; a row where it is empty. */\n bodies: ReadonlyMap<string, ReactNode>;\n /** Per sibling group, keyed by parent: move a row one place, as its drag would. */\n nudgers: Map<string | null, (id: string, delta: -1 | 1) => boolean>;\n keys: RowKeys;\n}\n\n/** The roving focus over the rows: one row is in the tab order at a time. */\ninterface RowKeys {\n tabStop: string | undefined;\n register: (id: string) => (el: HTMLElement | null) => void;\n onKeyDown: (item: LayerListItem, parentId: string | null) => (e: KeyboardEvent<HTMLElement>) => void;\n onFocus: (id: string) => void;\n}\n\ninterface VisibleRow {\n item: LayerListItem;\n parentId: string | null;\n level: number;\n}\n\nfunction hasBody(body: ReactNode): boolean {\n return body !== null && body !== undefined && body !== false;\n}\n\n/**\n * An ordered, drag-reorderable list of layers — a document's objects, a lab's\n * canvases, a fill's effects. Each layer is a one-line row or, with a body, a\n * card whose controls fold away; either can nest child layers. Selection,\n * visibility, removal and an add palette are each on when their handler is given.\n */\nexport function LayerList(props: LayerListProps) {\n const { items, collapsedIds, onCollapsedChange, title, addKinds, onAdd, empty, className } = props;\n // Only the user's own toggles are state; everything else reads the layer's\n // `defaultCollapsed`, so a layer added later starts the way it asks to.\n const [toggled, setToggled] = useState<ReadonlyMap<string, boolean>>(new Map());\n const collapsed = collapsedIds\n ? new Set(collapsedIds)\n : collectCollapsed(items, (item) => toggled.get(item.id) ?? item.defaultCollapsed === true);\n const toggle = (id: string) => {\n const next = new Set(collapsed);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n if (!collapsedIds) setToggled((prev) => new Map(prev).set(id, next.has(id)));\n onCollapsedChange?.([...next]);\n };\n\n // The press arrives after the drag session ends, which can be after a render\n // the closure did not see, so it reads the selection from here.\n const live = useRef(props);\n live.current = props;\n const press = (item: LayerListItem, mods: PressModifiers) => {\n const { selectedIds = [], onSelect } = live.current;\n if (!onSelect) return;\n anchor.current = item.id;\n if (item.locked || !mods.shiftKey) {\n onSelect([item.id]);\n return;\n }\n // A leftover locked selection does not carry into a multi-selection.\n const locked = new Set(lockedIds(live.current.items));\n const kept = selectedIds.filter((id) => !locked.has(id));\n onSelect(kept.includes(item.id) ? kept.filter((id) => id !== item.id) : [...kept, item.id]);\n };\n\n const bodies = new Map<string, ReactNode>();\n const collectBodies = (list: LayerListItem[]) => {\n for (const it of list) {\n bodies.set(it.id, props.renderBody ? props.renderBody(it) : null);\n if (it.children) collectBodies(it.children);\n }\n };\n collectBodies(items);\n\n // The rows the arrow keys walk: open branches only, and not cards, whose\n // heads are their own controls.\n const visible: VisibleRow[] = [];\n const walk = (list: LayerListItem[], parentId: string | null, level: number) => {\n for (const item of list) {\n if (!hasBody(bodies.get(item.id))) visible.push({ item, parentId, level });\n if (item.children?.length && !collapsed.has(item.id)) walk(item.children, item.id, level + 1);\n }\n };\n walk(items, null, 1);\n\n const rowEls = useRef(new Map<string, HTMLElement>());\n const [focusId, setFocusId] = useState<string | null>(null);\n const pendingFocus = useRef<string | null>(null);\n const anchor = useRef<string | null>(null);\n const nudgers = useRef(new Map<string | null, (id: string, delta: -1 | 1) => boolean>()).current;\n useLayoutEffect(() => {\n const id = pendingFocus.current;\n if (id === null) return;\n pendingFocus.current = null;\n const el = rowEls.current.get(id);\n if (el && document.activeElement !== el) el.focus();\n });\n\n const selectedIds = props.selectedIds ?? [];\n const tabStop = visible.some((v) => v.item.id === focusId)\n ? (focusId ?? undefined)\n : (visible.find((v) => selectedIds.includes(v.item.id)) ?? visible[0])?.item.id;\n\n const focusRow = (id: string) => {\n pendingFocus.current = id;\n setFocusId(id);\n };\n // Locked rows never join a multi-selection, so a range passes over them.\n const selectRange = (to: string) => {\n const from = anchor.current ?? to;\n const a = visible.findIndex((v) => v.item.id === from);\n const b = visible.findIndex((v) => v.item.id === to);\n if (a < 0 || b < 0) return;\n const range = visible\n .slice(Math.min(a, b), Math.max(a, b) + 1)\n .filter((v) => !v.item.locked)\n .map((v) => v.item.id);\n if (range.length > 0) live.current.onSelect?.(range);\n };\n\n const keys: RowKeys = {\n tabStop,\n register: (id) => (el) => {\n if (el) rowEls.current.set(id, el);\n else rowEls.current.delete(id);\n },\n onFocus: (id) => {\n setFocusId(id);\n anchor.current ??= id;\n },\n onKeyDown: (item, parentId) => (e) => {\n // Keys on a control inside the row are the control's.\n if (e.target !== e.currentTarget) return;\n const at = visible.findIndex((v) => v.item.id === item.id);\n const go = (index: number) => {\n const next = visible[Math.max(0, Math.min(visible.length - 1, index))];\n if (!next) return;\n focusRow(next.item.id);\n if (e.shiftKey && props.onSelect) selectRange(next.item.id);\n else anchor.current = next.item.id;\n };\n const branch = (item.children?.length ?? 0) > 0;\n const open = branch && !collapsed.has(item.id);\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowUp': {\n const delta = e.key === 'ArrowDown' ? 1 : -1;\n if (e.altKey) {\n if (nudgers.get(parentId)?.(item.id, delta)) pendingFocus.current = item.id;\n } else {\n go(at + delta);\n }\n break;\n }\n case 'Home':\n go(0);\n break;\n case 'End':\n go(visible.length - 1);\n break;\n case 'ArrowRight':\n if (branch && !open) toggle(item.id);\n else if (open) go(at + 1);\n else return;\n break;\n case 'ArrowLeft':\n if (open) toggle(item.id);\n else if (parentId !== null) go(visible.findIndex((v) => v.item.id === parentId));\n else return;\n break;\n case 'Enter':\n case ' ':\n press(item, { shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey });\n break;\n default:\n return;\n }\n e.preventDefault();\n },\n };\n\n const uid = useId();\n const shared: Shared = { props, collapsed, toggle, press, uid, nested: anyNested(items), bodies, nudgers, keys };\n const palette = onAdd ? (addKinds ?? []) : [];\n const cls = className ? `${s.root} ${className}` : s.root;\n\n return (\n <div className={cls}>\n {(title !== undefined || palette.length > 0) && (\n <div className={s.head}>\n {title !== undefined && <h2 className={s.title}>{title}</h2>}\n <div className={s.palette}>\n {palette.map((kind) => (\n <button\n key={kind}\n type=\"button\"\n className={s.add}\n aria-label={`Add ${kind}`}\n onClick={() => {\n dlog('layer-list', 'onAdd', { kind });\n onAdd?.(kind);\n }}\n >\n {kind}\n </button>\n ))}\n </div>\n </div>\n )}\n {items.length === 0 ? (\n <div className={rows.empty}>\n {empty ?? (palette.length > 0 ? 'No layers — add one above.' : 'No layers.')}\n </div>\n ) : (\n <Level items={items} parentId={null} shared={shared} path=\"\" level={1} />\n )}\n </div>\n );\n}\n\nfunction lockedIds(items: LayerListItem[]): string[] {\n const out: string[] = [];\n for (const it of items) {\n if (it.locked) out.push(it.id);\n if (it.children) out.push(...lockedIds(it.children));\n }\n return out;\n}\n\ninterface LevelProps {\n items: LayerListItem[];\n parentId: string | null;\n shared: Shared;\n path: string;\n level: number;\n}\n\n/** One sibling group. Each has its own drag, so a drag stays under one parent. */\nfunction Level({ items, parentId, shared, path, level }: LevelProps) {\n const { props } = shared;\n const selected = props.selectedIds ?? [];\n const drag = useReorderDragList({\n // With no `onReorder` every row is a locked one to the drag, so a press\n // still reaches `onPress` and nothing ever engages.\n items: items.map((it) => ({ id: it.id, label: it.label, locked: it.locked || !props.onReorder })),\n selectedIds: items.filter((it) => selected.includes(it.id)).map((it) => it.id),\n onReorder: (ids, target) => {\n const moving = new Set(ids);\n const inOrder = items.filter((it) => moving.has(it.id)).map((it) => it.id);\n // The hook's index counts the dragged rows; a move's index does not.\n const before = items.slice(0, target).filter((it) => moving.has(it.id)).length;\n const move = { ids: inOrder, parentId, index: target - before };\n dlog('layer-list', 'onReorder', move);\n props.onReorder?.(move);\n },\n onPress: (id, mods) => {\n const item = items.find((it) => it.id === id);\n if (item) shared.press(item, mods);\n },\n });\n shared.nudgers.set(parentId, (id, delta) =>\n drag.nudge(id, items.findIndex((it) => it.id === id), delta),\n );\n const [el, setEl] = useState<HTMLDivElement | null>(null);\n const bind = (node: HTMLDivElement | null) => {\n setEl(node);\n (drag.containerProps.ref as RefCallback<HTMLDivElement>)(node);\n };\n\n const bodies = items.map((it) => shared.bodies.get(it.id));\n const cards = bodies.some(hasBody);\n const { draggedIds, targetIndex, ghost } = drag.state;\n\n const entry = (item: LayerListItem, i: number, ghosted: boolean) => {\n const body = bodies[i];\n const kids = item.children ?? [];\n const open = !shared.collapsed.has(item.id);\n const childId = `${shared.uid}${path}-${i}`;\n const onPointerDown = ghosted ? undefined : drag.rowProps(item.id, i).onPointerDown;\n const isSelected = selected.includes(item.id);\n const cls = [s.entry];\n if (!ghosted) {\n if (draggedIds?.includes(item.id)) cls.push(s.dragging);\n if (targetIndex === i && !draggedIds?.includes(item.id)) cls.push(s.dropBefore);\n if (targetIndex === items.length && i === items.length - 1) cls.push(s.dropAfter);\n }\n const controls = <Controls item={item} shared={shared} />;\n const head =\n hasBody(body) ? (\n <PropertyGroup\n className={s.card}\n title={item.title ?? item.label}\n {...(item.tone === undefined ? {} : { tone: item.tone })}\n collapsed={ghosted || !open}\n onCollapsedChange={() => shared.toggle(item.id)}\n leading={\n <Handle\n item={item}\n reorder={props.onReorder !== undefined}\n onPointerDown={onPointerDown}\n onNudge={(delta) => shared.nudgers.get(parentId)?.(item.id, delta)}\n />\n }\n actions={ghosted ? undefined : controls}\n >\n {ghosted ? null : body}\n </PropertyGroup>\n ) : (\n <div\n className={[rows.row, s.row, isSelected ? rows.selected : ''].filter(Boolean).join(' ')}\n data-selected={isSelected ? 'true' : undefined}\n data-locked={item.locked ? 'true' : undefined}\n onPointerDown={onPointerDown}\n {...(ghosted\n ? {}\n : {\n role: 'row',\n 'aria-level': level,\n ...(kids.length > 0 ? { 'aria-expanded': open } : {}),\n ...(props.onSelect ? { 'aria-selected': isSelected } : {}),\n tabIndex: shared.keys.tabStop === item.id ? 0 : -1,\n ref: shared.keys.register(item.id),\n onKeyDown: shared.keys.onKeyDown(item, parentId),\n onFocus: () => shared.keys.onFocus(item.id),\n })}\n >\n {shared.nested &&\n (kids.length > 0 ? (\n <span className={s.stop} onPointerDown={stop}>\n <Disclosure\n open={open}\n onToggle={() => shared.toggle(item.id)}\n label={`${item.label} sublayers`}\n controls={childId}\n />\n </span>\n ) : (\n <span className={s.twistyGap} />\n ))}\n <Grip item={item} reorder={props.onReorder !== undefined} />\n {props.onVisibilityChange &&\n (item.locked ? (\n <span className={s.check} />\n ) : (\n <input\n className={s.check}\n type=\"checkbox\"\n checked={item.visible !== false}\n onPointerDown={stop}\n onChange={(e) => props.onVisibilityChange?.(item.id, e.target.checked)}\n aria-label={`Show ${item.label}`}\n />\n ))}\n {item.leading}\n <span className={rows.label}>{item.title ?? item.label}</span>\n {ghosted ? null : <Remove item={item} shared={shared} />}\n </div>\n );\n return (\n <div key={item.id} role=\"none\" className={cls.join(' ')} data-selected={isSelected ? 'true' : undefined}>\n {head}\n {!ghosted && kids.length > 0 && open && (\n <div id={childId} role=\"rowgroup\" className={s.children}>\n <Level items={kids} parentId={item.id} shared={shared} path={`${path}-${i}`} level={level + 1} />\n </div>\n )}\n </div>\n );\n };\n\n return (\n <>\n {cards ? (\n <PropertyList ref={bind} className={`${s.level} ${s.cards}`}>\n {items.map((it, i) => entry(it, i, false))}\n </PropertyList>\n ) : (\n <div\n ref={bind}\n className={s.level}\n {...(parentId === null\n ? {\n role: 'treegrid',\n 'aria-label': props.label ?? props.title ?? 'Layers',\n ...(props.onSelect ? { 'aria-multiselectable': true } : {}),\n }\n : { role: 'none' })}\n >\n {items.map((it, i) => entry(it, i, false))}\n </div>\n )}\n {ghost && el ? (\n <DragGhost at={ghost} from={el}>\n {items.map((it, i) => (ghost.ids.includes(it.id) ? entry(it, i, true) : null))}\n </DragGhost>\n ) : null}\n </>\n );\n}\n\n// Keeps a press on a control inside a row from also starting the row's press.\nconst stop = (e: { stopPropagation(): void }) => e.stopPropagation();\n\n/** A row's grip: decoration, since the whole row takes the drag. */\nfunction Grip({ item, reorder }: { item: LayerListItem; reorder: boolean }) {\n if (item.locked) {\n return (\n <span className={s.lock} role=\"img\" aria-label=\"Locked\">\n <LockIcon size={12} />\n </span>\n );\n }\n if (!reorder && item.badge === undefined) return null;\n return (\n <span className={s.grip} aria-hidden=\"true\">\n {item.badge ?? <DragHandleGlyph size={13} />}\n </span>\n );\n}\n\n/** A card's handle: the drag target, since the rest of its head folds it, and\n * Alt+Up/Down on it moves the card. */\nfunction Handle({\n item,\n reorder,\n onPointerDown,\n onNudge,\n}: {\n item: LayerListItem;\n reorder: boolean;\n onPointerDown: ((e: React.PointerEvent) => void) | undefined;\n onNudge: (delta: -1 | 1) => void;\n}) {\n if (item.locked) return <Grip item={item} reorder={reorder} />;\n if (!reorder && item.badge === undefined) return undefined;\n return (\n <button\n type=\"button\"\n className={s.handle}\n aria-label={`Drag to reorder ${item.label}`}\n aria-keyshortcuts=\"Alt+ArrowUp Alt+ArrowDown\"\n onPointerDown={onPointerDown}\n onKeyDown={(e) => {\n if (!e.altKey || (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')) return;\n e.preventDefault();\n onNudge(e.key === 'ArrowUp' ? -1 : 1);\n }}\n >\n {item.badge ?? <DragHandleGlyph />}\n </button>\n );\n}\n\nfunction Controls({ item, shared }: { item: LayerListItem; shared: Shared }) {\n const { onVisibilityChange } = shared.props;\n return (\n <>\n {onVisibilityChange && !item.locked && (\n <input\n className={s.check}\n type=\"checkbox\"\n checked={item.visible !== false}\n onClick={stop}\n onChange={(e) => onVisibilityChange(item.id, e.target.checked)}\n aria-label={`Show ${item.label}`}\n />\n )}\n <Remove item={item} shared={shared} />\n </>\n );\n}\n\nfunction Remove({ item, shared }: { item: LayerListItem; shared: Shared }) {\n const { onRemove } = shared.props;\n if (!onRemove || item.locked) return null;\n return (\n <span className={s.stop} onPointerDown={stop}>\n <Button variant=\"ghost\" size=\"sm\" iconOnly ariaLabel={`Remove ${item.label}`} onClick={() => onRemove(item.id)}>\n <CloseIcon size={14} />\n </Button>\n </span>\n );\n}\n","import { asNodeId, createMoveToIndexOp, dispatchApplyBatch } from '@weasel-js/core';\nimport type { NodeId, Scene, SceneNode, SelectionApi } from '@weasel-js/core';\nimport type { LayerListItem, LayerListProps } from './LayerList';\n\n/** Arguments for {@link useSceneLayerList}. */\nexport interface UseSceneLayerListArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n selection: SelectionApi;\n /** What the reorder op is dispatched against — the adapter `<SceneCanvas>` uses. */\n adapter: object;\n /** A row's content for a node: its label, a leading swatch or icon, whether it is locked. */\n itemFor: (node: SceneNode<TData, TLayer, TPose>) => Omit<LayerListItem, 'id' | 'children'>;\n /** Label of the reorder's history entry. Default `'Reorder'`. */\n label?: string;\n}\n\n/**\n * The props that put a scene in a `<LayerList>`: a row per node, a container's\n * children nested under it, the front-most at the top, selection read from and\n * written to `selection`, and a drag dispatched as a `MoveToIndexOp`.\n */\nexport function useSceneLayerList<TData, TLayer extends string, TPose>(\n args: UseSceneLayerListArgs<TData, TLayer, TPose>,\n): Required<Pick<LayerListProps, 'items' | 'selectedIds' | 'onSelect' | 'onReorder'>> {\n const { scene, selection, adapter, itemFor, label = 'Reorder' } = args;\n\n // A scene orders children back to front and the list front to back. Rebuilt\n // every render: the scene keeps its identity across a reorder, so a memo keyed\n // on it would serve the order from before.\n const build = (ids: readonly NodeId[]): LayerListItem[] =>\n [...ids].reverse().flatMap((id) => {\n const node = scene.get(id);\n if (!node) return [];\n const kids = scene.childrenOf(id);\n return [{ ...itemFor(node), id, ...(kids.length > 0 ? { children: build(kids) } : {}) }];\n });\n\n return {\n items: build(scene.roots),\n selectedIds: selection.current.map(String),\n onSelect: (ids) => selection.set(ids.map(asNodeId)),\n onReorder: ({ ids, parentId, index }) => {\n const siblings = parentId === null ? scene.roots : scene.childrenOf(asNodeId(parentId));\n // `index` counts from the front among the siblings the drag left behind.\n const back = siblings.length - ids.length - index;\n dispatchApplyBatch(adapter, [createMoveToIndexOp({ ids, parentId, index: Math.max(0, back) })], label);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ListEditor-C6zLzCsJ.js","names":[],"sources":["../../src/components/ListEditor/ListEditor.module.css","../../src/components/ListEditor/ListEditor.tsx"],"sourcesContent":[".editor {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n min-width: min(28rem, calc(100vw - 64px));\n}\n\n.list {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.entry {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-2);\n}\n\n.input {\n flex: 1 1 auto;\n min-width: 0;\n height: var(--wzl-field-h, var(--wzl-control-h));\n padding: 0 var(--wzl-field-pad-x);\n background: var(--wzl-surface-sunken);\n color: var(--wzl-fg);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n}\n\n.input:focus {\n outline: none;\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.empty {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n font-style: italic;\n}\n\n.actions {\n display: flex;\n}\n","import { useEffect, useRef, useState, type KeyboardEvent } from 'react';\nimport { Button } from '../Button';\nimport s from './ListEditor.module.css';\n\n/** Props for {@link ListEditor}. */\nexport interface ListEditorProps {\n value: readonly string[];\n /** Every edit, add and removal, with the whole list. Entries are written as\n * typed — an empty one stays until it is removed. */\n onChange: (next: string[]) => void;\n /** Shown in an empty entry. */\n placeholder?: string;\n /** Shown in place of the entries when there are none. */\n empty?: string;\n /** Text of the add button. */\n addLabel?: string;\n /** Names the list for assistive tech; each entry is named from it. */\n 'aria-label'?: string;\n className?: string;\n}\n\n/**\n * An editable list of strings: one field per entry, a remove button beside\n * each, and an add button under them. Enter in an entry adds one after it;\n * Backspace in an empty one removes it.\n */\nexport function ListEditor({\n value,\n onChange,\n placeholder,\n empty = 'None',\n addLabel = 'Add',\n 'aria-label': ariaLabel,\n className,\n}: ListEditorProps) {\n const inputs = useRef<(HTMLInputElement | null)[]>([]);\n const [focus, setFocus] = useState<number | null>(null);\n\n useEffect(() => {\n if (focus === null) return;\n inputs.current[focus]?.focus();\n setFocus(null);\n }, [focus]);\n\n const insert = (at: number): void => {\n onChange([...value.slice(0, at), '', ...value.slice(at)]);\n setFocus(at);\n };\n const remove = (at: number): void => {\n onChange(value.filter((_, i) => i !== at));\n if (value.length > 1) setFocus(Math.max(0, at - 1));\n };\n const onKeyDown = (e: KeyboardEvent<HTMLInputElement>, at: number): void => {\n if (e.key === 'Enter') {\n e.preventDefault();\n insert(at + 1);\n } else if (e.key === 'Backspace' && value[at] === '') {\n e.preventDefault();\n remove(at);\n }\n };\n const name = (i: number): string => `${ariaLabel ?? 'Entry'} ${i + 1}`;\n\n return (\n <div className={[s.editor, className].filter(Boolean).join(' ')} role=\"group\" aria-label={ariaLabel}>\n {value.length === 0 ? (\n <div className={s.empty}>{empty}</div>\n ) : (\n <ul className={s.list}>\n {value.map((entry, i) => (\n // Entries have no identity beyond their position, and two may be equal.\n <li key={i} className={s.entry}>\n <input\n ref={(el) => {\n inputs.current[i] = el;\n }}\n type=\"text\"\n className={s.input}\n aria-label={name(i)}\n value={entry}\n placeholder={placeholder}\n onChange={(e) => onChange(value.map((v, j) => (j === i ? e.target.value : v)))}\n onKeyDown={(e) => onKeyDown(e, i)}\n />\n <Button\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n ariaLabel={`Remove ${name(i)}`}\n onClick={() => remove(i)}\n >\n ×\n </Button>\n </li>\n ))}\n </ul>\n )}\n <div className={s.actions}>\n <Button size=\"sm\" onClick={() => insert(value.length)}>\n {addLabel}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AC0BA,SAAgB,EAAW,EACzB,UACA,aACA,gBACA,WAAQ,QACR,cAAW,OACX,cAAc,GACd,gBACkB;CAClB,IAAM,IAAS,EAAoC,EAAE,CAAC,EAChD,CAAC,GAAO,KAAY,EAAwB,KAAK;AAEvD,SAAgB;AACV,QAAU,SACd,EAAO,QAAQ,IAAQ,OAAO,EAC9B,EAAS,KAAK;IACb,CAAC,EAAM,CAAC;CAEX,IAAM,KAAU,MAAqB;AAEnC,EADA,EAAS;GAAC,GAAG,EAAM,MAAM,GAAG,EAAG;GAAE;GAAI,GAAG,EAAM,MAAM,EAAG;GAAC,CAAC,EACzD,EAAS,EAAG;IAER,KAAU,MAAqB;AAEnC,EADA,EAAS,EAAM,QAAQ,GAAG,MAAM,MAAM,EAAG,CAAC,EACtC,EAAM,SAAS,KAAG,EAAS,KAAK,IAAI,GAAG,IAAK,EAAE,CAAC;IAE/C,KAAa,GAAoC,MAAqB;AAC1E,EAAI,EAAE,QAAQ,WACZ,EAAE,gBAAgB,EAClB,EAAO,IAAK,EAAE,IACL,EAAE,QAAQ,eAAe,EAAM,OAAQ,OAChD,EAAE,gBAAgB,EAClB,EAAO,EAAG;IAGR,KAAQ,MAAsB,GAAG,KAAa,QAAQ,GAAG,IAAI;AAEnE,QACE,kBAAC,OAAD;EAAK,WAAW,CAAC,EAAE,QAAQ,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;EAAE,MAAK;EAAQ,cAAY;YAA1F,CACG,EAAM,WAAW,IAChB,kBAAC,OAAD;GAAK,WAAW,EAAE;aAAQ;GAAY,CAAA,GAEtC,kBAAC,MAAD;GAAI,WAAW,EAAE;aACd,EAAM,KAAK,GAAO,MAEjB,kBAAC,MAAD;IAAY,WAAW,EAAE;cAAzB,CACE,kBAAC,SAAD;KACE,MAAM,MAAO;AACX,QAAO,QAAQ,KAAK;;KAEtB,MAAK;KACL,WAAW,EAAE;KACb,cAAY,EAAK,EAAE;KACnB,OAAO;KACM;KACb,WAAW,MAAM,EAAS,EAAM,KAAK,GAAG,MAAO,MAAM,IAAI,EAAE,OAAO,QAAQ,EAAG,CAAC;KAC9E,YAAY,MAAM,EAAU,GAAG,EAAE;KACjC,CAAA,EACF,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,UAAA;KACA,WAAW,UAAU,EAAK,EAAE;KAC5B,eAAe,EAAO,EAAE;eACzB;KAEQ,CAAA,CACN;MAtBI,EAsBJ,CACL;GACC,CAAA,EAEP,kBAAC,OAAD;GAAK,WAAW,EAAE;aAChB,kBAAC,GAAD;IAAQ,MAAK;IAAK,eAAe,EAAO,EAAM,OAAO;cAClD;IACM,CAAA;GACL,CAAA,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MenuButton-BcdhrNGh.js","names":[],"sources":["../../src/components/MenuButton/MenuButton.module.css","../../src/components/MenuButton/MenuButton.tsx"],"sourcesContent":[".trigger {\n display: inline-flex;\n flex: none;\n align-items: center;\n gap: var(--wzl-space-3);\n height: var(--wzl-control-h);\n padding: 0 var(--wzl-space-3) 0 var(--wzl-space-5);\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n line-height: 1;\n white-space: nowrap;\n cursor: pointer;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.trigger[data-focus-visible] {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.trigger[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.label {\n color: var(--wzl-fg-subtle);\n}\n\n.chevron {\n width: 10px;\n height: 10px;\n color: var(--wzl-fg-muted);\n flex: 0 0 auto;\n}\n\n.popover {\n min-width: var(--trigger-width);\n max-height: inherit;\n outline: none;\n}\n\n.menu {\n background: var(--wzl-surface-raised);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);\n padding: var(--wzl-space-2);\n max-height: 280px;\n overflow: auto;\n outline: none;\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n}\n\n.item {\n display: flex;\n align-items: center;\n padding: var(--wzl-space-2) var(--wzl-space-4);\n border-radius: var(--wzl-radius-sm);\n cursor: pointer;\n color: var(--wzl-fg);\n white-space: nowrap;\n outline: none;\n}\n\n.item[data-focused] {\n background: var(--wzl-surface-hover);\n}\n\n.item[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n","import type { ReactNode } from 'react';\nimport {\n Button as RACButton,\n Menu as RACMenu,\n MenuItem as RACMenuItem,\n MenuTrigger,\n Popover as RACPopover,\n} from 'react-aria-components';\nimport { useOverlayPortal, type OverlayPortalProps } from '../../overlays/portalHost';\nimport s from './MenuButton.module.css';\nimport { TriggerTooltip, segmentTooltipContent, type SegmentTooltipFields } from '../segmentTooltip';\n\n/** One row in a {@link MenuButton}'s list. */\nexport type MenuButtonItem<T extends string = string> = {\n value: T;\n label: ReactNode;\n isDisabled?: boolean;\n /** Plain-text form of `label`, for type-to-select and screen readers. A\n * string label supplies this itself. */\n textValue?: string;\n};\n\n/** Props for {@link MenuButton}. */\nexport type MenuButtonProps<T extends string = string> = {\n /** What the button says. It never changes to show a chosen row. */\n label: ReactNode;\n items: ReadonlyArray<MenuButtonItem<T>>;\n /** Fired with the value of the row chosen. */\n onAction: (value: T) => void;\n isDisabled?: boolean;\n className?: string;\n 'aria-label'?: string;\n} & SegmentTooltipFields & OverlayPortalProps;\n\n/**\n * A button that opens a list and acts on the row chosen — \"Add trial…\",\n * \"Load…\". Unlike a {@link Select} it holds no value, so it sizes to its own\n * label rather than to its widest row.\n *\n * `shortcut` adds a kit tooltip reading `Name (⌘N)`, named by `aria-label` or\n * a string `label`, as `Button` does; `tooltip` replaces that text.\n */\nexport function MenuButton<T extends string = string>({\n label,\n items,\n onAction,\n isDisabled,\n className,\n 'aria-label': ariaLabel,\n portalContainer,\n shortcut,\n tooltip,\n}: MenuButtonProps<T>) {\n const { anchor, portalProps } = useOverlayPortal(portalContainer);\n return (\n <MenuTrigger>\n {anchor}\n <TriggerTooltip\n content={segmentTooltipContent({ tooltip, shortcut, ariaLabel, label })}\n disabled={isDisabled}\n >\n <RACButton\n className={[s.trigger, className].filter(Boolean).join(' ')}\n isDisabled={isDisabled}\n aria-label={ariaLabel}\n >\n <span className={s.label}>{label}</span>\n <svg className={s.chevron} viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M2 4 L5 7 L8 4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n </RACButton>\n </TriggerTooltip>\n {/* `data-weasel-overlay`: see Select — the list renders in a portal,\n outside the subtree the trigger sits in. */}\n <RACPopover className={s.popover} data-weasel-overlay=\"\" {...portalProps}>\n <RACMenu className={s.menu} onAction={(key) => onAction(key as T)}>\n {items.map((item) => (\n <RACMenuItem\n key={item.value}\n id={item.value}\n className={s.item}\n isDisabled={item.isDisabled}\n textValue={item.textValue ?? (typeof item.label === 'string' ? item.label : undefined)}\n >\n {item.label}\n </RACMenuItem>\n ))}\n </RACMenu>\n </RACPopover>\n </MenuTrigger>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AC0CA,SAAgB,EAAsC,EACpD,UACA,UACA,aACA,eACA,cACA,cAAc,GACd,oBACA,aACA,cACqB;CACrB,IAAM,EAAE,WAAQ,mBAAgB,EAAiB,EAAgB;AACjE,QACE,kBAAC,GAAD,EAAA,UAAA;EACG;EACD,kBAAC,GAAD;GACE,SAAS,EAAsB;IAAE;IAAS;IAAU;IAAW;IAAO,CAAC;GACvE,UAAU;aAEV,kBAAC,GAAD;IACE,WAAW,CAAC,EAAE,SAAS,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;IAC/C;IACZ,cAAY;cAHd,CAKE,kBAAC,QAAD;KAAM,WAAW,EAAE;eAAQ;KAAa,CAAA,EACxC,kBAAC,OAAD;KAAK,WAAW,EAAE;KAAS,SAAQ;KAAY,eAAY;eACzD,kBAAC,QAAD;MAAM,GAAE;MAAiB,MAAK;MAAO,QAAO;MAAe,aAAY;MAAM,eAAc;MAAQ,gBAAe;MAAU,CAAA;KACxH,CAAA,CACI;;GACG,CAAA;EAGjB,kBAAC,GAAD;GAAY,WAAW,EAAE;GAAS,uBAAoB;GAAG,GAAI;aAC3D,kBAAC,GAAD;IAAS,WAAW,EAAE;IAAM,WAAW,MAAQ,EAAS,EAAS;cAC9D,EAAM,KAAK,MACV,kBAAC,GAAD;KAEE,IAAI,EAAK;KACT,WAAW,EAAE;KACb,YAAY,EAAK;KACjB,WAAW,EAAK,cAAc,OAAO,EAAK,SAAU,WAAW,EAAK,QAAQ,KAAA;eAE3E,EAAK;KACM,EAPP,EAAK,MAOE,CACd;IACM,CAAA;GACC,CAAA;EACD,EAAA,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MeshEditor-BOOIFWnz.js","names":[],"sources":["../../src/components/MeshEditor/MeshEditor.module.css","../../src/components/MeshEditor/MeshEditor.tsx"],"sourcesContent":[".root {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n}\n\n.patch {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-4);\n}\n\n.preview {\n inline-size: 34px;\n block-size: 34px;\n flex: 0 0 auto;\n border-radius: var(--wzl-radius-sm);\n box-shadow: inset 0 0 0 1px var(--wzl-border);\n}\n\n.corners {\n display: grid;\n grid-template-columns: repeat(2, auto);\n gap: var(--wzl-space-2) var(--wzl-space-3);\n min-inline-size: 0;\n}\n\n.corner {\n min-inline-size: 0;\n}\n","import { type ReactElement } from 'react';\nimport {\n asPaint,\n isMeshGradientFill,\n type ColorSpace,\n type FillStyle,\n type MeshGradientFill,\n} from '@weasel-js/core';\nimport { ColorField } from '../ColorField';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport { paintPreviewCss } from '../../paintPreview';\nimport s from './MeshEditor.module.css';\n\nconst SPACES: readonly ToggleBarItem<ColorSpace>[] = [\n { value: 'rgb', label: 'sRGB' },\n { value: 'oklab', label: 'OKLab' },\n { value: 'oklch', label: 'OKLCh' },\n];\n\n/** Props for {@link MeshEditor}. `onInput` fires throughout a gesture and\n * `onChange` once at its end. */\nexport interface MeshEditorProps {\n value: MeshGradientFill;\n /** Emitted as a `FillStyle`, which is what every paint control passes\n * around — `FillStyle`'s discriminant stays closed over the built-in\n * members, so a registered kind's own shape only travels through it. */\n onInput?: (next: FillStyle) => void;\n onChange: (next: FillStyle) => void;\n /** Show the sRGB / OKLab / OKLCh switch. Default true. */\n spaceSwitch?: boolean;\n className?: string;\n}\n\n/**\n * Editor for a mesh gradient's colors.\n *\n * Geometry is absent for the same reason it is absent from `GradientEditor`:\n * a patch's twelve control points are a thing to drag on the artwork, not a\n * column of numbers in a panel. What is left is what a panel is good at — the\n * color at each corner, and the space the four of them blend through.\n *\n * Corners are numbered in the patch's own walk order, which is the order the\n * paint stores them in, so a corner's number is stable while it is dragged.\n */\nexport function MeshEditor(props: MeshEditorProps): ReactElement {\n const { value, onInput, onChange, spaceSwitch = true, className } = props;\n const space = value.interpolate ?? 'rgb';\n\n const withCorner = (patchIndex: number, corner: number, color: string): FillStyle => asPaint({\n ...value,\n patches: value.patches.map((patch, i) => {\n if (i !== patchIndex) return patch;\n const [a, b, c, d] = patch.colors;\n const next: [string, string, string, string] = [a, b, c, d];\n next[corner] = color;\n return { ...patch, colors: next };\n }),\n });\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n {value.patches.map((patch, patchIndex) => (\n // A patch has no id; its position in the list is its identity, the way\n // a gradient stop's is.\n <div key={patchIndex} className={s.patch}>\n <span\n className={s.preview}\n aria-hidden=\"true\"\n style={{ background: previewOf(value, patchIndex) }}\n />\n <div className={s.corners}>\n {patch.colors.map((color, corner) => (\n <ColorField\n key={corner}\n value={color}\n alpha\n aria-label={`Patch ${patchIndex + 1} corner ${corner + 1}`}\n className={s.corner}\n onInput={(next) => onInput?.(withCorner(patchIndex, corner, next))}\n onChange={(next) => onChange(withCorner(patchIndex, corner, next))}\n />\n ))}\n </div>\n </div>\n ))}\n\n {spaceSwitch && (\n <ToggleBar<ColorSpace>\n items={SPACES}\n value={space}\n size=\"sm\"\n ariaLabel=\"Blend space\"\n onChange={(next) => next && onChange(asPaint({ ...value, interpolate: next }))}\n />\n )}\n </div>\n );\n}\n\n/** One patch as a swatch: the paint with the other patches taken out, so a\n * multi-patch mesh shows which row belongs to which part of it. */\nfunction previewOf(value: MeshGradientFill, patchIndex: number): string | undefined {\n return paintPreviewCss(\n { ...value, patches: [value.patches[patchIndex]] } as unknown as FillStyle,\n );\n}\n\n/** Narrowing for a consumer rendering this behind a kind switch. */\nexport function isMeshPaint(fill: FillStyle | null | undefined): fill is FillStyle & MeshGradientFill {\n return isMeshGradientFill(fill);\n}\n"],"mappings":";;;;;;;;;;;;GCaM,IAA+C;CACnD;EAAE,OAAO;EAAO,OAAO;EAAQ;CAC/B;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAS,OAAO;EAAS;CACnC;AA2BD,SAAgB,EAAW,GAAsC;CAC/D,IAAM,EAAE,UAAO,YAAS,aAAU,iBAAc,IAAM,iBAAc,GAC9D,IAAQ,EAAM,eAAe,OAE7B,KAAc,GAAoB,GAAgB,MAA6B,EAAQ;EAC3F,GAAG;EACH,SAAS,EAAM,QAAQ,KAAK,GAAO,MAAM;AACvC,OAAI,MAAM,EAAY,QAAO;GAC7B,IAAM,CAAC,GAAG,GAAG,GAAG,KAAK,EAAM,QACrB,IAAyC;IAAC;IAAG;IAAG;IAAG;IAAE;AAE3D,UADA,EAAK,KAAU,GACR;IAAE,GAAG;IAAO,QAAQ;IAAM;IACjC;EACH,CAAC;AAEF,QACE,kBAAC,OAAD;EAAK,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA7D,CACG,EAAM,QAAQ,KAAK,GAAO,MAGzB,kBAAC,OAAD;GAAsB,WAAW,EAAE;aAAnC,CACE,kBAAC,QAAD;IACE,WAAW,EAAE;IACb,eAAY;IACZ,OAAO,EAAE,YAAY,EAAU,GAAO,EAAW,EAAE;IACnD,CAAA,EACF,kBAAC,OAAD;IAAK,WAAW,EAAE;cACf,EAAM,OAAO,KAAK,GAAO,MACxB,kBAAC,GAAD;KAEE,OAAO;KACP,OAAA;KACA,cAAY,SAAS,IAAa,EAAE,UAAU,IAAS;KACvD,WAAW,EAAE;KACb,UAAU,MAAS,IAAU,EAAW,GAAY,GAAQ,EAAK,CAAC;KAClE,WAAW,MAAS,EAAS,EAAW,GAAY,GAAQ,EAAK,CAAC;KAClE,EAPK,EAOL,CACF;IACE,CAAA,CACF;KAnBI,EAmBJ,CACN,EAED,KACC,kBAAC,GAAD;GACE,OAAO;GACP,OAAO;GACP,MAAK;GACL,WAAU;GACV,WAAW,MAAS,KAAQ,EAAS,EAAQ;IAAE,GAAG;IAAO,aAAa;IAAM,CAAC,CAAC;GAC9E,CAAA,CAEA;;;AAMV,SAAS,EAAU,GAAyB,GAAwC;AAClF,QAAO,EACL;EAAE,GAAG;EAAO,SAAS,CAAC,EAAM,QAAQ,GAAY;EAAE,CACnD;;AAIH,SAAgB,EAAY,GAA0E;AACpG,QAAO,EAAmB,EAAK"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NumberField-C1Y7zP-a.js","names":[],"sources":["../../src/components/NumberField/NumberField.module.css","../../src/components/NumberField/NumberField.tsx","../../src/components/NumberField/UnitField.tsx"],"sourcesContent":[".field {\n display: flex;\n flex-direction: column;\n width: 100%;\n min-width: 0;\n gap: var(--wzl-space-2);\n}\n\n/* `orientation='row'`: the label keeps its own width and the field takes the\n rest; description and error wrap onto a line of their own. A `fit` field\n keeps its own width and centers on the label instead. */\n.field.row {\n flex-direction: row;\n align-items: center;\n flex-wrap: wrap;\n column-gap: var(--wzl-space-4);\n row-gap: var(--wzl-space-2);\n}\n.field.row .label { flex: 0 0 auto; }\n.field.row:not(.fit) .frame { flex: 1 1 0; min-width: 0; }\n.field.row.fit { align-items: center; }\n.field.row .below { flex-basis: 100%; }\n\n.frame {\n display: flex;\n align-items: stretch;\n height: var(--wzl-field-h, var(--wzl-control-h));\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n line-height: 1;\n overflow: hidden;\n transition: border-color var(--wzl-motion-fast), box-shadow var(--wzl-motion-fast);\n}\n\n.frame:focus-within {\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.field[data-invalid] .frame {\n border-color: var(--wzl-danger);\n}\n\n.field[data-disabled] .frame {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.frame input {\n flex: 1;\n min-width: 0;\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font: inherit;\n padding: 0 var(--wzl-field-pad-x);\n text-align: right;\n -moz-appearance: textfield;\n}\n\n.frame input::-webkit-outer-spin-button,\n.frame input::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n}\n\n.steppers {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--wzl-border);\n}\n\n.stepper {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--wzl-control-h-xs);\n flex: 1;\n background: transparent;\n border: none;\n color: var(--wzl-fg-muted);\n cursor: pointer;\n padding: 0;\n font-size: var(--wzl-font-size-2xs);\n line-height: 1;\n}\n\n.stepper:not(:last-child) {\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.stepper[data-hovered]:not([data-disabled]) {\n background: var(--wzl-surface-hover);\n color: var(--wzl-fg);\n}\n\n.stepper[data-pressed]:not([data-disabled]) {\n background: var(--wzl-surface-pressed);\n}\n\n.stepper[data-disabled] {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n/* Mirrors the property rows' editable readout (`.readoutInput`), so a number\n sitting inside other chrome does not draw a second box around itself. */\n.ghost {\n height: auto;\n background: transparent;\n border-color: transparent;\n color: var(--wzl-accent-fg);\n font-weight: var(--wzl-font-weight-medium);\n}\n\n.ghost input {\n padding: 1px 0;\n}\n\n.ghost:hover {\n background: color-mix(in srgb, currentColor 8%, transparent);\n}\n\n.ghost:focus-within {\n background: var(--wzl-surface-sunken);\n border-color: var(--wzl-focus-ring);\n box-shadow: none;\n}\n\n/* `width='fit'` states the field's width in characters: left alone an `<input>`\n asks for its 20-character intrinsic width, wider than most rows it sits in. */\n.fit {\n width: auto;\n max-width: 100%;\n align-items: flex-start;\n}\n\n.fit .frame {\n width: fit-content;\n max-width: 100%;\n}\n\n.fit .frame input {\n flex: 0 0 auto;\n width: var(--wzl-number-field-width, 9ch);\n}\n","import { forwardRef, type ReactNode, type Ref } from 'react';\nimport {\n NumberField as RACNumberField,\n Label,\n Input as RACInput,\n Group,\n Button as RACButton,\n Text,\n FieldError,\n type NumberFieldProps as RACNumberFieldProps,\n type ValidationResult,\n} from 'react-aria-components';\nimport { fieldClasses, type FieldOrientation } from '../Field/Field';\nimport s from './NumberField.module.css';\n\n/** Props for {@link NumberField}, on top of React Aria's `NumberField` props. */\nexport type NumberFieldProps = Omit<RACNumberFieldProps, 'children' | 'className'> & {\n label?: ReactNode;\n /**\n * `'stacked'` (the default) puts the label above the field; `'row'` sets it\n * beside the field at its own width, with any description or error on a\n * line below. Same vocabulary as {@link Field}'s `orientation`.\n */\n orientation?: FieldOrientation;\n description?: ReactNode;\n errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);\n /** Hide the up/down stepper buttons. Defaults to false. */\n hideSteppers?: boolean;\n /** Render with no box until focused — the readout treatment the property\n * rows use, for a value that sits inside other chrome rather than in a form. */\n ghost?: boolean;\n /** Native input placeholder — e.g. `'Mixed'` for a multi-selection\n * editor with no shared value. */\n placeholder?: string;\n /**\n * `'fill'` (the default) takes the width of whatever row the field sits in.\n * `'fit'` sizes it to `--wzl-number-field-width` (`9ch` by default) plus its\n * own chrome, rather than to the input's 20-character intrinsic width.\n */\n width?: 'fill' | 'fit';\n className?: string;\n};\n\n/**\n * Numeric input with stepper buttons, wrapping React Aria's NumberField and\n * supplying the same label / description / errorMessage slots as `Input`.\n * Arrow keys and scroll step the value; parsing and formatting are React\n * Aria's.\n *\n * `ref` forwards to the underlying `<input>`.\n */\nexport const NumberField = forwardRef(function NumberField(\n props: NumberFieldProps,\n ref: Ref<HTMLInputElement>,\n) {\n const {\n label,\n orientation = 'stacked',\n description,\n errorMessage,\n hideSteppers,\n ghost,\n placeholder,\n width = 'fill',\n className,\n ...rest\n } = props;\n return (\n <RACNumberField\n {...rest}\n className={[\n s.field,\n width === 'fit' && s.fit,\n fieldClasses.root,\n orientation === 'row' && fieldClasses.row,\n orientation === 'row' && s.row,\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {label !== undefined && <Label className={`${fieldClasses.label} ${s.label}`}>{label}</Label>}\n <Group className={ghost ? `${s.frame} ${s.ghost}` : s.frame}>\n <RACInput ref={ref} placeholder={placeholder} />\n {!hideSteppers && (\n <div className={s.steppers}>\n <RACButton slot=\"increment\" className={s.stepper} aria-label=\"Increment\">▲</RACButton>\n <RACButton slot=\"decrement\" className={s.stepper} aria-label=\"Decrement\">▼</RACButton>\n </div>\n )}\n </Group>\n {description !== undefined && (\n <Text slot=\"description\" className={`${fieldClasses.hint} ${s.below}`}>\n {description}\n </Text>\n )}\n <FieldError className={`${fieldClasses.error} ${s.below}`}>{errorMessage}</FieldError>\n </RACNumberField>\n );\n});\n","import { useRef, useState } from 'react';\nimport { formatNumber, parseNumber, type UnitTable } from '../../format/number';\nimport { fieldClasses } from '../Field/Field';\nimport s from './NumberField.module.css';\n\n/** Props for {@link UnitField}. */\nexport interface UnitFieldProps {\n /** The value in the unit the field shows. NaN shows an empty field. */\n value: number;\n /** Fired with a value the field read and clamped, in the unit it shows. */\n onChange: (value: number) => void;\n minValue?: number;\n maxValue?: number;\n /** What an arrow key adds or takes away. Defaults to 1. */\n step?: number;\n /** Units a person may type, each mapped to the factor that turns a number\n * in it into the unit the field shows: `{ mm: 0.1, cm: 1 }`. */\n accepts?: Readonly<UnitTable>;\n placeholder?: string;\n /** Render with no box until focused, as {@link NumberField}'s `ghost` does. */\n ghost?: boolean;\n /** As {@link NumberField}'s `width`. */\n width?: 'fill' | 'fit';\n className?: string;\n 'aria-label'?: string;\n}\n\n/**\n * A number typed as text, so it can carry a unit: `12mm` in a field showing\n * centimeters commits `1.2`. Commits on blur or Enter, reverts on Escape or on\n * text it cannot read, clamps to its bounds, and steps with the arrow keys.\n * React Aria's `NumberField` refuses letters as they are typed, which is why\n * this is not that.\n */\nexport function UnitField({\n value,\n onChange,\n minValue,\n maxValue,\n step = 1,\n accepts,\n placeholder,\n ghost,\n width = 'fill',\n className,\n 'aria-label': ariaLabel,\n}: UnitFieldProps) {\n const [draft, setDraft] = useState<string | null>(null);\n const canceled = useRef(false);\n const text = Number.isFinite(value)\n ? formatNumber(value, { useGrouping: false, maximumFractionDigits: 20 })\n : '';\n const clamp = (n: number) =>\n Math.min(maxValue ?? Infinity, Math.max(minValue ?? -Infinity, n));\n const commit = (n: number) => {\n const next = clamp(n);\n if (next !== value) onChange(next);\n };\n\n return (\n <div\n className={[s.field, width === 'fit' && s.fit, fieldClasses.root, className]\n .filter(Boolean)\n .join(' ')}\n >\n <div className={ghost ? `${s.frame} ${s.ghost}` : s.frame}>\n <input\n type=\"text\"\n inputMode=\"decimal\"\n aria-label={ariaLabel}\n placeholder={placeholder}\n value={draft ?? text}\n onChange={(e) => setDraft(e.target.value)}\n onFocus={() => {\n canceled.current = false;\n }}\n onBlur={() => {\n if (draft !== null && !canceled.current) {\n const n = parseNumber(draft, accepts);\n if (Number.isFinite(n)) commit(n);\n }\n setDraft(null);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n canceled.current = true;\n e.currentTarget.blur();\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n e.preventDefault();\n const typed = draft === null ? value : parseNumber(draft, accepts);\n const from = Number.isFinite(typed) ? typed : 0;\n const next = clamp(from + (e.key === 'ArrowUp' ? step : -step));\n setDraft(null);\n if (next !== value) onChange(next);\n }\n }}\n />\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"OptionsBar-DlcagPYK.js","names":[],"sources":["../../src/components/OptionsBar/OptionsBar.tsx"],"sourcesContent":["import { type CSSProperties, type ReactElement, type ReactNode } from 'react';\nimport s from '../segmentedControl.module.css';\nimport { useRovingTabIndex } from '../../useRovingTabIndex';\n\n/**\n * One segment of an {@link OptionsBar}. Unlike `ToggleBar`, each item carries\n * its own selected state and its own `onChange`.\n */\nexport type OptionsBarItem<V extends string | number = string> = {\n value: V;\n label?: ReactNode;\n ariaLabel?: string;\n disabled?: boolean;\n selected: boolean;\n onChange: (next: boolean) => void;\n};\n\n/** Segment height and type scale for an {@link OptionsBar}. */\nexport type OptionsBarSize = 'sm' | 'md';\n/** Visual treatment of an {@link OptionsBar}. */\nexport type OptionsBarVariant = 'default' | 'minimal';\n\n/** Props for {@link OptionsBar}. */\nexport type OptionsBarProps<V extends string | number = string> = {\n items: readonly OptionsBarItem<V>[];\n ariaLabel?: string;\n className?: string;\n height?: number;\n /** Size variant. `sm` is ~60% of the default height with reduced padding\n * and font size — sized for dense surfaces like lab control panels. */\n size?: OptionsBarSize;\n /** Visual variant. `minimal` strips the pill track and glass treatment;\n * selection becomes a flat accent. For dense diagnostic surfaces. */\n variant?: OptionsBarVariant;\n};\n\n/**\n * Segmented strip of independent on/off options, each owning its own state.\n * Use it when the segments are unrelated booleans; use `ToggleBar` when they\n * are values of one setting.\n */\nexport function OptionsBar<V extends string | number = string>(props: OptionsBarProps<V>): ReactElement {\n const { items, ariaLabel, className, height, size, variant } = props;\n\n const toggle = (index: number) => {\n const item = items[index];\n if (item.disabled) return;\n item.onChange(!item.selected);\n };\n\n const roving = useRovingTabIndex({ items, itemClassName: s.segment, onActivate: toggle });\n\n const style: CSSProperties | undefined = height !== undefined\n ? ({ ['--wzl-tb-height' as string]: `${height}px` } as CSSProperties)\n : undefined;\n\n const rootCls = [\n s.root,\n size && s[`size_${size}`],\n variant && s[`variant_${variant}`],\n className,\n ].filter(Boolean).join(' ');\n\n return (\n <div\n ref={roving.rootRef}\n className={rootCls}\n role=\"group\"\n aria-label={ariaLabel}\n style={style}\n >\n {items.map((item, i) => {\n const cls = `${s.segment}${item.selected ? ` ${s.segmentSelected}` : ''}`;\n return (\n <button\n key={item.value}\n type=\"button\"\n aria-pressed={item.selected}\n aria-label={item.ariaLabel}\n disabled={item.disabled}\n tabIndex={roving.tabIndexFor(i)}\n className={cls}\n onClick={() => toggle(i)}\n onKeyDown={roving.onKeyDown(i)}\n >\n {item.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;AAyCA,SAAgB,EAA+C,GAAyC;CACtG,IAAM,EAAE,UAAO,cAAW,cAAW,WAAQ,SAAM,eAAY,GAEzD,KAAU,MAAkB;EAChC,IAAM,IAAO,EAAM;AACf,IAAK,YACT,EAAK,SAAS,CAAC,EAAK,SAAS;IAGzB,IAAS,EAAkB;EAAE;EAAO,eAAe,EAAE;EAAS,YAAY;EAAQ,CAAC,EAEnF,IAAmC,MAAW,KAAA,IAEhD,KAAA,IADC,EAAG,mBAA8B,GAAG,EAAO,KAAK,EAG/C,IAAU;EACd,EAAE;EACF,KAAQ,EAAE,QAAQ;EAClB,KAAW,EAAE,WAAW;EACxB;EACD,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAE3B,QACE,kBAAC,OAAD;EACE,KAAK,EAAO;EACZ,WAAW;EACX,MAAK;EACL,cAAY;EACL;YAEN,EAAM,KAAK,GAAM,MAAM;GACtB,IAAM,IAAM,GAAG,EAAE,UAAU,EAAK,WAAW,IAAI,EAAE,oBAAoB;AACrE,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,gBAAc,EAAK;IACnB,cAAY,EAAK;IACjB,UAAU,EAAK;IACf,UAAU,EAAO,YAAY,EAAE;IAC/B,WAAW;IACX,eAAe,EAAO,EAAE;IACxB,WAAW,EAAO,UAAU,EAAE;cAE7B,EAAK;IACC,EAXF,EAAK,MAWH;IAEX;EACE,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PaintField-B2mLRZa4.js","names":[],"sources":["../../src/components/PaintField/PaintField.module.css","../../src/components/PaintField/PaintField.tsx"],"sourcesContent":[".trigger {\n display: inline-flex;\n align-items: center;\n gap: var(--wzl-space-3);\n inline-size: 100%;\n min-inline-size: 0;\n padding: 1px var(--wzl-space-3) 1px 1px;\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n background: var(--wzl-surface);\n color: var(--wzl-fg);\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size-sm);\n cursor: pointer;\n}\n\n.trigger[data-unset] {\n opacity: 0.6;\n}\n\n.trigger[data-focus-visible] {\n outline: none;\n border-color: var(--wzl-focus-ring);\n box-shadow: 0 0 0 1px var(--wzl-focus-ring);\n}\n\n.swatch {\n /* A paint preview at a fixed 26:18 aspect, not a control box. */\n inline-size: 26px;\n block-size: 18px; /* not-a-control */\n flex: 0 0 auto;\n border-radius: calc(var(--wzl-radius-sm) - var(--wzl-border-w));\n /* Checker under the paint, so a translucent one reads as translucent. */\n background-color: var(--wzl-surface-raised);\n box-shadow: inset 0 0 0 1px var(--wzl-border);\n}\n\n/* No paint to show: the checker stands in rather than an invented color. */\n.swatch[data-empty],\n.trigger[data-mixed] .swatch {\n background: repeating-conic-gradient(\n var(--wzl-checker-a) 0% 25%,\n var(--wzl-checker-b) 0% 50%\n )\n 50% / var(--wzl-checker-size) var(--wzl-checker-size);\n}\n\n.label {\n flex: 1 1 auto;\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: start;\n}\n\n.popover {\n outline: none;\n}\n\n.body {\n inline-size: 240px;\n padding: var(--wzl-space-4);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-md);\n background: var(--wzl-surface-raised);\n box-shadow: 0 8px 24px rgb(0 0 0 / 0.3);\n outline: none;\n font-family: var(--wzl-font-ui);\n font-size: var(--wzl-font-size);\n}\n","import { type ReactElement } from 'react';\nimport {\n Button as RACButton,\n Dialog as RACDialog,\n DialogTrigger,\n Popover as RACPopover,\n} from 'react-aria-components';\nimport { getPaintKind, type FillStyle, type PaintKind } from '@weasel-js/core';\nimport { useOverlayPortal, type OverlayPortalProps } from '../../overlays/portalHost';\nimport { paintPreviewCss } from '../../paintPreview';\nimport { PaintInput } from '../PaintInput';\nimport s from './PaintField.module.css';\n\n/** Props for {@link PaintField}. `onInput` fires throughout a gesture inside\n * the popover and `onChange` once at its end. */\nexport interface PaintFieldProps extends OverlayPortalProps {\n /** The paint being edited. `null` is an explicit \"no paint\"; `undefined` is\n * no value to show. */\n value: FillStyle | null | undefined;\n /** Indeterminate presentation: the trigger says \"Mixed\" and the editor\n * lights no kind. */\n mixed?: boolean;\n /** Dim the trigger — a value is in effect but was never chosen. */\n unset?: boolean;\n /** Restrict the editor's kind bar. Default: every registered kind. */\n kinds?: readonly PaintKind[];\n /** Offer \"None\" in the editor. Default `true`. */\n allowNone?: boolean;\n onInput?: (next: FillStyle | null) => void;\n onChange: (next: FillStyle | null) => void;\n /** Names the paint this field edits — `Fill`, `Color`. */\n 'aria-label'?: string;\n className?: string;\n}\n\n/** What the trigger says it is holding. */\nfunction kindLabel(paint: FillStyle | null | undefined, mixed: boolean): string {\n if (mixed) return 'Mixed';\n if (paint === null) return 'None';\n if (paint === undefined) return '—';\n return getPaintKind(paint.fill ?? 'solid')?.label ?? (paint.fill ?? 'solid');\n}\n\n/**\n * A whole `FillStyle` in one control slot: a swatch that says which kind it\n * holds, and a popover holding {@link PaintInput}.\n *\n * `PaintInput` itself is a kind bar over a body — six segments and a stop\n * editor — which a property row's control column cannot hold. This is the\n * shape for a narrow slot, and the difference that matters is fidelity: a\n * `ColorField` in that slot reads a gradient's first stop and writes a solid\n * back, so touching the control loses the paint.\n */\nexport function PaintField(props: PaintFieldProps): ReactElement {\n const {\n value, mixed = false, unset = false, kinds, allowNone = true,\n onInput, onChange, className, portalContainer,\n } = props;\n const ariaLabel = props['aria-label'];\n const { anchor, portalProps } = useOverlayPortal(portalContainer);\n const preview = mixed ? undefined : paintPreviewCss(value);\n\n return (\n <DialogTrigger>\n {anchor}\n <RACButton\n className={[s.trigger, className].filter(Boolean).join(' ')}\n aria-label={ariaLabel}\n {...(unset ? { 'data-unset': '' } : {})}\n {...(mixed ? { 'data-mixed': '' } : {})}\n >\n <span\n className={s.swatch}\n aria-hidden=\"true\"\n {...(preview ? { style: { background: preview } } : { 'data-empty': '' })}\n />\n <span className={s.label}>{kindLabel(value, mixed)}</span>\n </RACButton>\n {/* `data-weasel-overlay`: see Select — the popover renders in a portal,\n outside the subtree the trigger sits in. */}\n <RACPopover className={s.popover} data-weasel-overlay=\"\" {...portalProps}>\n <RACDialog className={s.body} aria-label={ariaLabel ? `${ariaLabel} editor` : 'Paint editor'}>\n <PaintInput\n value={value}\n mixed={mixed}\n unset={unset}\n kinds={kinds}\n allowNone={allowNone}\n onInput={onInput}\n onChange={onChange}\n aria-label={ariaLabel}\n />\n </RACDialog>\n </RACPopover>\n </DialogTrigger>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;ACoCA,SAAS,EAAU,GAAqC,GAAwB;AAI9E,QAHI,IAAc,UACd,MAAU,OAAa,SACvB,MAAU,KAAA,IAAkB,MACzB,EAAa,EAAM,QAAQ,QAAQ,EAAE,SAAU,EAAM,QAAQ;;AAatE,SAAgB,EAAW,GAAsC;CAC/D,IAAM,EACJ,UAAO,WAAQ,IAAO,WAAQ,IAAO,UAAO,eAAY,IACxD,YAAS,aAAU,cAAW,uBAC5B,GACE,IAAY,EAAM,eAClB,EAAE,WAAQ,mBAAgB,EAAiB,EAAgB,EAC3D,IAAU,IAAQ,KAAA,IAAY,EAAgB,EAAM;AAE1D,QACE,kBAAC,GAAD,EAAA,UAAA;EACG;EACD,kBAAC,GAAD;GACE,WAAW,CAAC,EAAE,SAAS,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;GAC3D,cAAY;GACZ,GAAK,IAAQ,EAAE,cAAc,IAAI,GAAG,EAAE;GACtC,GAAK,IAAQ,EAAE,cAAc,IAAI,GAAG,EAAE;aAJxC,CAME,kBAAC,QAAD;IACE,WAAW,EAAE;IACb,eAAY;IACZ,GAAK,IAAU,EAAE,OAAO,EAAE,YAAY,GAAS,EAAE,GAAG,EAAE,cAAc,IAAI;IACxE,CAAA,EACF,kBAAC,QAAD;IAAM,WAAW,EAAE;cAAQ,EAAU,GAAO,EAAM;IAAQ,CAAA,CAChD;;EAGZ,kBAAC,GAAD;GAAY,WAAW,EAAE;GAAS,uBAAoB;GAAG,GAAI;aAC3D,kBAAC,GAAD;IAAW,WAAW,EAAE;IAAM,cAAY,IAAY,GAAG,EAAU,WAAW;cAC5E,kBAAC,GAAD;KACS;KACA;KACA;KACA;KACI;KACF;KACC;KACV,cAAY;KACZ,CAAA;IACQ,CAAA;GACD,CAAA;EACC,EAAA,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PaintInput-BFKhd83I.js","names":[],"sources":["../../src/components/PaintInput/PaintInput.module.css","../../src/components/PaintInput/PaintInput.tsx"],"sourcesContent":["/* Kind switch stacked over whichever paint body it selects. */\n.root {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-3);\n min-inline-size: 0;\n}\n\n/* A value is in effect but nobody chose it. Never louder than mixed. */\n.unset {\n opacity: 0.55;\n}\n\n/* A kind with no editor: named, not editable. */\n.noEditor {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-muted);\n}\n","import { useCallback, useRef, type ReactElement } from 'react';\nimport {\n getPaintKind,\n listPaintKinds,\n withGradientKind,\n type FillStyle,\n type GradientFill,\n type GradientKind,\n type PaintKind,\n} from '@weasel-js/core';\nimport { Icon } from '../../icons/Icon';\nimport { ICON_PATHS, type IconName } from '../../icons/paths';\nimport { ColorField } from '../ColorField';\nimport { GradientEditor } from '../GradientEditor';\nimport { MeshEditor, isMeshPaint } from '../MeshEditor';\nimport { PatternPicker, type PatternFill } from '../PatternPicker';\nimport { ToggleBar, type ToggleBarItem } from '../ToggleBar';\nimport s from './PaintInput.module.css';\n\n/** What a kind with no color of its own seeds from. */\nconst FALLBACK_COLOR = '#000000ff';\n\n/** The bar's answer for \"no paint at all\". Not a registered kind — absence\n * has no seed, no color and nothing to render — so it is this control's own\n * segment rather than a registry entry. */\nconst NONE = 'none';\n\nconst GRADIENT_KINDS: readonly string[] = [\n 'linear-gradient',\n 'radial-gradient',\n 'conic-gradient',\n];\n\nfunction isGradient(paint: FillStyle | undefined): paint is GradientFill {\n return paint !== undefined && GRADIENT_KINDS.includes(paint.fill ?? '');\n}\n\nfunction isPattern(paint: FillStyle | undefined): paint is PatternFill {\n return paint?.fill === 'pattern';\n}\n\n/** A paint's own discriminant. The solid member leaves `fill` off, and an\n * explicit `null` is the absence of paint rather than an unknown one. */\nfunction kindOf(paint: FillStyle | null | undefined): PaintKind | null {\n if (paint === null) return NONE;\n if (paint === undefined) return null;\n return paint.fill ?? 'solid';\n}\n\n/** Props for {@link PaintInput}. `onInput` fires throughout a gesture and\n * `onChange` once at its end. */\nexport interface PaintInputProps {\n /** The paint being edited. `null` is an explicit \"no paint\"; `undefined`\n * is no value to show. */\n value: FillStyle | null | undefined;\n /** Indeterminate presentation: no kind lit, and the body withheld because\n * there is no single paint to show. */\n mixed?: boolean;\n /** Dim the control — a value is in effect but was never chosen. */\n unset?: boolean;\n /** Restrict the kind bar. Default: every registered kind. */\n kinds?: readonly PaintKind[];\n /** Offer \"None\". Default `true` — the bar answers \"what kind of paint is\n * this?\" and no-paint is one of the answers. Turn it off where the slot\n * cannot express absence. */\n allowNone?: boolean;\n onInput?: (next: FillStyle | null) => void;\n onChange: (next: FillStyle | null) => void;\n /** Names the paint this control edits — `Fill`, `Color`. Carried by the\n * solid body's swatch, which is the part with a value to announce. */\n 'aria-label'?: string;\n className?: string;\n}\n\n/**\n * Editor for a whole `FillStyle` — a kind bar over whichever body that kind\n * wants.\n *\n * The bar is driven by the paint-kind registry rather than a fixed list, so a\n * consumer's registered kind appears in it and renders that entry's `Editor`.\n * The five built-in kinds keep their branch here: their editors live in this\n * package and `@weasel-js/core`, which owns the registry, cannot import it.\n *\n * Geometry is deliberately absent — on a canvas it belongs on the artwork,\n * via `<SceneGradientHandles>`.\n */\nexport function PaintInput(props: PaintInputProps): ReactElement {\n const {\n value, mixed = false, unset = false, kinds, allowNone = true,\n onInput, onChange, className,\n } = props;\n const ariaLabel = props['aria-label'];\n\n // Per-kind memory, so linear → solid → linear comes back with its stops.\n // A ref, not state: it must not survive the control being pointed at a\n // different selection, and nothing renders off it directly.\n const remembered = useRef(new Map<PaintKind, FillStyle>());\n\n const active = mixed ? null : kindOf(value);\n if (value != null && active !== null) remembered.current.set(active, value);\n\n const entries = listPaintKinds().filter(\n (entry) => kinds === undefined || kinds.includes(entry.id),\n );\n const segment = (id: string, label: string, icon: string | undefined): ToggleBarItem<PaintKind> =>\n icon && icon in ICON_PATHS\n // Icon-only: six labelled segments do not fit a property row, and the\n // full label stays the accessible name.\n // 14 to match the panel's other glyph segments. A `sm` bar is 17px tall\n // over 1px of padding, so 15 fills the segment box edge to edge.\n ? { value: id, label: <Icon name={icon as IconName} size={14} />, ariaLabel: label }\n : { value: id, label };\n\n const items: readonly ToggleBarItem<PaintKind>[] = [\n ...(allowNone ? [segment(NONE, 'None', 'paintNone')] : []),\n ...entries.map((entry) => segment(entry.id, entry.label, entry.icon)),\n ];\n\n /** The color a switch seeds from: whatever the current paint shows. */\n const currentColor = useCallback((): string => {\n if (value == null) return FALLBACK_COLOR;\n return getPaintKind(kindOf(value) ?? 'solid')?.colorOf(value) ?? FALLBACK_COLOR;\n }, [value]);\n\n const switchKind = (kind: PaintKind): void => {\n if (kind === active) return;\n if (kind === NONE) {\n onChange(null);\n return;\n }\n const seen = remembered.current.get(kind);\n if (seen !== undefined) {\n onChange(seen);\n return;\n }\n if (value != null && isGradient(value) && GRADIENT_KINDS.includes(kind)) {\n onChange(withGradientKind(value, kind as GradientKind));\n return;\n }\n const entry = getPaintKind(kind);\n if (entry) onChange(entry.seed(currentColor()));\n };\n\n return (\n <div\n className={[s.root, unset && s.unset, className].filter(Boolean).join(' ')}\n title={unset ? 'Not set' : undefined}\n >\n <ToggleBar<PaintKind>\n items={items}\n value={active}\n size=\"sm\"\n ariaLabel=\"Paint kind\"\n onChange={(kind) => kind && switchKind(kind)}\n />\n {renderBody()}\n </div>\n );\n\n function renderBody(): ReactElement | null {\n if (mixed || value === undefined) {\n return (\n <ColorField\n mixed\n aria-label={ariaLabel}\n onChange={(color) => onChange({ fill: 'solid', color })}\n />\n );\n }\n // Absence has nothing to edit. The lit segment is the whole statement.\n if (value === null) return null;\n // A registered `Editor` wins even for a built-in id: re-registering one is\n // how a consumer replaces a kit control, and the built-in branches below\n // are the fallback for the ids that ship without an entry editor.\n const entry = getPaintKind(kindOf(value) ?? 'solid');\n if (entry?.Editor) {\n const Editor = entry.Editor;\n return <Editor value={value} onInput={onInput} onChange={onChange} />;\n }\n if (isGradient(value)) {\n return (\n <GradientEditor\n value={value}\n kindSwitch={false}\n onInput={onInput}\n onChange={onChange}\n />\n );\n }\n if (isPattern(value)) {\n return <PatternPicker value={value} color={currentColor()} onChange={onChange} />;\n }\n if (isMeshPaint(value)) {\n return <MeshEditor value={value} onInput={onInput} onChange={onChange} />;\n }\n const kind = kindOf(value);\n if (kind !== 'solid') {\n // Editing a paint this control cannot represent would overwrite it, so\n // a kind with no editor is shown and left alone.\n return <div className={s.noEditor}>{entry?.label ?? kind}: no editor</div>;\n }\n return (\n <ColorField\n value={currentColor()}\n alpha\n aria-label={ariaLabel}\n onInput={(color) => onInput?.({ fill: 'solid', color })}\n onChange={(color) => onChange({ fill: 'solid', color })}\n />\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PatternPicker-DdfpxoQy.js","names":[],"sources":["../../src/components/PatternPicker/PatternPicker.module.css","../../src/components/PatternPicker/PatternPicker.tsx"],"sourcesContent":["/* Tile grid over the size switch. */\n.root {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-3);\n min-inline-size: 0;\n}\n\n.grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(var(--wzl-swatch-size, 28px), 1fr));\n gap: 0;\n}\n\n/* The swatch's whole face is its own tile, so it must not carry a flat\n background — and the tile repeats at its own scale, not the swatch's. */\n.swatch {\n width: 100%;\n aspect-ratio: 1 / 1;\n min-width: 0;\n padding: 0;\n border: 0.5px solid var(--wzl-border);\n border-radius: 0;\n cursor: pointer;\n background-color: var(--wzl-surface-sunken);\n background-image: var(--tile-preview);\n background-repeat: repeat;\n background-size: 16px 16px;\n background-clip: padding-box;\n}\n\n.swatch:hover {\n border-color: var(--wzl-fg-muted);\n}\n\n.swatchActive {\n border-color: var(--wzl-accent);\n box-shadow: 0 0 0 1px var(--wzl-accent);\n}\n","import type { ReactElement } from 'react';\nimport type { CSSProperties } from 'react';\nimport type { FillStyle, TilePatternSpec } from '@weasel-js/core';\nimport { tilePreviewCssUrl } from '@weasel-js/svg';\nimport { ToggleBar } from '../ToggleBar';\nimport s from './PatternPicker.module.css';\n\n/** The `pattern` member of the paint union. */\nexport type PatternFill = Extract<FillStyle, { fill: 'pattern' }>;\n\nconst TILES: readonly TilePatternSpec['tile'][] = ['hatch', 'crosshatch', 'dots', 'chunks'];\n\n/** Tile sizes the picker offers, coarse enough that each reads as a\n * distinct texture rather than a nudge. */\nconst TILE_SIZES: readonly { value: number; label: string }[] = [\n { value: 4, label: 'S' },\n { value: 8, label: 'M' },\n { value: 16, label: 'L' },\n { value: 32, label: 'XL' },\n];\n\nconst DEFAULT_TILE_SIZE = 8;\n\n/** Every swatch previews at one size so the grid compares tiles, not scales. */\nconst PREVIEW_SIZE = 16;\n\n/** The spec a pattern paint carries, if it carries one. A consumer-built\n * `TextureHandle` has no spec, so the picker shows no tile selected and\n * replaces it wholesale on the next click. */\nfunction specOf(fill: PatternFill): TilePatternSpec | null {\n return 'tile' in fill.pattern ? fill.pattern : null;\n}\n\n/**\n * A pattern seeded from a color. `units: 'bounds'` anchors the tile to the\n * node's box, so the texture travels with the shape.\n */\nexport function seedPattern(\n tile: TilePatternSpec['tile'],\n from: string,\n size: number = DEFAULT_TILE_SIZE,\n): PatternFill {\n const spec: TilePatternSpec = { tile, color: from, size };\n // `chunks` is a scatter over a ground rather than a line texture; without a\n // background it reads as loose specks on whatever sits beneath.\n if (tile === 'chunks') spec.bg = '#00000000';\n return { fill: 'pattern', pattern: spec, units: 'bounds' };\n}\n\n/** Props for {@link PatternPicker}. */\nexport interface PatternPickerProps {\n /** The pattern paint being edited. */\n value: PatternFill;\n /** The color new tiles are seeded with — the paint's own, normally. */\n color: string;\n /** Committed value: one call per click. A tile pick and a size change are\n * each a complete gesture, so there is no `onInput`. */\n onChange: (next: PatternFill) => void;\n className?: string;\n}\n\n/**\n * Grid of tile swatches over a size switch, each swatch previewing the real\n * tile at the current color.\n *\n * The preview shares its shape mapper with what the renderer paints, so a\n * swatch cannot drift from the texture it selects.\n */\nexport function PatternPicker(props: PatternPickerProps): ReactElement {\n const { value, color, onChange, className } = props;\n const spec = specOf(value);\n const size = spec?.size ?? DEFAULT_TILE_SIZE;\n\n const pick = (tile: TilePatternSpec['tile']): void => {\n onChange(seedPattern(tile, color, size));\n };\n\n const resize = (nextSize: number): void => {\n if (!spec) return;\n onChange({ ...value, pattern: { ...spec, size: nextSize } });\n };\n\n return (\n <div className={[s.root, className].filter(Boolean).join(' ')}>\n <div className={s.grid}>\n {TILES.map((tile) => {\n const preview: TilePatternSpec = { tile, color, size: PREVIEW_SIZE };\n if (tile === 'chunks') preview.bg = '#00000000';\n const selected = spec?.tile === tile;\n return (\n <button\n key={tile}\n type=\"button\"\n className={[s.swatch, selected && s.swatchActive].filter(Boolean).join(' ')}\n style={{ ['--tile-preview' as string]: tilePreviewCssUrl(preview) } as CSSProperties}\n title={tile}\n aria-label={tile}\n aria-pressed={selected}\n onClick={() => pick(tile)}\n />\n );\n })}\n </div>\n <ToggleBar<number>\n items={TILE_SIZES}\n value={size}\n size=\"sm\"\n ariaLabel=\"Tile size\"\n onChange={(v) => v != null && resize(v)}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;GCUM,IAA4C;CAAC;CAAS;CAAc;CAAQ;CAAS,EAIrF,IAA0D;CAC9D;EAAE,OAAO;EAAG,OAAO;EAAK;CACxB;EAAE,OAAO;EAAG,OAAO;EAAK;CACxB;EAAE,OAAO;EAAI,OAAO;EAAK;CACzB;EAAE,OAAO;EAAI,OAAO;EAAM;CAC3B,EAEK,IAAoB,GAGpB,IAAe;AAKrB,SAAS,EAAO,GAA2C;AACzD,QAAO,UAAU,EAAK,UAAU,EAAK,UAAU;;AAOjD,SAAgB,EACd,GACA,GACA,IAAe,GACF;CACb,IAAM,IAAwB;EAAE;EAAM,OAAO;EAAM;EAAM;AAIzD,QADI,MAAS,aAAU,EAAK,KAAK,cAC1B;EAAE,MAAM;EAAW,SAAS;EAAM,OAAO;EAAU;;AAsB5D,SAAgB,EAAc,GAAyC;CACrE,IAAM,EAAE,UAAO,UAAO,aAAU,iBAAc,GACxC,IAAO,EAAO,EAAM,EACpB,IAAO,GAAM,QAAQ,GAErB,KAAQ,MAAwC;AACpD,IAAS,EAAY,GAAM,GAAO,EAAK,CAAC;IAGpC,KAAU,MAA2B;AACpC,OACL,EAAS;GAAE,GAAG;GAAO,SAAS;IAAE,GAAG;IAAM,MAAM;IAAU;GAAE,CAAC;;AAG9D,QACE,kBAAC,OAAD;EAAK,WAAW,CAAC,EAAE,MAAM,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;YAA7D,CACE,kBAAC,OAAD;GAAK,WAAW,EAAE;aACf,EAAM,KAAK,MAAS;IACnB,IAAM,IAA2B;KAAE;KAAM;KAAO,MAAM;KAAc;AACpE,IAAI,MAAS,aAAU,EAAQ,KAAK;IACpC,IAAM,IAAW,GAAM,SAAS;AAChC,WACE,kBAAC,UAAD;KAEE,MAAK;KACL,WAAW,CAAC,EAAE,QAAQ,KAAY,EAAE,aAAa,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;KAC3E,OAAO,EAAG,kBAA6B,EAAkB,EAAQ,EAAE;KACnE,OAAO;KACP,cAAY;KACZ,gBAAc;KACd,eAAe,EAAK,EAAK;KACzB,EARK,EAQL;KAEJ;GACE,CAAA,EACN,kBAAC,GAAD;GACE,OAAO;GACP,OAAO;GACP,MAAK;GACL,WAAU;GACV,WAAW,MAAM,KAAK,QAAQ,EAAO,EAAE;GACvC,CAAA,CACE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Plot2D-lyO7waPQ.js","names":[],"sources":["../../src/components/Plot2D/geometry.ts","../../src/components/Plot2D/ticks.ts","../../src/components/Plot2D/Plot2D.module.css","../../src/components/Plot2D/Plot2D.tsx"],"sourcesContent":["/**\n * Coordinate transforms (model ↔ plot) for Plot2D. Pure / deterministic —\n * no DOM, no React, no state.\n *\n * Model space: caller-defined xRange × yRange. Y axis goes UP.\n * Plot space: 0..width × 0..height in CSS pixels. Y axis goes DOWN (SVG).\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\n/**\n * The caller-defined extent of model space, the coordinate system a plot's\n * data lives in.\n */\nexport interface ModelRange {\n xMin: number;\n xMax: number;\n yMin: number;\n yMax: number;\n}\n\n/** The plot's drawing area in CSS pixels. */\nexport interface PlotSize {\n width: number;\n height: number;\n}\n\n/**\n * Maps a model-space point into plot space, flipping the y axis so model y\n * grows upward while SVG y grows downward.\n */\nexport function modelToPlot(p: Point, m: ModelRange, plot: PlotSize): Point {\n const xFrac = (p.x - m.xMin) / (m.xMax - m.xMin);\n const yFrac = (p.y - m.yMin) / (m.yMax - m.yMin);\n return {\n x: xFrac * plot.width,\n // Invert y so model y=0 sits at plot bottom (SVG y grows downward).\n y: (1 - yFrac) * plot.height,\n };\n}\n\n/** The inverse of {@link modelToPlot}. */\nexport function plotToModel(p: Point, m: ModelRange, plot: PlotSize): Point {\n const xFrac = p.x / plot.width;\n const yFrac = 1 - p.y / plot.height;\n return {\n x: m.xMin + xFrac * (m.xMax - m.xMin),\n y: m.yMin + yFrac * (m.yMax - m.yMin),\n };\n}\n","/**\n * Tick placement for a numeric axis. Pure — no DOM, no React.\n */\n\n/** What a tick label formatter knows about the column it is part of. */\nexport interface TickFormatCtx {\n /** Places after the point that every label in the column shares. */\n decimals: number;\n /** Distance between neighboring ticks, or 0 when the ticks were given\n * explicitly and are not evenly spaced. */\n step: number;\n}\n\n/** Turns a tick value into its label. */\nexport type TickFormatter = (value: number, ctx: TickFormatCtx) => string;\n\n/** A column of ticks and the facts its labels share. */\nexport interface TickSet {\n values: number[];\n step: number;\n decimals: number;\n}\n\n/** Options for {@link niceTicks}. */\nexport interface NiceTickOptions {\n /** Smallest gap between neighboring ticks, in px. */\n minSpacing: number;\n /** Ticks closer than this to either end of the axis are dropped, in px.\n * Default 0. */\n inset?: number;\n}\n\nconst MAX_DECIMALS = 20;\n\n/** The smallest of 1, 2 or 5 times a power of ten that is at least `rough`. */\nexport function niceStep(rough: number): number {\n const mag = Math.pow(10, Math.floor(Math.log10(rough)));\n const norm = rough / mag;\n const step = norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 5 ? 5 : 10;\n return step * mag;\n}\n\n/** Places after the point a multiple of `step` needs, for a 1/2/5 step. */\nexport function stepDecimals(step: number): number {\n if (!(step > 0) || !Number.isFinite(step)) return 0;\n return Math.min(MAX_DECIMALS, Math.max(0, -Math.floor(Math.log10(step) + 1e-9)));\n}\n\n/** The most places after the point any of `values` needs. */\nexport function tickDecimals(values: readonly number[]): number {\n let most = 0;\n for (const v of values) {\n if (!Number.isFinite(v)) continue;\n let d = 0;\n while (d < MAX_DECIMALS && Math.abs(Number(v.toFixed(d)) - v) > 1e-9 * Math.max(1, Math.abs(v))) d++;\n most = Math.max(most, d);\n }\n return most;\n}\n\n/**\n * Ticks at round values across `range`, drawn `pixels` long: the finest\n * 1/2/5 step that keeps neighbors `minSpacing` px apart, so a taller plot\n * gets more of them.\n */\nexport function niceTicks(\n range: readonly [number, number],\n pixels: number,\n options: NiceTickOptions,\n): TickSet {\n const none: TickSet = { values: [], step: 0, decimals: 0 };\n const lo = Math.min(range[0], range[1]);\n const hi = Math.max(range[0], range[1]);\n const span = hi - lo;\n const { minSpacing, inset = 0 } = options;\n if (!(span > 0) || !Number.isFinite(span) || !(pixels > 0) || !(minSpacing > 0)) return none;\n\n const unitsPerPx = span / pixels;\n const step = niceStep(minSpacing * unitsPerPx);\n const decimals = stepDecimals(step);\n const from = lo + inset * unitsPerPx;\n const to = hi - inset * unitsPerPx;\n // A tolerance in step units, so a tick sitting exactly on a bound survives\n // the float error in `from / step`.\n const first = Math.ceil(from / step - 1e-9);\n const last = Math.floor(to / step + 1e-9);\n const values: number[] = [];\n for (let i = first; i <= last; i++) values.push(Number((i * step).toFixed(decimals)) + 0);\n return { values, step, decimals };\n}\n\n/** The default label: `value` fixed to the column's shared decimals, with\n * no negative zero. */\nexport function formatTick(value: number, ctx: TickFormatCtx): string {\n const text = value.toFixed(ctx.decimals);\n return /^-0(\\.0*)?$/.test(text) ? text.slice(1) : text;\n}\n",".root {\n /* Component-local vars layered over theme tokens. Consumers override\n * either the plot-* var (component scope) or the wzl-* token (app\n * scope). */\n --plot-bg: var(--wzl-surface-sunken);\n --plot-grid: var(--wzl-line);\n /* Axes get full foreground contrast — they're the plot frame, the\n * most prominent structural element after caller-rendered content. */\n --plot-axis: var(--wzl-fg);\n --plot-tick: var(--wzl-line-subtle);\n --plot-tick-label: var(--wzl-fg-muted);\n /* Knocks the tick line out from behind an inside label. */\n --plot-tick-label-halo: var(--plot-bg);\n\n display: block;\n /* Caller-rendered marks at the very edge (e.g. CurveEditor anchors)\n * extend past the SVG box by their radius — let them paint over the\n * host page instead of being half-clipped. */\n overflow: visible;\n background: var(--plot-bg);\n user-select: none;\n touch-action: none;\n outline: none;\n}\n\n/* Keyboard-only focus ring. Caller opts in via `tabIndex`. */\n.root:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: 2px;\n}\n\n.grid {\n stroke: var(--plot-grid);\n stroke-width: 1;\n fill: none;\n}\n\n.axis {\n stroke: var(--plot-axis);\n stroke-width: var(--wzl-line-width);\n fill: none;\n}\n\n.tick {\n stroke: var(--plot-tick);\n stroke-width: 1;\n fill: none;\n}\n\n.tickLabels { pointer-events: none; }\n\n.tickLabel {\n fill: var(--plot-tick-label);\n font-size: var(--wzl-font-size-xs);\n font-variant-numeric: tabular-nums;\n}\n\n.tickLabel[data-placement='inside'] {\n stroke: var(--plot-tick-label-halo);\n stroke-width: 3px;\n stroke-linejoin: round;\n paint-order: stroke;\n}\n","import {\n forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from 'react';\nimport {\n modelToPlot, plotToModel,\n type ModelRange, type Point,\n} from './geometry';\nimport {\n formatTick, niceTicks, tickDecimals,\n type TickFormatCtx, type TickFormatter,\n} from './ticks';\nimport s from './Plot2D.module.css';\nimport { dlog } from '../../dlog';\n\n/** Background grid configuration for a {@link Plot2D}. */\nexport interface GridSettings {\n /** Number of evenly-spaced internal grid lines per axis (excluding\n * the edges). Applied to both x and y. Default 3. */\n divisions?: number;\n /** Stroke color override. When omitted, uses `var(--plot-grid)`. */\n color?: string;\n}\n\n/** Axis-line configuration for a {@link Plot2D}. */\nexport interface AxesSettings {\n /** Stroke color override. When omitted, uses `var(--plot-axis)`. */\n color?: string;\n}\n\n/**\n * Ticks along one axis of a {@link Plot2D}: a line across the plot at each\n * tick value, and a label naming it. Every label in a column is formatted\n * to the same number of decimal places.\n */\nexport interface TickSettings {\n /** Tick values in model space; any outside the range are skipped.\n * Omitted = round 1/2/5 steps, as many as `minSpacing` allows at the\n * plot's current size. */\n values?: readonly number[];\n /** Smallest gap between generated ticks, in px. Default 24 on y, 64 on x. */\n minSpacing?: number;\n /** A generated tick nearer either end of the axis than this is dropped,\n * so its label stays on the plot, in px. Default 4 on y, 16 on x. */\n inset?: number;\n /** Draw a line across the plot at each tick. Default true. */\n lines?: boolean;\n /** Where labels sit: `'inside'` the plot along its left (y) or bottom (x)\n * edge, `'outside'` it past that edge in space the caller leaves free,\n * or `false` for none. Default `'inside'`. */\n labels?: 'inside' | 'outside' | false;\n /** Space between a label and the plot edge it sits against, in px.\n * Default 4. */\n gap?: number;\n /** Label text for a tick. Default {@link formatTick}. */\n format?: TickFormatter;\n}\n\n/** A pointer position given in both of the plot's coordinate systems. */\nexport interface Plot2DCoords {\n plot: Point;\n model: Point;\n}\n\n/** Props for {@link Plot2D}. */\nexport interface Plot2DProps {\n width: number;\n height: number;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n /** Background grid. `false` / `null` / omitted = no grid. Pass `{}`\n * for default (3 divisions per axis) or populated GridSettings. */\n grid?: GridSettings | false | null;\n /** Axis lines. `false` / `null` = no axes. Omitted = default-styled\n * axes (on). Pass AxesSettings to customize. */\n axes?: AxesSettings | false | null;\n /** Ticks at x values. `false` / `null` / omitted = none; `{}` = defaults. */\n xTicks?: TickSettings | false | null;\n /** Ticks at y values. `false` / `null` / omitted = none; `{}` = defaults. */\n yTicks?: TickSettings | false | null;\n /** Forwarded to the underlying svg. Consumer opts into focus this way. */\n tabIndex?: number;\n /** Default `'img'`. A plot holding focusable marks must not be an image,\n * whose children are presentational to assistive technology. */\n role?: string;\n 'aria-label'?: string;\n className?: string;\n style?: CSSProperties;\n /** Pointer down on the SVG. Receives both plot- and model-space coords\n * pre-computed so consumers don't repeat the rect/transform dance. */\n onPointerDown?: (e: ReactPointerEvent<SVGSVGElement>, coords: Plot2DCoords) => void;\n onKeyDown?: (e: ReactKeyboardEvent<SVGSVGElement>) => void;\n children?: ReactNode;\n}\n\n/**\n * Imperative handle on a {@link Plot2D}: the SVG element, its size, and the\n * coordinate conversions, including ones that start from a raw DOM event so a\n * drag tracked on `window` can still map back into the plot.\n */\nexport interface Plot2DHandle {\n readonly svg: SVGSVGElement | null;\n plotToModel(pt: Point): Point;\n modelToPlot(pt: Point): Point;\n /** Convert a DOM event's clientX/clientY (e.g. from a window-attached\n * pointermove during drag) to plot-space. */\n clientToPlot(e: { clientX: number; clientY: number }): Point;\n /** Convenience: clientToPlot → plotToModel. */\n clientToModel(e: { clientX: number; clientY: number }): Point;\n readonly width: number;\n readonly height: number;\n}\n\ntype Axis = 'x' | 'y';\n\nconst TICK_SPACING_PX: Record<Axis, number> = { x: 64, y: 24 };\nconst TICK_INSET_PX: Record<Axis, number> = { x: 16, y: 4 };\nconst LABEL_GAP_PX = 4;\n\ninterface ResolvedTick {\n value: number;\n /** Plot-space position along the axis. */\n at: number;\n label: string;\n}\n\nfunction resolveTicks(\n settings: TickSettings,\n axis: Axis,\n range: readonly [number, number],\n pixels: number,\n toPlot: (v: number) => number,\n): ResolvedTick[] {\n let values: number[];\n let ctx: TickFormatCtx;\n if (settings.values) {\n const lo = Math.min(range[0], range[1]);\n const hi = Math.max(range[0], range[1]);\n const eps = (hi - lo) * 1e-9;\n values = settings.values.filter((v) => v >= lo - eps && v <= hi + eps);\n const gaps = values.slice(1).map((v, i) => v - values[i]);\n const even = gaps.length > 0 && gaps.every((g) => Math.abs(g - gaps[0]) <= Math.abs(gaps[0]) * 1e-9);\n ctx = { decimals: tickDecimals(values), step: even ? gaps[0] : 0 };\n } else {\n const set = niceTicks(range, pixels, {\n minSpacing: settings.minSpacing ?? TICK_SPACING_PX[axis],\n inset: settings.inset ?? TICK_INSET_PX[axis],\n });\n values = set.values;\n ctx = { decimals: set.decimals, step: set.step };\n }\n const format = settings.format ?? formatTick;\n return values.map((value) => ({ value, at: toPlot(value), label: format(value, ctx) }));\n}\n\n/**\n * An SVG plotting surface with an optional grid, axes and ticks. It draws the\n * frame and owns the model-space to plot-space mapping; the plotted content is\n * whatever children are passed, positioned in plot space.\n */\nexport const Plot2D = forwardRef<Plot2DHandle, Plot2DProps>(function Plot2D(props, ref) {\n const {\n width, height,\n xRange = [0, 1],\n yRange = [0, 1],\n grid,\n axes,\n xTicks,\n yTicks,\n tabIndex,\n role = 'img',\n className,\n style,\n onPointerDown,\n onKeyDown,\n children,\n } = props;\n\n const svgRef = useRef<SVGSVGElement | null>(null);\n\n const modelRange: ModelRange = useMemo(\n () => ({ xMin: xRange[0], xMax: xRange[1], yMin: yRange[0], yMax: yRange[1] }),\n [xRange, yRange],\n );\n\n const plotSize = useMemo(() => ({ width, height }), [width, height]);\n\n const clientToPlot = useCallback((e: { clientX: number; clientY: number }): Point => {\n const rect = svgRef.current?.getBoundingClientRect();\n const left = rect?.left ?? 0;\n const top = rect?.top ?? 0;\n return { x: e.clientX - left, y: e.clientY - top };\n }, []);\n\n useEffect(() => {\n dlog('plot2d', 'mount', { width, height });\n return () => dlog('plot2d', 'unmount');\n // Mount/unmount trace: re-running on a resize would log a spurious mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n useImperativeHandle(ref, () => ({\n get svg() { return svgRef.current; },\n plotToModel: (pt: Point) => plotToModel(pt, modelRange, plotSize),\n modelToPlot: (pt: Point) => modelToPlot(pt, modelRange, plotSize),\n clientToPlot,\n clientToModel: (e) => plotToModel(clientToPlot(e), modelRange, plotSize),\n get width() { return plotSize.width; },\n get height() { return plotSize.height; },\n }), [modelRange, plotSize, clientToPlot]);\n\n const handlePointerDown = useCallback((e: ReactPointerEvent<SVGSVGElement>) => {\n if (!onPointerDown) return;\n const plot = clientToPlot(e);\n const model = plotToModel(plot, modelRange, plotSize);\n onPointerDown(e, { plot, model });\n }, [onPointerDown, clientToPlot, modelRange, plotSize]);\n\n const xTickList = xTicks\n ? resolveTicks(xTicks, 'x', xRange, width, (v) => modelToPlot({ x: v, y: yRange[0] }, modelRange, plotSize).x)\n : [];\n const yTickList = yTicks\n ? resolveTicks(yTicks, 'y', yRange, height, (v) => modelToPlot({ x: xRange[0], y: v }, modelRange, plotSize).y)\n : [];\n\n // An inside y column is right-aligned against its widest label, which only\n // the rendered text can say.\n const yInside = !!yTicks && (yTicks.labels ?? 'inside') === 'inside';\n const yLabelsRef = useRef<SVGGElement | null>(null);\n const [yLabelWidth, setYLabelWidth] = useState(0);\n const yLabelKey = yInside ? yTickList.map((t) => t.label).join('\\n') : '';\n useLayoutEffect(() => {\n if (!yInside) return;\n const measure = (): void => {\n let widest = 0;\n for (const el of yLabelsRef.current?.querySelectorAll('text') ?? []) {\n if (typeof el.getComputedTextLength === 'function') widest = Math.max(widest, el.getComputedTextLength());\n }\n setYLabelWidth((prev) => (prev === widest ? prev : widest));\n };\n measure();\n let live = true;\n // A web font landing after the first measure changes every width.\n document.fonts?.ready.then(() => { if (live) measure(); });\n return () => { live = false; };\n }, [yInside, yLabelKey]);\n\n const cls = [s.root, className].filter(Boolean).join(' ');\n\n const renderTickLines = (axis: Axis, list: ResolvedTick[], settings: TickSettings | false | null | undefined) => {\n if (!settings || settings.lines === false || list.length === 0) return null;\n return (\n <g>\n {list.map((t) => (\n <line\n key={`t${axis}-${t.value}`}\n data-plot-element=\"tick\"\n data-axis={axis}\n className={s.tick}\n {...(axis === 'x'\n ? { x1: t.at, x2: t.at, y1: 0, y2: height }\n : { x1: 0, x2: width, y1: t.at, y2: t.at })}\n />\n ))}\n </g>\n );\n };\n\n const renderTickLabels = (axis: Axis, list: ResolvedTick[], settings: TickSettings | false | null | undefined) => {\n if (!settings || list.length === 0) return null;\n const placement = settings.labels ?? 'inside';\n if (placement === false) return null;\n const outside = placement === 'outside';\n const gap = settings.gap ?? LABEL_GAP_PX;\n let place: { x?: number; y?: number; textAnchor: 'start' | 'middle' | 'end'; dominantBaseline?: 'central' | 'hanging' };\n if (axis === 'y') {\n place = outside\n ? { x: -gap, textAnchor: 'end', dominantBaseline: 'central' }\n : yLabelWidth > 0\n ? { x: gap + yLabelWidth, textAnchor: 'end', dominantBaseline: 'central' }\n : { x: gap, textAnchor: 'start', dominantBaseline: 'central' };\n } else {\n place = outside\n ? { y: height + gap, textAnchor: 'middle', dominantBaseline: 'hanging' }\n : { y: height - gap, textAnchor: 'middle' };\n }\n return (\n <g ref={axis === 'y' ? yLabelsRef : undefined} className={s.tickLabels} aria-hidden=\"true\">\n {list.map((t) => (\n <text\n key={`l${axis}-${t.value}`}\n data-plot-element=\"tick-label\"\n data-axis={axis}\n data-placement={placement}\n className={s.tickLabel}\n x={axis === 'x' ? t.at : place.x}\n y={axis === 'y' ? t.at : place.y}\n textAnchor={place.textAnchor}\n dominantBaseline={place.dominantBaseline}\n >\n {t.label}\n </text>\n ))}\n </g>\n );\n };\n\n return (\n <svg\n ref={svgRef}\n className={cls}\n style={style}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role={role}\n aria-label={props['aria-label']}\n tabIndex={tabIndex}\n onPointerDown={onPointerDown ? handlePointerDown : undefined}\n onKeyDown={onKeyDown}\n >\n {grid && (() => {\n const divisions = grid.divisions ?? 3;\n const stroke = grid.color;\n // Evenly-spaced internal divisions, excluding the edges (which\n // belong to the axes). For divisions=3, fractions are 1/4, 2/4, 3/4.\n const fractions: number[] = [];\n for (let i = 1; i <= divisions; i++) fractions.push(i / (divisions + 1));\n return (\n <g>\n {fractions.map((f) => (\n <line\n key={`gx-${f}`}\n data-plot-element=\"grid\"\n className={s.grid}\n stroke={stroke}\n x1={f * width} x2={f * width}\n y1={0} y2={height}\n />\n ))}\n {fractions.map((f) => (\n <line\n key={`gy-${f}`}\n data-plot-element=\"grid\"\n className={s.grid}\n stroke={stroke}\n x1={0} x2={width}\n y1={f * height} y2={f * height}\n />\n ))}\n </g>\n );\n })()}\n {renderTickLines('x', xTickList, xTicks)}\n {renderTickLines('y', yTickList, yTicks)}\n {axes !== false && axes !== null && (() => {\n const stroke = axes?.color;\n return (\n <g>\n <line\n data-plot-element=\"axis\"\n className={s.axis}\n stroke={stroke}\n x1={0} x2={width}\n y1={height} y2={height}\n />\n <line\n data-plot-element=\"axis\"\n className={s.axis}\n stroke={stroke}\n x1={0} x2={0}\n y1={0} y2={height}\n />\n </g>\n );\n })()}\n {renderTickLabels('x', xTickList, xTicks)}\n {renderTickLabels('y', yTickList, yTicks)}\n {children}\n </svg>\n );\n});\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PointPlotter-DGs9i5P-.js","names":[],"sources":["../../src/components/PointPlotter/PointPlotter.tsx"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\nimport {\n CurveEditor,\n type AnchorRenderProps,\n type ControlPoint,\n type CurveEditorProps,\n} from '../CurveEditor';\nimport type { GridSettings, AxesSettings, TickSettings } from '../Plot2D';\n\n/** Props for {@link PointPlotter}. */\nexport interface PointPlotterProps {\n /** Plotted points; caller-owned. */\n value: readonly ControlPoint[];\n /** Fires every frame during drag with the live in-flight value. */\n onInput: (next: ControlPoint[]) => void;\n /** Fires once per discrete user action (drag-end, add, delete) with\n * the new value and the value at gesture start. Wire history here. */\n onChange?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;\n /** Model-space x range. Default [0, 1]. */\n xRange?: readonly [number, number];\n /** Model-space y range. Default [0, 1]. */\n yRange?: readonly [number, number];\n width: number;\n height: number;\n grid?: GridSettings | false | null;\n axes?: AxesSettings | false | null;\n xTicks?: TickSettings | false | null;\n yTicks?: TickSettings | false | null;\n /** Minimum allowed point count. User-initiated deletion is refused\n * while `value.length <= minPoints`. */\n minPoints?: number;\n /** Maximum allowed point count. User-initiated insertion is refused\n * while `value.length >= maxPoints`. */\n maxPoints?: number;\n /** How new points are added. Default `'click-empty'`. Set to `'never'`\n * to disable insertion entirely. */\n addPointMode?: 'click-empty' | 'never';\n /** Built-in undo/redo via keyboard when the component has focus.\n * Default `true`. See `CurveEditor.history` for details. */\n history?: boolean;\n /** Custom per-anchor renderer. See `CurveEditor.renderAnchor`. */\n renderAnchor?: (info: AnchorRenderProps) => ReactNode;\n /** Extra SVG content beneath the anchors. See `CurveEditor.decorations`. */\n decorations?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * 2D point plotter — a CurveEditor with the curve hidden. Anchors,\n * drag, shift+click delete, right-click delete, and click-empty insert\n * are inherited from CurveEditor verbatim, so the two components share\n * gesture semantics and stay in sync as CurveEditor evolves.\n *\n * Differences from CurveEditor: no curve `<path>` drawn, no fill, no\n * endpoint pinning, no interpolation, default `addPointMode` is\n * `'click-empty'` (CurveEditor defaults to `'click-curve'`, which\n * doesn't make sense without a curve to hit-test against).\n */\nexport function PointPlotter(props: PointPlotterProps) {\n const curveProps: CurveEditorProps = {\n ...props,\n addPointMode: props.addPointMode ?? 'click-empty',\n curve: false,\n fill: false,\n endpoints: 'free',\n };\n return <CurveEditor {...curveProps} />;\n}\n"],"mappings":";;;AA2DA,SAAgB,EAAa,GAA0B;AAQrD,QAAO,kBAAC,GAAD;EANL,GAAG;EACH,cAAc,EAAM,gBAAgB;EACpC,OAAO;EACP,MAAM;EACN,WAAW;EAEyB,CAAA"}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { t as e } from "./Badge-DZ0lFs14.js";
|
|
2
|
-
import { jsx as t } from "react/jsx-runtime";
|
|
3
|
-
var n = {
|
|
4
|
-
row: "_row_1a09e_1",
|
|
5
|
-
matrix: "_matrix_1a09e_7",
|
|
6
|
-
segment: "_segment_1a09e_15"
|
|
7
|
-
};
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/components/Powerline/Powerline.tsx
|
|
10
|
-
function r({ segments: r, startCap: i = "flat", size: a, variant: o, depth: s, gap: c, className: l, ...u }) {
|
|
11
|
-
return /* @__PURE__ */ t("span", {
|
|
12
|
-
className: [n.row, l].filter(Boolean).join(" "),
|
|
13
|
-
style: c === void 0 ? void 0 : { "--powerline-gap": typeof c == "number" ? `${c}px` : c },
|
|
14
|
-
"aria-label": u["aria-label"],
|
|
15
|
-
children: r.map((c, l) => /* @__PURE__ */ t(e, {
|
|
16
|
-
base: "powerline",
|
|
17
|
-
baseParams: {
|
|
18
|
-
leftEdge: l === 0 ? i : r[l - 1].endCap ?? "flat",
|
|
19
|
-
rightEdge: c.endCap ?? "flat",
|
|
20
|
-
...s !== void 0 && { depth: s }
|
|
21
|
-
},
|
|
22
|
-
status: c.status,
|
|
23
|
-
stance: c.stance,
|
|
24
|
-
tone: c.tone,
|
|
25
|
-
variant: c.variant ?? o,
|
|
26
|
-
size: c.size ?? a,
|
|
27
|
-
onClick: c.onClick,
|
|
28
|
-
href: c.href,
|
|
29
|
-
"aria-label": c["aria-label"],
|
|
30
|
-
className: n.segment,
|
|
31
|
-
children: c.text
|
|
32
|
-
}, l))
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
//#endregion
|
|
36
|
-
export { r as t };
|
|
37
|
-
|
|
38
|
-
//# sourceMappingURL=Powerline-DBEPceLE.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Powerline-DBEPceLE.js","names":[],"sources":["../../src/components/Powerline/Powerline.module.css","../../src/components/Powerline/Powerline.tsx"],"sourcesContent":[".row {\n display: inline-flex;\n align-items: stretch;\n gap: var(--powerline-gap, 0.2em);\n}\n\n.matrix {\n display: grid;\n gap: var(--wzl-space-6);\n}\n\n/* Reset Badge's opinionated text defaults — Powerline segments should pick up\n * the surrounding text style instead of forcing uppercase / 10px / extra\n * tracking. Consumers can still override via className on Powerline. */\n.segment {\n font-size: inherit;\n text-transform: none;\n letter-spacing: normal;\n}\n","import type { CSSProperties, ReactNode } from 'react';\nimport { Badge } from '../Badge/Badge';\nimport type { BadgeSize, BadgeStatus, BadgeVariant } from '../Badge/types';\nimport type { StanceProps } from '../stance';\nimport type { EdgeCap } from '../Badge/bases/edgeProfiles';\nimport s from './Powerline.module.css';\n\n/**\n * One segment of a {@link Powerline}. Giving it `onClick` or `href` makes it\n * interactive, the same way it does on a `Badge`.\n */\nexport interface PowerlineSegment extends StanceProps {\n text: ReactNode;\n /** Cap on this segment's right edge. Next segment's left edge adopts the same profile. */\n endCap?: EdgeCap;\n status?: BadgeStatus;\n variant?: BadgeVariant;\n size?: BadgeSize;\n onClick?: () => void;\n href?: string;\n 'aria-label'?: string;\n}\n\n/** Props for {@link Powerline}. */\nexport interface PowerlineProps {\n segments: PowerlineSegment[];\n /** Left edge of the first segment. Defaults to 'flat'. */\n startCap?: EdgeCap;\n /** Default size for every segment (per-segment `size` wins). */\n size?: BadgeSize;\n /** Default variant for every segment (per-segment `variant` wins). */\n variant?: BadgeVariant;\n /** Protrusion depth in CSS px, passed through to every segment's base. */\n depth?: number;\n /** Visible gap between adjacent segments. Number → px; string → literal CSS length.\n * Default: `0.2em` (scales with the row's font size). Pass `0` for flush. */\n gap?: number | string;\n className?: string;\n 'aria-label'?: string;\n}\n\n/**\n * A row of chevron-linked badge segments, in the style of a shell powerline\n * prompt. Each segment's `endCap` shapes both its own right edge and the next\n * segment's left edge, so the run reads as one continuous chain.\n *\n * Built for showing a path through a sequence of steps — the inspector uses\n * it to display how input routes from tool to gesture to action to target.\n */\nexport function Powerline({\n segments,\n startCap = 'flat',\n size,\n variant,\n depth,\n gap,\n className,\n ...rest\n}: PowerlineProps) {\n const cls = [s.row, className].filter(Boolean).join(' ');\n const style: CSSProperties | undefined = gap !== undefined\n ? { ['--powerline-gap' as never]: typeof gap === 'number' ? `${gap}px` : gap }\n : undefined;\n return (\n <span className={cls} style={style} aria-label={rest['aria-label']}>\n {segments.map((seg, i) => {\n const leftEdge: EdgeCap = i === 0 ? startCap : (segments[i - 1].endCap ?? 'flat');\n const rightEdge: EdgeCap = seg.endCap ?? 'flat';\n return (\n <Badge\n key={i}\n base=\"powerline\"\n baseParams={{ leftEdge, rightEdge, ...(depth !== undefined && { depth }) }}\n status={seg.status}\n stance={seg.stance}\n tone={seg.tone}\n variant={seg.variant ?? variant}\n size={seg.size ?? size}\n onClick={seg.onClick}\n href={seg.href}\n aria-label={seg['aria-label']}\n className={s.segment}\n >\n {seg.text}\n </Badge>\n );\n })}\n </span>\n );\n}\n"],"mappings":";;;;;;;;;ACiDA,SAAgB,EAAU,EACxB,aACA,cAAW,QACX,SACA,YACA,UACA,QACA,cACA,GAAG,KACc;AAKjB,QACE,kBAAC,QAAD;EAAM,WALI,CAAC,EAAE,KAAK,EAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAKjC;EAAY,OAJU,MAAQ,KAAA,IAE7C,KAAA,IADA,EAAG,mBAA6B,OAAO,KAAQ,WAAW,GAAG,EAAI,MAAM,GAAK;EAG1C,cAAY,EAAK;YAClD,EAAS,KAAK,GAAK,MAIhB,kBAAC,GAAD;GAEE,MAAK;GACL,YAAY;IAAE,UANQ,MAAM,IAAI,IAAY,EAAS,IAAI,GAAG,UAAU;IAM9C,WALD,EAAI,UAAU;IAKF,GAAI,MAAU,KAAA,KAAa,EAAE,UAAO;IAAG;GAC1E,QAAQ,EAAI;GACZ,QAAQ,EAAI;GACZ,MAAM,EAAI;GACV,SAAS,EAAI,WAAW;GACxB,MAAM,EAAI,QAAQ;GAClB,SAAS,EAAI;GACb,MAAM,EAAI;GACV,cAAY,EAAI;GAChB,WAAW,EAAE;aAEZ,EAAI;GACC,EAdD,EAcC,CAEV;EACG,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Prefs-Ctu0H4-b.js","names":[],"sources":["../../src/useScrollSpy.ts","../../src/components/Prefs/Prefs.module.css","../../src/components/Prefs/PrefsRow.tsx","../../src/components/Prefs/PrefsPane.tsx","../../src/components/Prefs/PrefsRail.tsx","../../src/components/Prefs/PrefsForm.tsx","../../src/components/Prefs/PrefsDialog.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type RefObject } from 'react';\n\n/** One observed section: its id and its top edge, in the container's\n * coordinates (its `getBoundingClientRect().top` less the container's). */\nexport interface SectionOffset {\n id: string;\n top: number;\n}\n\n/**\n * The section a reader is looking at: the last one whose top edge has passed\n * the container's, within `slack` of it so a heading flush with the top counts\n * as arrived. Null until one has — the settings above the first section belong\n * to the group itself, and marking a section the reader has not reached yet\n * points them at the wrong place.\n *\n * Offsets arrive in document order and are not re-sorted — a caller reading\n * them off the DOM already has them that way, and sorting would hide a\n * measurement that came back wrong.\n */\nexport function pickActiveSection(\n offsets: readonly SectionOffset[],\n slack = 8,\n): string | null {\n let active: string | null = null;\n for (const { id, top } of offsets) {\n if (top - slack > 0) break;\n active = id;\n }\n return active;\n}\n\n/** Options for {@link useScrollSpy}. */\nexport interface UseScrollSpyOptions {\n /** The scrolling element holding the sections. */\n rootRef: RefObject<HTMLElement | null>;\n /** Section element ids, in document order. */\n ids: readonly string[];\n /** Pixels of tolerance at the container's top edge. */\n slack?: number;\n}\n\n/** What {@link useScrollSpy} returns. */\nexport interface ScrollSpy {\n /** Id of the section in view, or null before anything is measured. */\n active: string | null;\n /**\n * Scroll `id` to the container's top and hold that answer until the scroll\n * settles. Without the hold, the scroll the click causes is observed on its\n * way past every intervening section and the rail flickers through them.\n */\n scrollTo: (id: string) => void;\n}\n\n/** How long to keep a programmatic scroll's answer when `scrollend` never\n * arrives — Safari and Firefox have not shipped it. Comfortably longer than a\n * smooth scroll, short enough that a reader who then scrolls by hand is not\n * left looking at a stale highlight. */\nconst SETTLE_MS = 700;\n\n/**\n * Which of a scrolling container's sections is in view, for a navigation rail\n * that follows the reader. Positions are read on the container's own `scroll`\n * event — already throttled to the frame by the browser — rather than through\n * `IntersectionObserver`, so the decision stays a pure function of measured\n * offsets ({@link pickActiveSection}) and is testable apart from the DOM.\n */\nexport function useScrollSpy(options: UseScrollSpyOptions): ScrollSpy {\n const { rootRef, ids, slack } = options;\n const [active, setActive] = useState<string | null>(null);\n // Set while a programmatic scroll runs: what to report, instead of what the\n // moving container would say.\n const heldRef = useRef<string | null>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const releaseRef = useRef<(() => void) | null>(null);\n const key = ids.join('\\u0000');\n\n const measure = useCallback((): void => {\n const root = rootRef.current;\n if (!root) return;\n if (heldRef.current !== null) {\n setActive(heldRef.current);\n return;\n }\n const rootTop = root.getBoundingClientRect().top;\n const offsets: SectionOffset[] = [];\n for (const id of ids) {\n const el = root.querySelector<HTMLElement>(`[data-spy-section=\"${CSS.escape(id)}\"]`);\n if (el) offsets.push({ id, top: el.getBoundingClientRect().top - rootTop });\n }\n setActive(pickActiveSection(offsets, slack));\n // `ids` is compared by content through `key`: a caller deriving the array\n // each render would otherwise resubscribe on every one.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [rootRef, key, slack]);\n\n useEffect(() => {\n const root = rootRef.current;\n if (!root) return;\n measure();\n root.addEventListener('scroll', measure, { passive: true });\n return () => root.removeEventListener('scroll', measure);\n }, [rootRef, measure]);\n\n const scrollTo = useCallback((id: string): void => {\n const root = rootRef.current;\n if (!root) return;\n const el = root.querySelector<HTMLElement>(`[data-spy-section=\"${CSS.escape(id)}\"]`);\n heldRef.current = id;\n setActive(id);\n const release = (): void => {\n heldRef.current = null;\n root.removeEventListener('scrollend', release);\n measure();\n };\n // A scroll interrupted by the next one would otherwise leave its\n // `scrollend` listener behind to fire against a later scroll.\n releaseRef.current?.();\n if (timerRef.current !== null) clearTimeout(timerRef.current);\n releaseRef.current = release;\n timerRef.current = setTimeout(release, SETTLE_MS);\n root.addEventListener('scrollend', release);\n if (!el) return;\n const top = el.getBoundingClientRect().top - root.getBoundingClientRect().top + root.scrollTop;\n root.scrollTo?.({ top, behavior: prefersReducedMotion() ? 'auto' : 'smooth' });\n }, [rootRef, measure]);\n\n useEffect(() => () => {\n if (timerRef.current !== null) clearTimeout(timerRef.current);\n releaseRef.current?.();\n }, []);\n\n return { active, scrollTo };\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof matchMedia === 'function'\n && matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n","/* Ported from WeaselDraw's wd-prefs-* rules (apps/draw/src/app.css),\n * generalized onto theme tokens. Column width is overridable via\n * --wzl-prefs-column-width at any ancestor scope. */\n\n.columns {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: var(--wzl-space-6);\n overflow-y: auto;\n padding-right: var(--wzl-space-2);\n}\n\n.column {\n flex: 0 0 var(--wzl-prefs-column-width, 300px);\n width: var(--wzl-prefs-column-width, 300px);\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n}\n\n.panel {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n padding: var(--wzl-space-5);\n background: var(--wzl-surface-sunken);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n --wzl-input-surface: var(--wzl-surface);\n}\n\n.subpanel {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-3);\n margin-top: var(--wzl-space-3);\n padding: var(--wzl-space-4) var(--wzl-space-4) var(--wzl-space-4) var(--wzl-space-5);\n background: var(--wzl-surface-sunken);\n border-left: 2px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n --wzl-input-surface: var(--wzl-surface);\n}\n\n.groupHeader {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-1);\n padding-bottom: var(--wzl-space-2);\n border-bottom: 1px solid var(--wzl-border);\n}\n\n.subpanel .groupHeader {\n border-bottom: none;\n padding-bottom: var(--wzl-space-1);\n}\n\n.groupTitle {\n margin: 0;\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-bold);\n color: var(--wzl-fg);\n letter-spacing: var(--wzl-params-label-tracking, var(--wzl-tracking-wider));\n text-transform: var(--wzl-params-label-case, uppercase);\n}\n\n.groupDesc {\n margin: 0;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n line-height: 1.35;\n}\n\n.rows {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-3);\n}\n\n/* A leaf's row is a PropertyRow, inline; this only sets the form's type. */\n.row {\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg);\n cursor: default;\n}\n\n/* Control slot — fixed width so the label can take the rest of the row.\n * Kit form controls render as block-level flex columns that fill it. */\n.rowControl {\n flex: 0 0 110px;\n min-width: 0;\n display: flex;\n align-items: center;\n}\n\n.unrenderable {\n font-family: var(--wzl-font-mono);\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n}\n\n/* PrefsDialog header row: title text + optional headerExtra chrome. */\n.titleRow {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-6);\n flex: 1;\n min-width: 0;\n}\n\n.titleRow > span:first-child {\n flex: 1;\n min-width: 0;\n}\n\n/* An object leaf renders its fields as its own rows. */\n.objectLeaf {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-3);\n inline-size: 100%;\n}\n\n/* Outranks the PropertyRow it styles, which is a flex row. */\n.objectLeaf .objectRow {\n display: grid;\n grid-template-columns: 96px 1fr;\n gap: var(--wzl-space-4);\n align-items: center;\n}\n\n.objectGroup {\n margin: var(--wzl-space-2) 0 0;\n font-size: var(--wzl-font-size-xs);\n font-weight: var(--wzl-font-weight-bold);\n text-transform: var(--wzl-params-label-case, uppercase);\n letter-spacing: var(--wzl-params-label-tracking, var(--wzl-tracking-wide));\n opacity: 0.55;\n}\n\n/* ---------------------------------------------------------------------------\n * Rail layout. The rail is the only chrome: no panels, because the surface\n * the form sits on already has a border and a card inside it restates one.\n * Width is overridable via --wzl-prefs-rail-width at any ancestor scope.\n * ------------------------------------------------------------------------ */\n\n.columnsLayout {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-4);\n min-height: 0;\n}\n\n.railLayout {\n display: flex;\n flex-direction: row;\n align-items: stretch;\n min-height: 0;\n block-size: 100%;\n /* The pane scrolls, not the layout: a rail that scrolled away with the\n settings would be useless exactly when it is needed. */\n overflow: hidden;\n}\n\n/* What `PrefsDialog` hands `Dialog` for its body in rail layout: the pane\n inside does the scrolling and the rail meets the dialog's own edges. */\n.railBody {\n padding: 0;\n overflow: hidden;\n /* A settled height: without one the dialog grows and shrinks as the reader\n moves between a two-row group and a twenty-row one, and the rail entry\n they are aiming at moves while they aim at it. */\n min-block-size: min(60vh, 420px);\n /* Same reason across: a filter that narrows the schema to two rows should\n not pull the dialog's edges in around them. */\n min-inline-size: min(760px, 90vw);\n}\n\n.rail {\n /* Never more than two fifths of the box: at its full width in a narrow\n container the rail takes the space the settings need and leaves a pane\n too thin to hold a row. The clamp belongs on the flex basis — an\n `inline-size` here is simply ignored, which is what made a first attempt\n at this look right and do nothing. */\n flex: 0 1 min(var(--wzl-prefs-rail-width, 216px), 40%);\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-2);\n padding: var(--wzl-space-3) var(--wzl-space-2);\n background: var(--wzl-surface-sunken);\n border-right: 1px solid var(--wzl-border);\n overflow-y: auto;\n /* The rail is itself sunken, so a field taking the default surface would be\n the same color as the rail behind it. */\n --wzl-input-surface: var(--wzl-surface);\n}\n\n.railList {\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.railItem {\n display: flex;\n align-items: center;\n gap: var(--wzl-space-2);\n padding: var(--wzl-space-2) var(--wzl-space-3);\n border: none;\n border-radius: var(--wzl-radius-sm);\n background: none;\n color: var(--wzl-fg);\n font: inherit;\n font-size: var(--wzl-font-size-sm);\n text-align: start;\n cursor: default;\n}\n\n.railItem:hover {\n background: var(--wzl-surface-raised);\n}\n\n.railItem:focus-visible {\n outline: 2px solid var(--wzl-focus-ring);\n outline-offset: -2px;\n}\n\n.railSub {\n padding-left: var(--wzl-space-7);\n color: var(--wzl-fg-subtle);\n}\n\n.railOpen,\n.railOpen:hover {\n background: var(--wzl-accent);\n color: var(--wzl-fg-on-accent);\n}\n\n.railInView {\n color: var(--wzl-fg);\n box-shadow: inset 2px 0 0 var(--wzl-accent);\n}\n\n.railName {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Last in the row: a count that changes as the reader types would otherwise\n shove the name on every keystroke. */\n.railCount {\n margin-left: auto;\n font-size: var(--wzl-font-size-xs);\n font-variant-numeric: tabular-nums;\n opacity: 0.6;\n}\n\n.pane {\n flex: 1;\n min-width: 0;\n overflow-y: auto;\n padding: var(--wzl-space-5) var(--wzl-space-6);\n}\n\n.paneHead {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-1);\n margin-bottom: var(--wzl-space-5);\n}\n\n.paneTitle {\n margin: 0;\n font-size: var(--wzl-font-size-lg);\n font-weight: var(--wzl-font-weight-medium);\n color: var(--wzl-fg);\n}\n\n.paneDesc {\n margin: 0;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n line-height: 1.35;\n}\n\n.paneSection {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-3);\n margin-top: var(--wzl-space-6);\n}\n\n.paneSubsection {\n display: flex;\n flex-direction: column;\n gap: var(--wzl-space-2);\n margin-top: var(--wzl-space-4);\n padding-left: var(--wzl-space-4);\n border-left: 2px solid var(--wzl-border);\n}\n\n.sectionTitle {\n position: sticky;\n /* Sticky offsets are measured from the scrollport's edge, not from inside\n the pane's padding, so this is 0 and the heading's own padding is what\n keeps the text off the edge. */\n top: 0;\n z-index: 1;\n margin: 0;\n padding: var(--wzl-space-3) 0 var(--wzl-space-2);\n background: var(--wzl-surface);\n border-bottom: 1px solid var(--wzl-border);\n font-size: var(--wzl-font-size-sm);\n font-weight: var(--wzl-font-weight-bold);\n color: var(--wzl-fg-subtle);\n letter-spacing: var(--wzl-params-label-tracking, var(--wzl-tracking-wider));\n text-transform: var(--wzl-params-label-case, uppercase);\n}\n\n.subsectionTitle {\n margin: 0;\n font-size: var(--wzl-font-size-xs);\n font-weight: var(--wzl-font-weight-bold);\n color: var(--wzl-fg-subtle);\n letter-spacing: var(--wzl-params-label-tracking, var(--wzl-tracking-wide));\n text-transform: var(--wzl-params-label-case, uppercase);\n}\n\n/* In the rail layout a row is a grid, so the control track grows with the\n pane instead of holding at the columns layout's fixed slot. */\n.railLayout .row {\n display: grid;\n /* The control track is capped rather than taking the rest: a number field\n as wide as the pane reads as a text area, and every row's control starts\n at the same x either way. */\n grid-template-columns:\n minmax(140px, var(--wzl-params-label-width, 240px))\n minmax(180px, 260px);\n align-items: center;\n}\n\n.railLayout .rowControl {\n flex: 1 1 auto;\n}\n\n.filter {\n padding: 0 var(--wzl-space-1) var(--wzl-space-2);\n}\n\n.noMatches {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: var(--wzl-space-3);\n padding: var(--wzl-space-6) 0;\n font-size: var(--wzl-font-size-sm);\n color: var(--wzl-fg-subtle);\n}\n\n.noMatches p {\n margin: 0;\n}\n\n.clearFilter {\n padding: var(--wzl-space-1) var(--wzl-space-3);\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n background: var(--wzl-surface-raised);\n color: var(--wzl-fg);\n font: inherit;\n font-size: var(--wzl-font-size-sm);\n}\n","import { type ReactNode } from 'react';\nimport { PropertyControl } from '../Properties/PropertyField';\nimport { PropertyRow } from '../Properties/PropertyPanel';\nimport { prefFieldProps } from './prefField';\nimport {\n isPrefLeaf,\n prefValueAtPath,\n type PrefGroup,\n type PrefLeaf,\n type PrefObject,\n} from './schema';\nimport s from './Prefs.module.css';\n\n/** What a {@link PrefRenderer} is given for the leaf it is rendering. */\nexport interface PrefRenderContext {\n /** Dotted path of the leaf within the schema root. */\n path: string;\n /** The schema node. App renderers narrow this to their own kind shape. */\n pref: PrefLeaf;\n /** Current value — `values` at `path`, falling back to `pref.default`. */\n value: unknown;\n setValue: (value: unknown) => void;\n /** Whether this leaf is currently auto — not pinned, computed by the owner. */\n auto: boolean;\n /** Toggle this leaf's auto state. */\n setAuto: (next: boolean) => void;\n}\n\n/**\n * Renders the control cell for one preference leaf. Returning `null`\n * collapses the row.\n */\nexport type PrefRenderer = (ctx: PrefRenderContext) => ReactNode;\n\nexport interface WalkCtx {\n values: unknown;\n onChange: (path: string, value: unknown) => void;\n renderers?: Record<string, PrefRenderer>;\n}\n\nexport function PrefRow({ ctx, path, pref }: { ctx: WalkCtx; path: string; pref: PrefLeaf }) {\n const stored = prefValueAtPath(ctx.values, path);\n const renderCtx: PrefRenderContext = {\n path,\n pref,\n value: stored !== undefined ? stored : pref.default,\n setValue: (v) => ctx.onChange(path, v),\n // A prefs form pins every leaf: it has no computed state to hand back.\n auto: false,\n setAuto: () => {},\n };\n\n const custom = ctx.renderers?.[pref.kind];\n const control = custom ? custom(renderCtx) : renderBuiltin(renderCtx);\n if (custom && control === null) return null;\n\n // `block` leaves own their chrome (embedded editors with their own\n // header) — no label/tooltip row.\n if (pref.block) return <>{control}</>;\n\n return (\n <PropertyRow\n label={pref.name}\n description={pref.description}\n layout=\"inline\"\n chrome=\"framed\"\n className={s.row}\n >\n <span className={s.rowControl}>{control}</span>\n </PropertyRow>\n );\n}\n\nfunction renderBuiltin(\n ctx: PrefRenderContext,\n // The object a nested leaf is a field of — what an enum `encoding` and a\n // font's weight and slant read against. Undefined for a top-level leaf.\n siblings?: Record<string, unknown>,\n): ReactNode {\n const { pref, value, setValue } = ctx;\n if (pref.kind === 'object') return <ObjectLeaf ctx={ctx} />;\n const field = prefFieldProps(pref, { value, siblings, setValue });\n if (field === null) {\n // App-defined kind with no `renderers` entry: labeled placeholder, not a\n // crash — a missing wiring should be visible and recoverable.\n return <span className={s.unrenderable}>({pref.kind}: no renderer)</span>;\n }\n return <PropertyControl {...field} chrome=\"framed\" name={pref.name} />;\n}\n\n/** One value with its fields hanging off it: each field renders its own\n * control and commits the parent object whole. */\nfunction ObjectLeaf({ ctx }: { ctx: PrefRenderContext }) {\n const pref = ctx.pref as PrefObject;\n const { value, setValue } = ctx;\n const held = typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n const objectRows = (children: Record<string, PrefLeaf | PrefGroup>): ReactNode[] => {\n const out: ReactNode[] = [];\n for (const [key, child] of Object.entries(children)) {\n if (!isPrefLeaf(child)) {\n const inner = objectRows(child.children);\n if (inner.length === 0) continue;\n out.push(<h4 key={`group:${key}`} className={s.objectGroup}>{child.name}</h4>, ...inner);\n continue;\n }\n out.push(\n <PropertyRow\n key={key}\n label={child.name}\n layout=\"inline\"\n chrome=\"framed\"\n className={s.objectRow}\n >\n <span className={s.rowControl}>\n {renderBuiltin({\n path: `${ctx.path}.${key}`,\n pref: child,\n value: held?.[key],\n setValue: (v) => {\n const base = held ?? pref.fromScalar?.(value) ?? {};\n setValue({ ...base, [key]: v });\n },\n // A field is not pinned on its own — it shares the state of\n // the object leaf it hangs off.\n auto: ctx.auto,\n setAuto: ctx.setAuto,\n }, held)}\n </span>\n </PropertyRow>,\n );\n }\n return out;\n };\n return <div className={s.objectLeaf}>{objectRows(pref.children)}</div>;\n}\n","import { type RefObject } from 'react';\nimport { isPrefLeaf, type PrefGroup, type PrefLeaf } from './schema';\nimport { PrefRow, type WalkCtx } from './PrefsRow';\nimport s from './Prefs.module.css';\n\n/** Props for {@link PrefsPane}. */\nexport interface PrefsPaneProps {\n ctx: WalkCtx;\n /** The open group, or a synthetic one holding the root's loose leaves. */\n group: PrefGroup;\n /** Dotted path of `group`. Empty for the loose-leaf pane. */\n path: string;\n /** The pane's scroll container, for the rail's scroll spy. */\n scrollRef: RefObject<HTMLDivElement | null>;\n className?: string;\n}\n\n/**\n * One rail section's settings: the group's own leaves, then one headed\n * section per nested group.\n *\n * Nothing here draws a panel. The rail is the layout's structure, and a card\n * inside a dialog that already has a border only restates it — the columns\n * layout's sunken panels are what this is the alternative to.\n */\nexport function PrefsPane(props: PrefsPaneProps) {\n const { ctx, group, path, scrollRef, className } = props;\n const leaves = Object.entries(group.children).filter(\n (entry): entry is [string, PrefLeaf] => isPrefLeaf(entry[1]),\n );\n const groups = Object.entries(group.children).filter(\n (entry): entry is [string, PrefGroup] => !isPrefLeaf(entry[1]),\n );\n const childPath = (key: string): string => (path === '' ? key : `${path}.${key}`);\n\n return (\n <div className={[s.pane, className].filter(Boolean).join(' ')} ref={scrollRef}>\n <div className={s.paneHead}>\n <h3 className={s.paneTitle}>{group.name}</h3>\n {group.description !== undefined && (\n <p className={s.paneDesc}>{group.description}</p>\n )}\n </div>\n {leaves.length > 0 && (\n <div className={s.rows}>\n {leaves.map(([key, leaf]) => (\n <PrefRow key={key} ctx={ctx} path={childPath(key)} pref={leaf} />\n ))}\n </div>\n )}\n {groups.map(([key, child]) => (\n <PaneSection\n key={key}\n ctx={ctx}\n group={child}\n path={childPath(key)}\n depth={0}\n />\n ))}\n </div>\n );\n}\n\n/**\n * A nested group inside a pane. Depth 0 sections are the ones the rail links\n * to and carry `data-spy-section`; deeper ones indent and are reached only by\n * scrolling, which is what keeps the rail two levels deep whatever the schema\n * does.\n */\nfunction PaneSection({ ctx, group, path, depth }: {\n ctx: WalkCtx;\n group: PrefGroup;\n path: string;\n depth: number;\n}) {\n const Heading = depth === 0 ? 'h4' : 'h5';\n return (\n <section\n className={depth === 0 ? s.paneSection : s.paneSubsection}\n data-spy-section={depth === 0 ? path : undefined}\n aria-label={group.name}\n >\n <Heading className={depth === 0 ? s.sectionTitle : s.subsectionTitle}>\n {group.name}\n </Heading>\n {group.description !== undefined && (\n <p className={s.paneDesc}>{group.description}</p>\n )}\n <div className={s.rows}>\n {Object.entries(group.children).map(([key, child]) => {\n const p = `${path}.${key}`;\n return isPrefLeaf(child) ? (\n <PrefRow key={key} ctx={ctx} path={p} pref={child} />\n ) : (\n <PaneSection key={key} ctx={ctx} group={child} path={p} depth={depth + 1} />\n );\n })}\n </div>\n </section>\n );\n}\n","import type { ReactNode } from 'react';\nimport { useRovingTabIndex } from '../../useRovingTabIndex';\nimport type { PrefRailItem } from './schema';\nimport s from './Prefs.module.css';\n\n/** Props for {@link PrefsRail}. */\nexport interface PrefsRailProps {\n items: readonly PrefRailItem[];\n /** Path of the open depth-0 group. */\n section: string;\n /** Path of the depth-1 group in view, from the pane's scroll spy. */\n current: string | null;\n /** A depth-0 entry was chosen: open its pane. */\n onOpen: (path: string) => void;\n /** A depth-1 entry was chosen: scroll the open pane to it. */\n onScrollTo: (path: string) => void;\n /** Accessible name for the rail's landmark. */\n ariaLabel: string;\n /** Rendered above the list — the filter field, when there is one. */\n header?: ReactNode;\n /** Show each entry's match count. Only meaningful while filtering. */\n showCounts?: boolean;\n}\n\n/**\n * The rail beside a `PrefsForm` pane: depth-0 groups, each followed by the\n * depth-1 groups that scroll within it.\n *\n * A list of buttons rather than a tablist, because only half of these entries\n * switch anything — the nested ones scroll the pane the selected entry already\n * opened, and a tab that does not own a panel has no honest role. The open\n * group is `aria-current=\"page\"`; the nested group in view is\n * `aria-current=\"location\"`.\n */\nexport function PrefsRail(props: PrefsRailProps) {\n const { items, section, current, onOpen, onScrollTo, ariaLabel, header, showCounts } = props;\n const activate = (index: number): void => {\n const item = items[index];\n if (!item) return;\n if (item.depth === 0) onOpen(item.path);\n else onScrollTo(item.path);\n };\n const roving = useRovingTabIndex<HTMLDivElement>({\n items: items.map(() => ({})),\n itemClassName: s.railItem,\n tabStopIndex: Math.max(0, items.findIndex((i) => i.path === section)),\n });\n\n return (\n <nav className={s.rail} aria-label={ariaLabel}>\n {header}\n <div className={s.railList} ref={roving.rootRef}>\n {items.map((item, index) => {\n const open = item.depth === 0 && item.path === section;\n const inView = item.depth === 1 && item.path === current;\n return (\n <button\n key={item.path === '' ? '\\u0000root' : item.path}\n type=\"button\"\n className={[s.railItem, item.depth === 1 && s.railSub, open && s.railOpen, inView && s.railInView]\n .filter(Boolean)\n .join(' ')}\n aria-current={open ? 'page' : inView ? 'location' : undefined}\n tabIndex={roving.tabIndexFor(index)}\n onKeyDown={roving.onKeyDown(index)}\n onClick={() => activate(index)}\n >\n <span className={s.railName}>{item.name}</span>\n {showCounts ? <span className={s.railCount}>{item.matches}</span> : null}\n </button>\n );\n })}\n </div>\n </nav>\n );\n}\n","import { useMemo, useRef, useState, type ReactNode } from 'react';\nimport { Input } from '../Input';\nimport { useScrollSpy } from '../../useScrollSpy';\nimport {\n filterPrefSubtree,\n isPrefLeaf,\n prefRailItems,\n visiblePrefSubtree,\n type PrefGroup,\n} from './schema';\nimport { PrefRow, type PrefRenderer, type WalkCtx } from './PrefsRow';\nimport { PrefsPane } from './PrefsPane';\nimport { PrefsRail } from './PrefsRail';\nimport s from './Prefs.module.css';\n\nexport type { PrefRenderer, PrefRenderContext } from './PrefsRow';\n\n/** How a {@link PrefsForm} lays its groups out. */\nexport type PrefsLayout = 'columns' | 'rail';\n\n/** Props for {@link PrefsForm}. */\nexport interface PrefsFormProps {\n /** Root of the schema tree. Core `ToolPrefGroup`s assign structurally. */\n schema: PrefGroup;\n /** Nested value tree (shape mirrors the schema). Sparse is fine —\n * missing leaves fall back to their schema `default`. */\n values?: unknown;\n /** Change callback with the leaf's dotted path. Values apply live;\n * there is no dirty/commit state. */\n onChange: (path: string, value: unknown) => void;\n /**\n * Per-kind renderers for app-defined kinds. Entries also override the\n * built-in kinds when keys collide. A renderer owns the control cell\n * (the label/tooltip row chrome stays with the form, unless the leaf\n * sets `block`); returning `null` collapses the row entirely. Unknown\n * kinds with no renderer show a labeled placeholder instead of\n * crashing.\n */\n renderers?: Record<string, PrefRenderer>;\n /** Reveal `hidden` leaves (dev tooling). Default false. */\n showHidden?: boolean;\n /**\n * `'columns'` (the default) wraps each top-level group into its own panel\n * column. `'rail'` puts a two-level navigation rail beside one group's\n * settings at a time — what a dialog-sized surface wants, since columns\n * overflow sideways once there are more than two.\n */\n layout?: PrefsLayout;\n /** Show a filter field that narrows the form to matching leaves. */\n filterable?: boolean;\n /** Rail layout: path of the open top-level group. Controlled. */\n section?: string;\n /** Rail layout: path of the group open before the reader picks one.\n * Defaults to the first in the schema. */\n defaultSection?: string;\n onSectionChange?: (path: string) => void;\n className?: string;\n}\n\n/**\n * Schema-driven preferences form. Leaves render as label + control rows;\n * how the groups around them are arranged is `layout`'s to say — columns of\n * panels, or a navigation rail beside one group at a time.\n *\n * Storage-agnostic: pair with `PrefsDialog` for the modal composition, and\n * persist however the app likes via `onChange`.\n */\nexport function PrefsForm(props: PrefsFormProps) {\n const {\n schema,\n values,\n onChange,\n renderers,\n showHidden = false,\n layout = 'columns',\n filterable = false,\n className,\n } = props;\n const [query, setQuery] = useState('');\n const root = useMemo(() => {\n const visible = visiblePrefSubtree(schema, showHidden);\n return visible === null ? null : filterPrefSubtree(visible, query);\n }, [schema, showHidden, query]);\n\n const ctx: WalkCtx = { values, onChange, renderers };\n const field = filterable ? (\n <div className={s.filter}>\n <Input\n value={query}\n onChange={setQuery}\n aria-label={`Filter ${schema.name}`}\n placeholder=\"Filter settings\"\n />\n </div>\n ) : null;\n\n if (layout === 'rail') {\n return (\n <RailLayout\n {...props}\n root={root}\n ctx={ctx}\n query={query}\n filterField={field}\n onClearFilter={() => setQuery('')}\n />\n );\n }\n\n return (\n <div className={[s.columnsLayout, className].filter(Boolean).join(' ')}>\n {field}\n {root === null ? (\n <NoMatches query={query} onClear={() => setQuery('')} />\n ) : (\n <div className={s.columns}>\n {Object.entries(root.children).map(([key, child]) => (\n <div key={key} className={s.column}>\n {isPrefLeaf(child) ? (\n // Top-level leaves are unusual but legal — give each its own\n // column for symmetry with grouped siblings.\n <PrefRow ctx={ctx} path={key} pref={child} />\n ) : (\n <GroupBody ctx={ctx} group={child} path={key} depth={0} />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n/** The rail layout's own state: which group is open, and where the pane is\n * scrolled to within it. Split out so the columns layout runs none of it. */\nfunction RailLayout(props: PrefsFormProps & {\n root: PrefGroup | null;\n ctx: WalkCtx;\n query: string;\n filterField: ReactNode;\n onClearFilter: () => void;\n}) {\n const { schema, root, ctx, query, filterField, onClearFilter, className } = props;\n const items = useMemo(() => (root === null ? [] : prefRailItems(root)), [root]);\n const [uncontrolled, setUncontrolled] = useState(\n () => props.defaultSection ?? '',\n );\n const requested = props.section ?? uncontrolled;\n // A filter can take the open group out of the rail entirely, and a section\n // the schema never had can arrive from a consumer's stale state. Either way\n // the first surviving entry is what the reader should be looking at.\n const open = items.some((i) => i.path === requested && i.depth === 0)\n ? requested\n : (items.find((i) => i.depth === 0)?.path ?? '');\n\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const sectionIds = useMemo(\n () => items.filter((i) => i.depth === 1 && i.section === open).map((i) => i.path),\n [items, open],\n );\n const spy = useScrollSpy({ rootRef: scrollRef, ids: sectionIds });\n\n const setOpen = (path: string): void => {\n if (props.section === undefined) setUncontrolled(path);\n props.onSectionChange?.(path);\n // Optional-called: jsdom's elements have no `scrollTo`, and neither does\n // a pane that is not the scrolling box in some consumer's layout.\n scrollRef.current?.scrollTo?.({ top: 0 });\n };\n\n const group = root === null ? null : paneGroup(root, open, schema.name);\n\n return (\n <div className={[s.railLayout, className].filter(Boolean).join(' ')}>\n <PrefsRail\n items={items}\n section={open}\n current={spy.active}\n onOpen={setOpen}\n onScrollTo={spy.scrollTo}\n ariaLabel={schema.name}\n header={filterField}\n showCounts={query.trim() !== ''}\n />\n {group === null ? (\n <div className={s.pane}>\n <NoMatches query={query} onClear={onClearFilter} />\n </div>\n ) : (\n <PrefsPane ctx={ctx} group={group} path={open} scrollRef={scrollRef} />\n )}\n </div>\n );\n}\n\n/**\n * The group a rail path opens. The empty path is the root's loose leaves,\n * which belong to no group of their own and are handed back as one named for\n * the schema — the rail lists them under the same name.\n */\nfunction paneGroup(root: PrefGroup, path: string, rootName: string): PrefGroup | null {\n if (path === '') {\n const children = Object.fromEntries(\n Object.entries(root.children).filter(([, child]) => isPrefLeaf(child)),\n );\n return Object.keys(children).length === 0\n ? null\n : { ...root, name: rootName, children };\n }\n let node: PrefGroup | undefined;\n let cursor = root;\n for (const seg of path.split('.')) {\n const next = cursor.children[seg];\n if (next === undefined || isPrefLeaf(next)) return null;\n node = next;\n cursor = next;\n }\n return node ?? null;\n}\n\n/** What a filter matching nothing leaves behind. */\nfunction NoMatches({ query, onClear }: { query: string; onClear: () => void }) {\n return (\n <div className={s.noMatches}>\n <p>No settings match “{query.trim()}”.</p>\n <button type=\"button\" className={s.clearFilter} onClick={onClear}>\n Clear filter\n </button>\n </div>\n );\n}\n\nfunction GroupBody({ ctx, group, path, depth }: {\n ctx: WalkCtx;\n group: PrefGroup;\n path: string;\n depth: number;\n}) {\n return (\n <div className={depth === 0 ? s.panel : s.subpanel}>\n <div className={s.groupHeader}>\n <h3 className={s.groupTitle}>{group.name}</h3>\n {group.description !== undefined && (\n <p className={s.groupDesc}>{group.description}</p>\n )}\n </div>\n <div className={s.rows}>\n {Object.entries(group.children).map(([key, child]) => {\n const childPath = `${path}.${key}`;\n return isPrefLeaf(child) ? (\n <PrefRow key={key} ctx={ctx} path={childPath} pref={child} />\n ) : (\n <GroupBody key={key} ctx={ctx} group={child} path={childPath} depth={depth + 1} />\n );\n })}\n </div>\n </div>\n );\n}\n\n","import type { ReactNode } from 'react';\nimport { Dialog } from '../Dialog';\nimport { PrefsForm, type PrefsFormProps } from './PrefsForm';\nimport s from './Prefs.module.css';\n\n/**\n * Props for {@link PrefsDialog} — everything {@link PrefsForm} takes, plus\n * the dialog's own open state and chrome.\n */\nexport interface PrefsDialogProps extends PrefsFormProps {\n isOpen: boolean;\n onOpenChange: (open: boolean) => void;\n /** Dialog heading. Defaults to the schema root's `name`. */\n title?: ReactNode;\n /** Extra chrome rendered at the end of the title row (e.g. a dev-mode\n * \"Show hidden\" switch). An action the reader acts on when they are done\n * — resetting, importing — belongs in `footer` instead, where a dialog's\n * actions live. */\n headerExtra?: ReactNode;\n /** Footer slot, passed through to `Dialog` — typically a reset or done\n * action. */\n footer?: ReactNode;\n /** Class applied to the dialog's modal box. */\n dialogClassName?: string;\n}\n\n/**\n * The kit's preferences dialog: `Dialog` + `PrefsForm`. Changes apply\n * live through `onChange` — there is no OK/Cancel staging. For custom\n * placement (sidebar, popover, inline page), compose `PrefsForm`\n * directly; this wrapper is intentionally thin.\n *\n * `layout=\"rail\"` is what a dialog usually wants: the default columns wrap\n * sideways past two groups, and this box is 900px at its widest.\n */\nexport function PrefsDialog(props: PrefsDialogProps) {\n const { isOpen, onOpenChange, title, headerExtra, footer, dialogClassName, ...form } = props;\n const rail = form.layout === 'rail';\n return (\n <Dialog\n isOpen={isOpen}\n onOpenChange={onOpenChange}\n aria-label={typeof title === 'string' ? title : form.schema.name}\n className={dialogClassName}\n footer={footer}\n bodyClassName={rail ? s.railBody : undefined}\n title={\n headerExtra !== undefined ? (\n <span className={s.titleRow}>\n <span>{title ?? form.schema.name}</span>\n {headerExtra}\n </span>\n ) : (\n title ?? form.schema.name\n )\n }\n >\n <PrefsForm {...form} />\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Properties-Bbneh1ws.js","names":[],"sources":["../../src/components/Properties/CurveField.module.css","../../src/components/Properties/CurveField.tsx","../../src/components/Properties/DialogRow.tsx","../../src/components/Properties/PropertyGroup.tsx","../../src/components/Properties/Subpanel.tsx"],"sourcesContent":[".field {\n display: grid;\n gap: var(--wzl-space-3);\n grid-column: 1 / -1;\n box-sizing: border-box;\n}\n\n.field * {\n box-sizing: border-box;\n}\n\n.plot {\n position: relative;\n width: 100%;\n}\n\n.marks {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.actions {\n display: flex;\n gap: var(--wzl-space-3);\n justify-self: start;\n}\n\n.action {\n background: transparent;\n border: 1px solid var(--wzl-border);\n border-radius: var(--wzl-radius-sm);\n color: var(--wzl-fg);\n padding: var(--wzl-space-2) var(--wzl-space-5);\n font-size: var(--wzl-font-size-sm);\n cursor: pointer;\n}\n\n.action:hover {\n background: var(--wzl-surface-raised);\n}\n","import { useCallback, useMemo } from 'react';\nimport { dlog } from '../../dlog';\nimport { type ControlPoint, CurveEditor } from '../CurveEditor';\nimport s from './CurveField.module.css';\n\n/** An annotation drawn over a curve plot: a shaded band across a range of x,\n * or a vertical line at one x. */\nexport type CurveMark =\n | { kind: 'band'; x: [number, number]; color?: string }\n | { kind: 'line'; x: number; color?: string };\n\n/** Props for `<CurveField>`. */\nexport interface CurveFieldProps {\n /** Flat [x0, y0, x1, y1, …] — matches how curve-as-array configs\n * serialize in JSON snapshots. */\n values: number[];\n min: number;\n max: number;\n step: number;\n /** Plot width in CSS px. Caller sizes (typically uses a ResizeObserver). */\n width: number;\n /** Plot height in CSS px. Default 110. */\n height?: number;\n /** Flat default curve. When provided, a Reset button restores it. */\n defaults?: readonly number[];\n onChange: (next: number[]) => void;\n /** Optional vertical marks overlaid on the plot (e.g. landmarks). */\n marks?: readonly CurveMark[];\n}\n\n/**\n * A function-domain (y = f(x), x ∈ [0,1]) curve editor with per-stop\n * numeric readouts and flip buttons. Wraps weasel-ui's\n * `CurveEditor`; consumers wanting the raw editor (2D paths,\n * custom anchors) should import that instead.\n */\nexport function CurveField({\n values,\n min,\n max,\n step,\n width,\n height = 110,\n defaults,\n onChange,\n marks,\n}: CurveFieldProps) {\n const points: ControlPoint[] = useMemo(() => {\n const out: ControlPoint[] = [];\n for (let i = 0; i + 1 < values.length; i += 2) out.push({ x: values[i], y: values[i + 1] });\n return out;\n }, [values]);\n\n const handleChange = useCallback(\n (next: ControlPoint[]) => {\n const flat: number[] = new Array(next.length * 2);\n for (let i = 0; i < next.length; i++) {\n const ySnap = Math.round(next[i].y / step) * step;\n const y = Math.max(min, Math.min(max, ySnap));\n flat[i * 2] = next[i].x;\n flat[i * 2 + 1] = y;\n }\n dlog('curve-field', 'handleChange', {\n nLen: next.length,\n flatLen: flat.length,\n sample: flat.slice(0, 4),\n });\n onChange(flat);\n },\n [onChange, min, max, step],\n );\n\n const handleFlip = useCallback(() => {\n const flipped: Array<{ x: number; y: number }> = [];\n for (let i = 0; i + 1 < values.length; i += 2) {\n flipped.push({ x: 1 - values[i], y: values[i + 1] });\n }\n flipped.sort((a, b) => a.x - b.x);\n const out: number[] = [];\n for (const p of flipped) out.push(p.x, p.y);\n onChange(out);\n }, [values, onChange]);\n\n const handleFlipVertical = useCallback(() => {\n const mid = (min + max) / 2;\n const flipped: number[] = new Array(values.length);\n for (let i = 0; i + 1 < values.length; i += 2) {\n flipped[i] = values[i];\n flipped[i + 1] = 2 * mid - values[i + 1];\n }\n onChange(flipped);\n }, [values, min, max, onChange]);\n\n return (\n <div className={s.field}>\n <div className={s.plot}>\n <CurveEditor\n value={points}\n onInput={handleChange}\n domain=\"1d\"\n constrain=\"function\"\n xRange={[0, 1]}\n yRange={[min, max]}\n width={width}\n height={height}\n endpoints=\"pinned-x\"\n addPointMode=\"click-curve\"\n minPoints={2}\n grid={{}}\n history={false}\n />\n {marks && marks.length > 0 && (\n <svg\n className={s.marks}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n aria-hidden=\"true\"\n >\n {marks.map((m) => {\n const color = m.color ?? '#ffcc00';\n if (m.kind === 'band') {\n const x0 = Math.max(0, Math.min(1, m.x[0])) * width;\n const x1 = Math.max(0, Math.min(1, m.x[1])) * width;\n const left = Math.min(x0, x1);\n const w = Math.abs(x1 - x0);\n return (\n <g key={`band-${m.x[0]}-${m.x[1]}-${color}`}>\n <rect x={left} y={0} width={w} height={height} fill={color} fillOpacity={0.4} />\n <line\n x1={left}\n y1={0}\n x2={left}\n y2={height}\n stroke={color}\n strokeOpacity={0.85}\n strokeWidth={1}\n />\n <line\n x1={left + w}\n y1={0}\n x2={left + w}\n y2={height}\n stroke={color}\n strokeOpacity={0.85}\n strokeWidth={1}\n />\n </g>\n );\n }\n const x = Math.max(0, Math.min(1, m.x)) * width;\n return (\n <line\n key={`mark-${m.x}-${color}`}\n x1={x}\n y1={0}\n x2={x}\n y2={height}\n stroke={color}\n strokeWidth={1}\n />\n );\n })}\n </svg>\n )}\n </div>\n <div className={s.actions}>\n <button type=\"button\" className={s.action} onClick={handleFlip}>\n Flip horizontally\n </button>\n <button type=\"button\" className={s.action} onClick={handleFlipVertical}>\n Flip vertically\n </button>\n {defaults && (\n <button\n type=\"button\"\n className={s.action}\n onClick={() => onChange([...defaults])}\n >\n Reset\n </button>\n )}\n </div>\n </div>\n );\n}\n","import { type ReactNode, useState } from 'react';\nimport { Button } from '../Button';\nimport { Dialog } from '../Dialog';\nimport { type PropertyMetricProps, type PropertyRowLayout, PropertyRow } from './PropertyPanel';\nimport s from './Properties.module.css';\n\n/** Props for `<DialogRow>`. */\nexport interface DialogRowProps extends PropertyMetricProps {\n label: ReactNode;\n /** What the row shows of the value, on the button that opens the dialog. One\n * line; a longer one is cut off with an ellipsis. */\n summary: ReactNode;\n /** The dialog's body. A function runs only while the dialog is open, as a\n * component — it may use hooks — and is handed `close`. */\n children: ReactNode | ((close: () => void) => ReactNode);\n /** The dialog's heading. Defaults to `label`. */\n title?: ReactNode;\n /** Dialog footer. Defaults to a Done button; `null` draws none. */\n footer?: ReactNode | null;\n layout?: PropertyRowLayout;\n description?: string;\n /** Right-aligned readout shown next to the label — see `<PropertyRow readout>`. */\n readout?: ReactNode;\n /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */\n span?: boolean;\n /** The row is auto — see `<PropertyRow auto>`. */\n auto?: boolean;\n /** Toggles `auto` from the row's label — see `<PropertyRow onAutoChange>`. */\n onAutoChange?: (next: boolean) => void;\n}\n\n/**\n * A row for a value too big to edit in place: it shows a one-line summary on a\n * button, and the button opens a modal holding whatever editor the row is\n * given. The body edits live — the dialog is room, not a transaction.\n */\nexport function DialogRow({\n label,\n summary,\n children,\n title,\n footer,\n layout,\n description,\n readout,\n span,\n density,\n align,\n auto,\n onAutoChange,\n}: DialogRowProps) {\n const [open, setOpen] = useState(false);\n const close = (): void => setOpen(false);\n const name = typeof label === 'string' ? label : undefined;\n return (\n <PropertyRow\n label={label}\n readout={readout}\n layout={layout}\n description={description}\n span={span}\n density={density}\n align={align}\n auto={auto}\n onAutoChange={onAutoChange}\n // A `<label>` hands a click on its text to its button, which would open\n // the dialog from the label — and the label is the auto toggle.\n group\n >\n <button\n type=\"button\"\n className={s.dialogTrigger}\n aria-haspopup=\"dialog\"\n aria-label={name === undefined ? undefined : `Edit ${name}`}\n onClick={() => setOpen(true)}\n >\n <span className={s.dialogSummary}>{summary}</span>\n <span className={s.dialogGlyph} aria-hidden=\"true\">\n …\n </span>\n </button>\n <Dialog\n isOpen={open}\n onOpenChange={setOpen}\n title={title ?? label}\n footer={\n footer === undefined ? (\n <Button variant=\"primary\" onClick={close}>\n Done\n </Button>\n ) : (\n (footer ?? undefined)\n )\n }\n >\n {typeof children === 'function' ? <DialogBody render={children} close={close} /> : children}\n </Dialog>\n </PropertyRow>\n );\n}\n\n/** Calls a function body as a component, so it runs only while the dialog is\n * mounted and may use hooks of its own. */\nfunction DialogBody({\n render,\n close,\n}: {\n render: (close: () => void) => ReactNode;\n close: () => void;\n}) {\n return render(close);\n}\n","import { type ReactNode, useId, useState } from 'react';\nimport { DisclosureRow } from '../Disclosure';\nimport { type StanceProps, useStance } from '../stance';\nimport s from './Properties.module.css';\nimport {\n type PropertyListPack,\n type PropertyMetricProps,\n PropertyNote,\n propertyMetricClass,\n} from './PropertyPanel';\n\n/** Props for `<PropertyGroup>`. */\nexport interface PropertyGroupProps extends PropertyMetricProps, StanceProps {\n /** Title rendered between two rules at the top of the group. */\n title: ReactNode;\n /** Before the title: a drag handle, an ordinal. Clicks here do not fold the group. */\n leading?: ReactNode;\n /** After the title: a summary of the group's value, a remove button. Clicks here do not fold the group. */\n actions?: ReactNode;\n /** Help text for the whole group, drawn under the title and above the\n * rows. `<PropertyRow description>` covers the per-row case. */\n description?: ReactNode;\n /** When true the group renders nothing — useful for conditional sections. */\n hidden?: boolean;\n /**\n * Give the title a twisty that folds the rows away. Implied by any of the\n * three props below, so it is only needed for a group that starts open and\n * keeps its own state.\n */\n collapsible?: boolean;\n /** Start folded. Read once; the group owns the state from then on. */\n defaultCollapsed?: boolean;\n /**\n * Folded or not, as the consumer holds it. Given, the group keeps no state\n * of its own and every toggle arrives at `onCollapsedChange` instead — which\n * is what a panel that remembers its sections past a reload needs.\n */\n collapsed?: boolean;\n onCollapsedChange?: (next: boolean) => void;\n children: ReactNode;\n className?: string;\n /** Take the whole width of a packed grid, like `<PropertyRow span>`: a group\n * lays its own rows out, so half a column leaves them overlapping. */\n span?: boolean;\n /** How rows pack into the 2-column grid — see `<PropertyList pack>`. */\n pack?: PropertyListPack;\n}\n\n/**\n * Visually-bordered group inside a PropertyList. Use to scope a set of\n * related rows under a heading (e.g. \"Aqua\", \"Bevel\", \"Dome\" sections\n * inside a fill effect's controls).\n *\n * A collapsible group keeps its rows mounted and hides them, so a control's\n * local state survives being folded away. `stance` and `tone` work as on\n * `<PropertyPanel>`; a tone also draws the group's leading edge in it, which\n * is how a list of like groups — effects, tails — tells its members apart.\n * `leading` and `actions` put a handle and controls in the title row, and the\n * groups of a `<PropertyList>` reorder with `useReorderDragList`.\n */\nexport function PropertyGroup({\n title,\n leading,\n actions,\n description,\n hidden,\n collapsible,\n defaultCollapsed,\n collapsed,\n onCollapsedChange,\n children,\n className,\n span,\n pack = 'auto-color',\n density,\n align,\n stance,\n tone,\n}: PropertyGroupProps) {\n const bodyId = useId();\n const [own, setOwn] = useState(defaultCollapsed ?? false);\n const stanced = useStance({ stance, tone });\n if (hidden) return null;\n\n const folds =\n collapsible ??\n (defaultCollapsed !== undefined || collapsed !== undefined || onCollapsedChange !== undefined);\n const folded = folds && (collapsed ?? own);\n const toggle = () => {\n if (collapsed === undefined) setOwn(!folded);\n onCollapsedChange?.(!folded);\n };\n\n const base = `${s.group}${pack === 'pairs' ? ` ${s.groupPairs}` : pack === 'one-up' ? ` ${s.groupOneUp}` : ''}`;\n const cls = propertyMetricClass(span ? `${base} ${s.span}` : base, { density, align }, className);\n const titled = (\n <h3 className={s.groupTitle}>\n <hr />\n <span>{title}</span>\n <hr />\n </h3>\n );\n const heading =\n leading === undefined && actions === undefined ? (\n titled\n ) : (\n <div className={s.groupHeadRow}>\n {leading === undefined ? null : <span className={s.groupLeading}>{leading}</span>}\n {titled}\n {actions === undefined ? null : <span className={s.groupActions}>{actions}</span>}\n </div>\n );\n return (\n <div className={cls} {...stanced}>\n {folds ? (\n <DisclosureRow\n className={s.groupHead}\n open={!folded}\n onToggle={toggle}\n label={typeof title === 'string' ? title : 'this section'}\n controls={bodyId}\n >\n {heading}\n </DisclosureRow>\n ) : (\n heading\n )}\n {description !== undefined && description !== '' && <PropertyNote>{description}</PropertyNote>}\n <div id={bodyId} className={s.groupBody} hidden={folded}>\n {children}\n </div>\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport { type StanceProps, useStance } from '../stance';\nimport s from './Properties.module.css';\nimport { type PropertyMetricProps, propertyMetricClass } from './PropertyPanel';\n\n/** Props for `<Subpanel>`. */\nexport interface SubpanelProps extends PropertyMetricProps, StanceProps {\n title: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\n/** A typographic divider inside a property list: a small title flanked by a\n * rule. Purely a heading — use `<PropertyGroup>` for a bordered section. A\n * stance or tone colors the title and rule. */\nexport function Subpanel({ title, children, className, density, align, stance, tone }: SubpanelProps) {\n const cls = propertyMetricClass(s.subpanel, { density, align }, className);\n return (\n <div className={cls} {...useStance({ stance, tone })}>\n <h4 className={s.subpanelTitle}>\n <span>{title}</span>\n <hr />\n </h4>\n {children}\n </div>\n );\n}\n"],"mappings":"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"}
|