@jimka/typescript-ui 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/{AbstractBooleanInput-C3rj7sje.js → AbstractBooleanInput-BGesvIZy.js} +2 -2
- package/dist/lib/{AbstractBooleanInput-C3rj7sje.js.map → AbstractBooleanInput-BGesvIZy.js.map} +1 -1
- package/dist/lib/{AbstractInput-CfjW_HM2.js → AbstractInput-BxOtFrER.js} +2 -2
- package/dist/lib/{AbstractInput-CfjW_HM2.js.map → AbstractInput-BxOtFrER.js.map} +1 -1
- package/dist/lib/AnimatedDropdown-CZQEqQi-.js +2 -0
- package/dist/lib/AnimatedDropdown-CZQEqQi-.js.map +1 -0
- package/dist/lib/Border-BgaZkc1R.js +2 -0
- package/dist/lib/Border-BgaZkc1R.js.map +1 -0
- package/dist/lib/{BoxLayout-8ryMD4j5.js → BoxLayout-DrKGfsup.js} +2 -2
- package/dist/lib/{BoxLayout-8ryMD4j5.js.map → BoxLayout-DrKGfsup.js.map} +1 -1
- package/dist/lib/Button-U1rsP4v8.js +2 -0
- package/dist/lib/Button-U1rsP4v8.js.map +1 -0
- package/dist/lib/{Card-AKIUAWYS.js → Card-BVwOS8AG.js} +2 -2
- package/dist/lib/{Card-AKIUAWYS.js.map → Card-BVwOS8AG.js.map} +1 -1
- package/dist/lib/ComboBox-B9EDPu6T.js +2 -0
- package/dist/lib/ComboBox-B9EDPu6T.js.map +1 -0
- package/dist/lib/Component-DXf5a3g0.js +2 -0
- package/dist/lib/Component-DXf5a3g0.js.map +1 -0
- package/dist/lib/Container-D5H8VxJK.js +2 -0
- package/dist/lib/{Container-3KsvsJ6S.js.map → Container-D5H8VxJK.js.map} +1 -1
- package/dist/lib/DOM-CGn7xyTw.js +2 -0
- package/dist/lib/DOM-CGn7xyTw.js.map +1 -0
- package/dist/lib/{DragManager-c6M_mE3e.js → DragManager-CI4mZQFy.js} +2 -2
- package/dist/lib/{DragManager-tPMnbjeO.js.map → DragManager-CI4mZQFy.js.map} +1 -1
- package/dist/lib/FieldDecorator-CJs9gwqH.js +2 -0
- package/dist/lib/{FieldDecorator-Bl0h9UEF.js.map → FieldDecorator-CJs9gwqH.js.map} +1 -1
- package/dist/lib/{Fit-CFIr4P9R.js → Fit-DjRsiwlg.js} +2 -2
- package/dist/lib/{Fit-CUS1JXy3.js.map → Fit-DjRsiwlg.js.map} +1 -1
- package/dist/lib/Glyph-KEf4nIv2.js +2 -0
- package/dist/lib/Glyph-KEf4nIv2.js.map +1 -0
- package/dist/lib/{Grid-Czux--Mr.js → Grid-DdL9TVeJ.js} +2 -2
- package/dist/lib/{Grid-D8luwRPI.js.map → Grid-DdL9TVeJ.js.map} +1 -1
- package/dist/lib/GridConstraints-DkLueyrf.js +2 -0
- package/dist/lib/GridConstraints-DkLueyrf.js.map +1 -0
- package/dist/lib/{HBox-BWMf9h6r.js → HBox-Ce0GTCJP.js} +2 -2
- package/dist/lib/{HBox-BWMf9h6r.js.map → HBox-Ce0GTCJP.js.map} +1 -1
- package/dist/lib/{Header-CQjomjo0.js → Header-BQIVpBte.js} +2 -2
- package/dist/lib/{Header-CQjomjo0.js.map → Header-BQIVpBte.js.map} +1 -1
- package/dist/lib/{IconText-BXJK0MOq.js → IconText-Bo4Qcnot.js} +2 -2
- package/dist/lib/{IconText-BXJK0MOq.js.map → IconText-Bo4Qcnot.js.map} +1 -1
- package/dist/lib/LayoutConstraints-DkctJZXB.js +2 -0
- package/dist/lib/LayoutConstraints-DkctJZXB.js.map +1 -0
- package/dist/lib/LayoutSerialization-BT66TY83.js +2 -0
- package/dist/lib/LayoutSerialization-BT66TY83.js.map +1 -0
- package/dist/lib/LayoutSizes-DU3qSq0w.js +2 -0
- package/dist/lib/LayoutSizes-DU3qSq0w.js.map +1 -0
- package/dist/lib/List-DtTVTyWW.js +2 -0
- package/dist/lib/List-DtTVTyWW.js.map +1 -0
- package/dist/lib/Menu-Dz4L7EKJ.js +2 -0
- package/dist/lib/Menu-Dz4L7EKJ.js.map +1 -0
- package/dist/lib/{MenuButton-D15cSbMf.js → MenuButton-VJ5uC3c1.js} +2 -2
- package/dist/lib/{MenuButton-D15cSbMf.js.map → MenuButton-VJ5uC3c1.js.map} +1 -1
- package/dist/lib/Panel-DzWdYwq1.js +2 -0
- package/dist/lib/Panel-DzWdYwq1.js.map +1 -0
- package/dist/lib/{Position-DJKB0N3l.js → Position-A39T7-_1.js} +2 -2
- package/dist/lib/{Position-DJKB0N3l.js.map → Position-A39T7-_1.js.map} +1 -1
- package/dist/lib/ProgressSpinner-zJKtGyNJ.js +2 -0
- package/dist/lib/ProgressSpinner-zJKtGyNJ.js.map +1 -0
- package/dist/lib/RadioButton-CYSSv4nV.js +2 -0
- package/dist/lib/RadioButton-CYSSv4nV.js.map +1 -0
- package/dist/lib/{RovingTabIndex-CFIzIjjU.js → RovingTabIndex-BSZ31Vp5.js} +2 -2
- package/dist/lib/{RovingTabIndex-D-zOuptw.js.map → RovingTabIndex-BSZ31Vp5.js.map} +1 -1
- package/dist/lib/Scrollbar-BhtOjqkG.js +2 -0
- package/dist/lib/Scrollbar-BhtOjqkG.js.map +1 -0
- package/dist/lib/Slider-CdXm7E64.js +2 -0
- package/dist/lib/Slider-CdXm7E64.js.map +1 -0
- package/dist/lib/{Spacer-BVQncB5B.js → Spacer-D9NGW4QG.js} +2 -2
- package/dist/lib/{Spacer-BVQncB5B.js.map → Spacer-D9NGW4QG.js.map} +1 -1
- package/dist/lib/{TabButton-DqYDfN7P.js → TabButton-CThevdY_.js} +2 -2
- package/dist/lib/{TabButton-DqYDfN7P.js.map → TabButton-CThevdY_.js.map} +1 -1
- package/dist/lib/Text-gz6qjFfP.js +2 -0
- package/dist/lib/Text-gz6qjFfP.js.map +1 -0
- package/dist/lib/Tooltip-tkXDk5Bo.js +3 -0
- package/dist/lib/Tooltip-tkXDk5Bo.js.map +1 -0
- package/dist/lib/{VBox-CdV9VGxR.js → VBox-EIqw85Rh.js} +2 -2
- package/dist/lib/{VBox-CdV9VGxR.js.map → VBox-EIqw85Rh.js.map} +1 -1
- package/dist/lib/{VirtualRowView-C18j90n5.js → VirtualRowView-BxpHQ_u8.js} +2 -2
- package/dist/lib/{VirtualRowView-C18j90n5.js.map → VirtualRowView-BxpHQ_u8.js.map} +1 -1
- package/dist/lib/{VirtualScroller-BKMvTisA.js → VirtualScroller-DwEEJE1_.js} +2 -2
- package/dist/lib/{VirtualScroller-BKMvTisA.js.map → VirtualScroller-DwEEJE1_.js.map} +1 -1
- package/dist/lib/component/button.es.js +1 -1
- package/dist/lib/component/button.es.js.map +1 -1
- package/dist/lib/component/chart.es.js +1 -1
- package/dist/lib/component/chart.es.js.map +1 -1
- package/dist/lib/component/container.es.js +1 -1
- package/dist/lib/component/container.es.js.map +1 -1
- package/dist/lib/component/diagram.es.js +1 -1
- package/dist/lib/component/diagram.es.js.map +1 -1
- package/dist/lib/component/display.es.js +1 -1
- package/dist/lib/component/display.es.js.map +1 -1
- package/dist/lib/component/editor.es.js +2 -2
- package/dist/lib/component/editor.es.js.map +1 -1
- package/dist/lib/component/input.es.js +1 -1
- package/dist/lib/component/input.es.js.map +1 -1
- package/dist/lib/component/list.es.js +1 -1
- package/dist/lib/component/menubar.es.js +1 -1
- package/dist/lib/component/menubar.es.js.map +1 -1
- package/dist/lib/component/table.es.js +2 -2
- package/dist/lib/component/table.es.js.map +1 -1
- package/dist/lib/component/tree.es.js +1 -1
- package/dist/lib/component/tree.es.js.map +1 -1
- package/dist/lib/core.es.js +1 -1
- package/dist/lib/core.es.js.map +1 -1
- package/dist/lib/layout.es.js +1 -1
- package/dist/lib/overlay.es.js +1 -1
- package/dist/lib/overlay.es.js.map +1 -1
- package/dist/lib/primitive.es.js +1 -1
- package/dist/lib/router.es.js +1 -1
- package/dist/lib/types/component/button/Button.d.ts +4 -3
- package/dist/lib/types/component/chart/AbstractChart.d.ts +1 -1
- package/dist/lib/types/component/chart/ChartLegend.d.ts +1 -1
- package/dist/lib/types/component/container/DialogBackdrop.d.ts +2 -1
- package/dist/lib/types/component/container/MenuItem.d.ts +1 -1
- package/dist/lib/types/component/container/ScrollStrip.d.ts +1 -0
- package/dist/lib/types/component/container/SplitGutter.d.ts +3 -2
- package/dist/lib/types/component/container/TabBar.d.ts +1 -2
- package/dist/lib/types/component/container/TabPanel.d.ts +6 -3
- package/dist/lib/types/component/container/WindowBorder.d.ts +2 -1
- package/dist/lib/types/component/container/WindowHeader.d.ts +2 -1
- package/dist/lib/types/component/display/Glyph.d.ts +2 -1
- package/dist/lib/types/component/display/Markdown.d.ts +1 -1
- package/dist/lib/types/component/display/PaginationBar.d.ts +1 -1
- package/dist/lib/types/component/display/SpinnerWrap.d.ts +2 -0
- package/dist/lib/types/component/display/Video.d.ts +1 -1
- package/dist/lib/types/component/display/VideoPlayer.d.ts +1 -1
- package/dist/lib/types/component/editor/CodeEditor.d.ts +2 -1
- package/dist/lib/types/component/editor/MarkdownEditor.d.ts +1 -1
- package/dist/lib/types/component/input/Checkbox.d.ts +2 -1
- package/dist/lib/types/component/input/Link.d.ts +1 -1
- package/dist/lib/types/component/input/RadioButton.d.ts +2 -1
- package/dist/lib/types/component/input/Slider.d.ts +2 -1
- package/dist/lib/types/component/input/Text.d.ts +1 -3
- package/dist/lib/types/component/input/TextInput.d.ts +3 -2
- package/dist/lib/types/component/list/AbstractSelectableList.d.ts +2 -1
- package/dist/lib/types/component/menubar/MenuBarButton.d.ts +1 -1
- package/dist/lib/types/core/AnimatedDropdown.d.ts +6 -2
- package/dist/lib/types/core/Animation.d.ts +9 -6
- package/dist/lib/types/core/Component.d.ts +16 -11
- package/dist/lib/types/core/DOM.d.ts +8 -0
- package/dist/lib/types/core/Event.d.ts +12 -6
- package/dist/lib/types/core/index.d.ts +2 -2
- package/dist/lib/types/layout/Accordion.d.ts +2 -0
- package/dist/lib/types/layout/Border.d.ts +1 -0
- package/dist/lib/types/layout/CollapseSupport.d.ts +5 -1
- package/dist/lib/types/layout/LayoutConstraints.d.ts +1 -0
- package/dist/lib/types/layout/LayoutManager.d.ts +2 -0
- package/dist/lib/types/layout/Split.d.ts +1 -0
- package/dist/lib/types/layout/Tab.d.ts +10 -3
- package/dist/lib/types/overlay/AbstractWindow.d.ts +9 -2
- package/dist/lib/types/overlay/Dialog.d.ts +6 -0
- package/dist/lib/types/overlay/Dock.d.ts +12 -2
- package/dist/lib/types/overlay/Drawer.d.ts +5 -0
- package/dist/lib/types/overlay/Menu.d.ts +3 -1
- package/dist/lib/types/overlay/Notification.d.ts +3 -0
- package/dist/lib/types/overlay/Popover.d.ts +3 -1
- package/dist/lib/types/overlay/Rail.d.ts +7 -0
- package/dist/lib/types/overlay/Tooltip.d.ts +3 -0
- package/dist/lib/types/overlay/index.d.ts +1 -1
- package/dist/lib/validation.es.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/lib/AbstractBooleanInput-C6CidMMX.js +0 -2
- package/dist/lib/AbstractBooleanInput-C6CidMMX.js.map +0 -1
- package/dist/lib/AbstractInput-DD-NpicS.js +0 -2
- package/dist/lib/AbstractInput-DD-NpicS.js.map +0 -1
- package/dist/lib/AnimatedDropdown-BFz-DO7R.js +0 -2
- package/dist/lib/AnimatedDropdown-BFz-DO7R.js.map +0 -1
- package/dist/lib/AnimatedDropdown-BbGCs70E.js +0 -2
- package/dist/lib/AnimatedDropdown-BbGCs70E.js.map +0 -1
- package/dist/lib/Border-5Ki2DCWz.js +0 -2
- package/dist/lib/Border-5Ki2DCWz.js.map +0 -1
- package/dist/lib/Border-oZC_SDy8.js +0 -2
- package/dist/lib/Border-oZC_SDy8.js.map +0 -1
- package/dist/lib/BoxLayout-DgZukB5_.js +0 -2
- package/dist/lib/BoxLayout-DgZukB5_.js.map +0 -1
- package/dist/lib/Button-Cdq2ySPL.js +0 -2
- package/dist/lib/Button-Cdq2ySPL.js.map +0 -1
- package/dist/lib/Button-DEUtBqK4.js +0 -2
- package/dist/lib/Button-DEUtBqK4.js.map +0 -1
- package/dist/lib/Card-UF0fq8cQ.js +0 -2
- package/dist/lib/Card-UF0fq8cQ.js.map +0 -1
- package/dist/lib/ComboBox-GOBOAJ1N.js +0 -2
- package/dist/lib/ComboBox-GOBOAJ1N.js.map +0 -1
- package/dist/lib/ComboBox-e_YVsWBr.js +0 -2
- package/dist/lib/ComboBox-e_YVsWBr.js.map +0 -1
- package/dist/lib/Component-BR2f0Itb.js +0 -2
- package/dist/lib/Component-BR2f0Itb.js.map +0 -1
- package/dist/lib/Component-Bs6TvaGZ.js +0 -2
- package/dist/lib/Component-Bs6TvaGZ.js.map +0 -1
- package/dist/lib/Container-3KsvsJ6S.js +0 -2
- package/dist/lib/Container-CzyZeMyZ.js +0 -2
- package/dist/lib/Container-CzyZeMyZ.js.map +0 -1
- package/dist/lib/DOM-DeF_dWe2.js +0 -2
- package/dist/lib/DOM-DeF_dWe2.js.map +0 -1
- package/dist/lib/DragManager-c6M_mE3e.js.map +0 -1
- package/dist/lib/DragManager-tPMnbjeO.js +0 -2
- package/dist/lib/FieldDecorator-Bl0h9UEF.js +0 -2
- package/dist/lib/FieldDecorator-E7WzutjO.js +0 -2
- package/dist/lib/FieldDecorator-E7WzutjO.js.map +0 -1
- package/dist/lib/Fit-CFIr4P9R.js.map +0 -1
- package/dist/lib/Fit-CUS1JXy3.js +0 -2
- package/dist/lib/Glyph-6Ki3A1_a.js +0 -2
- package/dist/lib/Glyph-6Ki3A1_a.js.map +0 -1
- package/dist/lib/Glyph-WUqTF_Z9.js +0 -2
- package/dist/lib/Glyph-WUqTF_Z9.js.map +0 -1
- package/dist/lib/Grid-Czux--Mr.js.map +0 -1
- package/dist/lib/Grid-D8luwRPI.js +0 -2
- package/dist/lib/GridConstraints-CzC0w4i-.js +0 -2
- package/dist/lib/GridConstraints-CzC0w4i-.js.map +0 -1
- package/dist/lib/GridConstraints-Q3wvuovN.js +0 -2
- package/dist/lib/GridConstraints-Q3wvuovN.js.map +0 -1
- package/dist/lib/HBox-Q8y1PHlk.js +0 -2
- package/dist/lib/HBox-Q8y1PHlk.js.map +0 -1
- package/dist/lib/Header-zPdvC2AX.js +0 -2
- package/dist/lib/Header-zPdvC2AX.js.map +0 -1
- package/dist/lib/IconText-BvGfu7eq.js +0 -2
- package/dist/lib/IconText-BvGfu7eq.js.map +0 -1
- package/dist/lib/LayoutConstraints-CHI2lhPd.js +0 -2
- package/dist/lib/LayoutConstraints-CHI2lhPd.js.map +0 -1
- package/dist/lib/LayoutSerialization-CWU0iEuV.js +0 -2
- package/dist/lib/LayoutSerialization-CWU0iEuV.js.map +0 -1
- package/dist/lib/LayoutSerialization-C_zcwaN3.js +0 -2
- package/dist/lib/LayoutSerialization-C_zcwaN3.js.map +0 -1
- package/dist/lib/LayoutSizes-BW4wyGDI.js +0 -2
- package/dist/lib/LayoutSizes-BW4wyGDI.js.map +0 -1
- package/dist/lib/LayoutSizes-XdhpAOmc.js +0 -2
- package/dist/lib/LayoutSizes-XdhpAOmc.js.map +0 -1
- package/dist/lib/List-BgxnLWnl.js +0 -2
- package/dist/lib/List-BgxnLWnl.js.map +0 -1
- package/dist/lib/List-D-GhoVgB.js +0 -2
- package/dist/lib/List-D-GhoVgB.js.map +0 -1
- package/dist/lib/Menu-BdnwRY0M.js +0 -2
- package/dist/lib/Menu-BdnwRY0M.js.map +0 -1
- package/dist/lib/Menu-Wz4MK6sA.js +0 -2
- package/dist/lib/Menu-Wz4MK6sA.js.map +0 -1
- package/dist/lib/MenuButton-JQQaKThJ.js +0 -2
- package/dist/lib/MenuButton-JQQaKThJ.js.map +0 -1
- package/dist/lib/Panel-DApa7vA6.js +0 -2
- package/dist/lib/Panel-DApa7vA6.js.map +0 -1
- package/dist/lib/Panel-DIsdaqc2.js +0 -2
- package/dist/lib/Panel-DIsdaqc2.js.map +0 -1
- package/dist/lib/Position-B-WfZYAJ.js +0 -2
- package/dist/lib/Position-B-WfZYAJ.js.map +0 -1
- package/dist/lib/ProgressSpinner-CNYcsBhw.js +0 -2
- package/dist/lib/ProgressSpinner-CNYcsBhw.js.map +0 -1
- package/dist/lib/ProgressSpinner-Dzaka7_c.js +0 -2
- package/dist/lib/ProgressSpinner-Dzaka7_c.js.map +0 -1
- package/dist/lib/RadioButton-CepkneSA.js +0 -2
- package/dist/lib/RadioButton-CepkneSA.js.map +0 -1
- package/dist/lib/RadioButton-eI3-vXn3.js +0 -2
- package/dist/lib/RadioButton-eI3-vXn3.js.map +0 -1
- package/dist/lib/RovingTabIndex-CFIzIjjU.js.map +0 -1
- package/dist/lib/RovingTabIndex-D-zOuptw.js +0 -2
- package/dist/lib/Scrollbar-DQdCdj94.js +0 -2
- package/dist/lib/Scrollbar-DQdCdj94.js.map +0 -1
- package/dist/lib/Scrollbar-DchfiFKC.js +0 -2
- package/dist/lib/Scrollbar-DchfiFKC.js.map +0 -1
- package/dist/lib/Slider-DvDdVvRm.js +0 -2
- package/dist/lib/Slider-DvDdVvRm.js.map +0 -1
- package/dist/lib/Slider-xRxNSjFN.js +0 -2
- package/dist/lib/Slider-xRxNSjFN.js.map +0 -1
- package/dist/lib/Spacer-BqiPiCqz.js +0 -2
- package/dist/lib/Spacer-BqiPiCqz.js.map +0 -1
- package/dist/lib/TabButton-pZKFYg7b.js +0 -2
- package/dist/lib/TabButton-pZKFYg7b.js.map +0 -1
- package/dist/lib/Text-D1RrBY52.js +0 -2
- package/dist/lib/Text-D1RrBY52.js.map +0 -1
- package/dist/lib/Text-DJoUHukb.js +0 -2
- package/dist/lib/Text-DJoUHukb.js.map +0 -1
- package/dist/lib/Tooltip-4lU_tf6r.js +0 -3
- package/dist/lib/Tooltip-4lU_tf6r.js.map +0 -1
- package/dist/lib/Tooltip-5BR7yYEX.js +0 -3
- package/dist/lib/Tooltip-5BR7yYEX.js.map +0 -1
- package/dist/lib/VBox-D5B9ecw0.js +0 -2
- package/dist/lib/VBox-D5B9ecw0.js.map +0 -1
- package/dist/lib/VirtualRowView-Con9SWiU.js +0 -2
- package/dist/lib/VirtualRowView-Con9SWiU.js.map +0 -1
- package/dist/lib/VirtualScroller-BPDmO1ir.js +0 -2
- package/dist/lib/VirtualScroller-BPDmO1ir.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"List-D-GhoVgB.js","names":[],"sources":["../../src/typescript/lib/component/list/ListItemRenderer.ts","../../src/typescript/lib/component/list/renderer/Label.ts","../../src/typescript/lib/component/list/AbstractSelectableList.ts","../../src/typescript/lib/component/list/List.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { ListItemRenderContext } from \"~/component/list/ListItemRenderContext.js\";\n\n/**\n * Abstract base class for list item renderers.\n *\n * @remarks\n * A `ListItemRenderer` owns the content of a single list row — the label, and\n * optionally an icon or other affordance. The owning\n * [`List`](/api/component/list/classes/List) /\n * [`MultiSelectList`](/api/component/list/classes/MultiSelectList) holds a\n * renderer factory; each pooled row calls that factory once at construction and\n * rebinds via {@link update} when the row is remapped to a different item. The\n * collapsed [`ComboBox`](/api/component/input/classes/ComboBox) control hosts\n * one renderer built from the same factory, so the selected entry renders on\n * the closed control exactly as it does in the open dropdown.\n *\n * Selection / focus / hover chrome is applied by the list on the row element\n * directly and never reaches the renderer.\n *\n * Subclasses implement {@link update} and {@link layoutChildren}. See\n * [`LabelListItemRenderer`](/api/component/list/classes/LabelListItemRenderer)\n * for the canonical default. {@link getContentWidth} is optional: it is read\n * only by a list running with `horizontalScrolling` on, and the base\n * implementation already reports \"no intrinsic width\".\n *\n * Not wrapped with `callable()` — abstract classes are never instantiated.\n *\n * @category Components\n */\nexport abstract class ListItemRenderer extends Component {\n\n constructor() {\n super();\n }\n\n /**\n * Rebinds this renderer to new item data.\n *\n * @param context - The bound-item state for this render pass.\n *\n * @remarks Called every time the owning row (or collapsed control) is\n * mapped to a different item.\n */\n abstract update(context: ListItemRenderContext): void;\n\n /**\n * Returns the natural width of the currently bound content — the width at\n * which it renders without clipping.\n *\n * Read only by a list running with\n * [`horizontalScrolling`](/api/component/list/classes/List#sethorizontalscrolling)\n * on, which sizes every row to the widest value across the bound items so\n * over-long content scrolls into view instead of ellipsising. A list with\n * horizontal scrolling off — the default — never calls this, so a renderer\n * that measures lazily pays nothing for it.\n *\n * The base implementation returns 0, meaning \"no intrinsic width\": rows\n * stay at the viewport width and never extend the horizontal scroll extent.\n * That keeps a custom renderer written against the previous contract\n * working unchanged; override it to opt that renderer into horizontal\n * scrolling.\n *\n * @returns The bound content's natural width in pixels, or 0 when unknown.\n */\n getContentWidth(): number {\n return 0;\n }\n\n /**\n * Positions this renderer's internal children within the given box.\n *\n * @param width - The horizontal extent of the renderer in pixels.\n * @param height - The vertical extent of the renderer in pixels.\n *\n * @remarks Called by the owning row after the renderer's own size has been\n * set via `setX` / `setY` / `setWidth` / `setHeight`.\n */\n abstract layoutChildren(width: number, height: number): void;\n}\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { ListItemRenderer } from \"~/component/list/ListItemRenderer.js\";\nimport { ListItemRenderContext } from \"~/component/list/ListItemRenderContext.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * The default [`ListItemRenderer`](/api/component/list/classes/ListItemRenderer) —\n * a single [`Text`](/api/component/input/classes/Text) child carrying the\n * item's label.\n *\n * @remarks\n * Used as the implicit factory by [`List`](/api/component/list/classes/List) /\n * [`MultiSelectList`](/api/component/list/classes/MultiSelectList) and the\n * collapsed [`ComboBox`](/api/component/input/classes/ComboBox) control when no\n * custom renderer is set. The label keeps `Text`'s default `truncate: true`, so\n * a label wider than its row clips with an ellipsis — reproducing the row\n * chrome the list carried before renderers existed. The label is sized to fill\n * the row in {@link layoutChildren} rather than to its natural width: the row\n * is never narrower than {@link getContentWidth}, so filling it lets the\n * selection wash span the whole row without clipping the text.\n *\n * @example\n * ```typescript\n * list.setRendererFactory(() => new LabelListItemRenderer());\n * ```\n *\n * @category Components\n */\nclass LabelListItemRenderer extends ListItemRenderer {\n\n private _label: Text;\n /**\n * Whether `_label`'s cached natural width matches the bound text. The label\n * runs with `autoMeasure(false)`, so the measure is driven from\n * {@link getContentWidth} rather than {@link update} — a list with\n * horizontal scrolling off never asks, and so never pays for it.\n */\n private _measured: boolean = false;\n\n /**\n * Constructs a label renderer with an empty text node. The label is\n * populated on the first {@link update} call.\n */\n constructor() {\n super();\n this.clearInsets();\n\n this._label = new Text();\n this._label.clearInsets();\n this._label.setAutoMeasure(false);\n }\n\n /**\n * Returns the underlying label component so consumers can tweak font /\n * colour properties at construction time.\n *\n * @returns The internal [`Text`](/api/component/input/classes/Text) instance.\n */\n getLabel(): Text {\n return this._label;\n }\n\n /**\n * Updates the rendered label text.\n *\n * @param context - The bound-item state for this render pass.\n */\n update(context: ListItemRenderContext): void {\n this._label.setText(context.item.label);\n this._measured = false;\n }\n\n /**\n * Returns the natural width of the bound label, measuring it on first ask\n * after each {@link update}.\n *\n * @returns The label's natural width in pixels.\n */\n getContentWidth(): number {\n if (!this._measured) {\n this._label.measure();\n this._measured = true;\n }\n\n return this._label.getPreferredSize()?.width ?? 0;\n }\n\n /**\n * Sizes the label to fill the renderer's allocated box and centres its\n * line-box vertically by matching `line-height` to the row height.\n *\n * @param width - The horizontal extent of the row in pixels.\n * @param height - The vertical extent of the row in pixels.\n */\n layoutChildren(width: number, height: number): void {\n this._label.setAutoCommitStyle(false);\n this._label.setX(0);\n this._label.setY(0);\n this._label.setWidth(width);\n this._label.setHeight(height);\n this._label.setLineHeight(height);\n this._label.setAutoCommitStyle(true);\n }\n\n /**\n * Appends the label sub-component element to the renderer's DOM element.\n *\n * @param element - Optional element passed by the rendering pipeline; falls\n * back to getElement().\n *\n * @returns This renderer, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n const el = element || this.getElement();\n if (!el) {\n return this;\n }\n\n DOM.sink.appendChild(el, this._label.getElement(true)!);\n\n return this;\n }\n}\n\nconst LabelListItemRendererCallable = callable(LabelListItemRenderer);\ntype LabelListItemRendererCallable = LabelListItemRenderer;\nexport {\n LabelListItemRenderer as _LabelListItemRenderer,\n LabelListItemRendererCallable as LabelListItemRenderer,\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractInput, AbstractInputOptions } from \"~/component/input/AbstractInput.js\";\nimport { Component } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle } from \"~/core/DOM.js\";\nimport { Panel } from \"~/core/Panel.js\";\nimport { StyleRule } from \"~/core/StyleTarget.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { ListenerBag } from \"~/core/ListenerBag.js\";\nimport { Tooltip } from \"~/overlay/Tooltip.js\";\nimport { Type } from \"~/core/Type.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport { Fit } from \"~/layout/Fit.js\";\nimport { _VBox } from \"~/layout/VBox.js\";\nimport { Size } from \"~/primitive/Size.js\";\nimport { AbstractStore } from \"~/data/AbstractStore.js\";\nimport { ModelRecord } from \"~/data/ModelRecord.js\";\nimport { ListItemRenderer } from \"~/component/list/ListItemRenderer.js\";\nimport { LabelListItemRenderer } from \"~/component/list/renderer/Label.js\";\nimport { Text } from \"~/component/input/Text.js\";\n\n/**\n * One entry in a [`List`](/api/component/list/classes/List) /\n * [`MultiSelectList`](/api/component/list/classes/MultiSelectList) item\n * array. Plain data — the row pool is the view layer.\n *\n * @category Components\n */\nexport interface SelectableListItem {\n /** Binding identifier — what `getValue` / `setValue` round-trip. */\n key: string;\n /** Display text rendered in the row. */\n label: string;\n /**\n * Optional registry glyph name, read by\n * [`GlyphListItemRenderer`](/api/component/list/classes/GlyphListItemRenderer)\n * to paint an icon beside the label. Ignored by the default label\n * renderer. Array-supplied items carry it directly; store-bound items\n * resolve it from the record field named by the list's `glyphField`.\n */\n glyph?: string;\n /**\n * Optional hover-tooltip text for the row, shown after the standard hover\n * delay via [`Tooltip`](/api/overlay/classes/Tooltip). Lets a host surface\n * the full value behind a truncated label without reaching into the row DOM.\n * Array-supplied items carry it directly; store-bound items resolve it from\n * the record field named by the list's `tooltipField`.\n */\n tooltip?: string;\n}\n\n/**\n * Accepted form for a single item passed to a custom list's `setItems` /\n * `addItem`: either a plain string (auto-keyed by its array position) or a\n * pre-formed {@link SelectableListItem} with an explicit caller-supplied key.\n *\n * @category Components\n */\nexport type SelectableListItemSpec = String | SelectableListItem;\n\n/**\n * Pixel height of one rendered row. Matches `SelectableListRow`'s cached\n * `preferredSize(0, 22)` and the `lineHeight: 22px` declaration in the\n * shared `.SelectableListRow` class rule. Keep these three values in lockstep\n * if the row chrome changes — keyboard `PageUp`/`PageDown` derives its\n * page size from this constant divided into the visible viewport height.\n */\nconst ROW_HEIGHT_PX = 22;\n\n/**\n * Horizontal padding on each side of a row, between the row edge and its\n * renderer. Read by {@link SelectableListRow.publishContentWidth} to convert a\n * renderer's content width into the row's own natural width, so keep it in step\n * with the row's `setPadding` call.\n */\nconst ROW_PADDING_X_PX = 8;\n\n/**\n * Maximum time (in milliseconds) between successive printable-character\n * keypresses before the type-ahead search buffer resets. Picked to match\n * the native `<select>` type-ahead window the custom list replaces — a\n * burst of letters within this window builds a single search prefix; a\n * pause longer than this starts a fresh search.\n */\nconst TYPE_AHEAD_TIMEOUT_MS = 700;\n\n/**\n * Construction-time options for {@link AbstractSelectableList}.\n *\n * @category Components\n */\nexport interface AbstractSelectableListOptions extends AbstractInputOptions {\n items?: String | Array<String>;\n store?: AbstractStore;\n displayField?: string;\n valueField?: string;\n /**\n * Record field whose value becomes each store-bound item's `glyph`, read\n * by [`GlyphListItemRenderer`](/api/component/list/classes/GlyphListItemRenderer).\n * Array-supplied items carry their glyph on the item instead.\n */\n glyphField?: string;\n /**\n * Record field whose value becomes each store-bound item's `tooltip` (the\n * hover text). Array-supplied items carry their tooltip on the item instead.\n */\n tooltipField?: string;\n /**\n * Zero-argument factory producing the renderer for each row. Defaults to a\n * label renderer reproducing the plain-text rows. Supply\n * `() => new GlyphListItemRenderer()` to paint each item's `glyph` beside\n * its label.\n */\n rendererFactory?: () => ListItemRenderer;\n /**\n * Construction-time listener bag — the declarative form of `on()`. Adds the\n * list's `action` shorthand to the inherited `change` / `binding`.\n */\n listeners?: {\n action?: () => void;\n change?: (value: any) => void;\n binding?: () => void;\n };\n /**\n * Muted placeholder text shown inside the scroll area when the list is\n * empty. Opt-in: with neither `emptyText` nor `emptyComponent` set, an empty\n * list has no placeholder child.\n */\n emptyText?: string;\n /**\n * Factory for a custom empty-state placeholder, shown inside the scroll area\n * when the list is empty. Takes precedence over `emptyText`. The returned\n * component should report an unbounded max (the `Component` default) so the\n * empty list still fills its region and drag-resizes.\n */\n emptyComponent?: () => Component;\n /**\n * Construction-time shortcut for\n * [`setHorizontalScrolling`](/api/component/list/classes/List#sethorizontalscrolling).\n * Defaults to `false` — a label wider than the row ellipsises.\n */\n horizontalScrolling?: boolean;\n}\n\n/**\n * Shared visual defaults for every {@link AbstractSelectableList} subclass.\n * Layered into the defaults bag passed to `super` from the abstract\n * constructor so {@link List} and {@link MultiSelectList} share row\n * chrome without duplicating the bag at every leaf.\n */\nconst _defaultAbstractSelectableListOptions: Partial<AbstractSelectableListOptions> = {\n tag: \"div\",\n backgroundColor: \"var(--ts-ui-list-bg, rgb(255, 255, 255))\",\n foregroundColor: \"var(--ts-ui-text-color, black)\",\n border: \"1px solid var(--ts-ui-list-border, rgb(200, 200, 200))\",\n borderRadius: \"var(--ts-ui-border-radius, 4px)\",\n preferredSize: { width: 200, height: 200 },\n maxSize: { width: Number.MAX_SAFE_INTEGER, height: Number.MAX_SAFE_INTEGER },\n};\n\n/**\n * Static styling registered once at module init. The container surface\n * carries the focus ring (matched against the framework auto-added\n * `.List` / `.MultiSelectList` classes that `Component.init()` derives\n * from `constructor.name`); `.SelectableListRow` carries the row chrome\n * (single-line text with ellipsis truncation, optional theme-controlled\n * separator); the `.selected` / `.focused` modifier classes layer the\n * selection wash and keyboard-focus outline on top.\n *\n * The `:focus` ring is attached via a compound selector covering both\n * concrete subclass names rather than the abstract base — TypeScript\n * `class.constructor.name` is the leaf class, so the framework adds\n * `\"List\"` / `\"MultiSelectList\"` (not `\"AbstractSelectableList\"`) to the\n * surface's classList. Keep this list in sync if a new concrete\n * subclass extends `AbstractSelectableList`.\n */\n(() => {\n new StyleRule({\n scope: \"selector\",\n name: \".List, .MultiSelectList\",\n styles: {\n userSelect: \"none\",\n outline: \"none\",\n },\n });\n\n // Pseudo-element overlay rather than a plain `outline:` rule so an\n // ancestor with `overflow: hidden` (the framework's Component default)\n // can't clip the focus indicator. `z-index: 1` lifts the ring above the\n // absolutely-positioned rows.\n new StyleRule({\n scope: \"selector\",\n name: \".List:focus::after, .MultiSelectList:focus::after\",\n styles: {\n content: \"''\",\n position: \"absolute\",\n inset: \"0\",\n border: \"2px solid var(--ts-ui-indicator-focus, rgb(30, 100, 200))\",\n borderRadius: \"inherit\",\n boxSizing: \"border-box\",\n pointerEvents: \"none\",\n zIndex: \"1\",\n },\n });\n\n // `lineHeight: 22px` centers the single line of label text vertically\n // without `display: flex` — matches `ROW_HEIGHT_PX` and the row's\n // cached `preferredSize(0, 22)`. The whiteSpace/overflow/textOverflow\n // trio truncates long labels with an ellipsis when the row is narrower\n // than the label text.\n new StyleRule({\n scope: \"class\",\n name: \"SelectableListRow\",\n styles: {\n lineHeight: \"22px\",\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n borderBottom: \"1px solid var(--ts-ui-list-row-separator, transparent)\",\n cursor: \"pointer\",\n },\n });\n\n new StyleRule({\n scope: \"selector\",\n name: \".SelectableListRow:hover\",\n styles: {\n backgroundColor: \"var(--ts-ui-list-row-hover-bg, rgba(30, 100, 200, 0.08))\",\n },\n });\n\n new StyleRule({\n scope: \"selector\",\n name: \".SelectableListRow.selected\",\n styles: {\n backgroundColor: \"var(--ts-ui-list-row-selected-bg, rgba(30, 100, 200, 0.18))\",\n color: \"var(--ts-ui-list-row-selected-color, inherit)\",\n },\n });\n\n // The keyboard-focused row is part of the *selection* indicator family\n // (a light per-row mark, distinct from the heavier focus border around\n // the focusable list root itself). Uses the dashed `indicator.selection`\n // shorthand so future themes can re-skin every \"selection mark\" in one\n // place. Rows have no positioned descendants, so the outline draws on\n // top of the row's text without any covering issue.\n new StyleRule({\n scope: \"selector\",\n name: \".SelectableListRow.focused\",\n styles: {\n outline: \"var(--ts-ui-indicator-selection, 1px dashed rgb(120, 170, 240))\",\n },\n });\n})();\n\n/**\n * A single row inside an {@link AbstractSelectableList}. Holds the static\n * row styling via the `.SelectableListRow` / `.SelectableListRow:hover` /\n * `.SelectableListRow.selected` / `.SelectableListRow.focused` class rules and\n * exposes typed setters for the label, the pool index, the selected\n * flag, and the focused flag.\n *\n * Internal — not re-exported from the per-subpath barrel; the public\n * surface lives on `List` / `MultiSelectList`.\n */\nclass SelectableListRow extends Component {\n // Cached so setter calls made before the element renders survive to\n // be applied at render time.\n private _selected: boolean = false;\n private _focused: boolean = false;\n /** Zero-based index in the row pool; forwarded to the handlers on a gesture. */\n private _index: number;\n /** Owner-supplied gesture handlers, each invoked with this row's `_index`. */\n private readonly _handlers: RowHandlers;\n /** Whether a tooltip is currently attached, so `updateItem` can detach it. */\n private _tooltipAttached: boolean = false;\n /**\n * The renderer owning this row's content (label, optional glyph). Built\n * from the owning list's factory, appended straight into the row DOM in\n * {@link init}, and positioned from {@link doLayout}. Rendered\n * `pointer-events: none` so a click on the label falls through to the\n * row element, whose exact-target `click` listener drives selection.\n */\n private _renderer: ListItemRenderer;\n\n /**\n * @param handlers - Owner-supplied gesture callbacks (click, contextmenu,\n * dblclick), each invoked with the row's index and the raw mouse event.\n * @param index - Initial pool index.\n * @param rendererFactory - Zero-argument factory producing this row's\n * content renderer.\n */\n constructor(handlers: RowHandlers, index: number, rendererFactory: () => ListItemRenderer) {\n super({ tag: \"div\" });\n\n this._handlers = handlers;\n this._index = index;\n this._renderer = rendererFactory();\n this._renderer.setPointerEvents(\"none\");\n\n this.getAria().setRole(\"option\");\n this.setPreferredSize(0, ROW_HEIGHT_PX);\n // Do NOT cap the row's max height. A finite per-row height max makes the\n // list's VBox sum to a finite content max (VBox.aggregateMaxSize), which\n // shrink-wraps the whole list to its content and breaks stretch/scroll\n // and the accordion's resizable drag. The row is already pinned to\n // ROW_HEIGHT_PX by its preferredSize above; leave its max unbounded (the\n // Component default).\n this.setPadding(new Insets(0, ROW_PADDING_X_PX, 0, ROW_PADDING_X_PX));\n // Component's framework default writes `cursor: default` as an\n // inline style, which would beat the `.SelectableListRow` class rule\n // — set the inline cursor explicitly so rows show the hand\n // cursor on hover.\n this.setCursor(\"pointer\");\n\n Event.addListener(this, \"pointerdown\", this.onPointerDown);\n Event.addListener(this, \"click\", this.onClick);\n Event.addListener(this, \"contextmenu\", this.onContextMenu);\n Event.addListener(this, \"dblclick\", this.onDblClick);\n }\n\n /**\n * Rebinds this row's renderer to a new item, replacing the plain-text\n * label write the pool sync used before renderers existed.\n *\n * @param item - The item to display.\n * @param index - The item's zero-based index.\n *\n * @returns This row, for method chaining.\n */\n updateItem(item: SelectableListItem, index: number): this {\n this._renderer.update({ item, index });\n this.applyTooltip(item.tooltip);\n\n return this;\n }\n\n /**\n * Attaches (or detaches) the row's hover tooltip to match the item. Called\n * from {@link updateItem} so a pooled row reused for a different item tracks\n * the new item's `tooltip` — attaching when set, detaching when cleared.\n *\n * @param text - The item's tooltip text, or `undefined` for no tooltip.\n */\n private applyTooltip(text: string | undefined): void {\n if (text) {\n // Tooltip.attach replaces any prior attachment, so re-attaching with\n // new text on pool reuse is safe. The renderer is pointer-events:none,\n // so hover events target the row element and the component-level\n // attach matches (no need to reach the label child).\n Tooltip.attach(this, text);\n this._tooltipAttached = true;\n } else if (this._tooltipAttached) {\n Tooltip.detach(this);\n this._tooltipAttached = false;\n }\n }\n\n /**\n * Returns the row's natural width — its renderer's content width plus the\n * row's own horizontal padding — i.e. the width at which the bound item\n * renders without clipping.\n *\n * Read by {@link ListRowColumn.computeTotalMinSize}, and only while the\n * owning list scrolls horizontally, so a list with the setting off never\n * calls this and never makes its renderers measure.\n *\n * Deliberately *not* published as the row's `minSize`: a minimum is a\n * constraint that propagates outward (`VBox.getMinSize` → the inner\n * `Panel` → the list's `Fit` → `Component.clampWidth`), so a wide row would\n * inflate the whole `List` element inside its host rather than scroll\n * within it. The natural width is an input to the column's own overflow\n * inflation only.\n *\n * @returns The row's natural width in pixels.\n */\n getNaturalWidth(): number {\n return this._renderer.getContentWidth() + ROW_PADDING_X_PX * 2;\n }\n\n /**\n * Swaps in a new content renderer, removing the old renderer's element\n * and appending the new one. Used when the owning list's renderer factory\n * changes. The new renderer is left blank until the next\n * {@link updateItem}.\n *\n * @param renderer - The replacement renderer.\n *\n * @returns This row, for method chaining.\n */\n setRenderer(renderer: ListItemRenderer): this {\n const el = this.getElement();\n\n if (el) {\n const oldEl = this._renderer.getElement();\n if (oldEl && DOM.source.getParentNode(oldEl) === el) {\n DOM.sink.removeChild(el, oldEl);\n }\n }\n\n this._renderer = renderer;\n this._renderer.setPointerEvents(\"none\");\n\n if (el) {\n DOM.sink.appendChild(el, this._renderer.getElement(true)!);\n }\n\n return this;\n }\n\n /**\n * Updates the index this row reports through its click callback. Used\n * when the row pool is reconciled against a new item list and an\n * existing row is reused at a new position.\n *\n * @param index - The new zero-based row index.\n *\n * @returns This row, for method chaining.\n */\n setIndex(index: number): this {\n this._index = index;\n\n return this;\n }\n\n /**\n * Returns the row's current pool index.\n *\n * @returns The zero-based index.\n */\n getIndex(): number {\n return this._index;\n }\n\n /**\n * Toggles the `.selected` class and `aria-selected` to reflect\n * membership in the owning list's selection set.\n *\n * @param value - `true` when this row is currently selected.\n *\n * @returns This row, for method chaining.\n */\n setSelected(value: boolean): this {\n this._selected = value;\n this.getAria().setSelected(value);\n this.applyRowClass();\n\n return this;\n }\n\n /**\n * Returns the cached selected state.\n *\n * @returns `true` when this row is currently selected.\n */\n isSelected(): boolean {\n return this._selected;\n }\n\n /**\n * Toggles the `.focused` class to reflect the keyboard-focus position\n * inside the owning list.\n *\n * @param value - `true` when this row currently holds the keyboard\n * focus position.\n *\n * @returns This row, for method chaining.\n */\n setFocused(value: boolean): this {\n this._focused = value;\n this.applyRowClass();\n\n return this;\n }\n\n /**\n * Returns the cached focused state.\n *\n * @returns `true` when this row holds the keyboard-focus position.\n */\n isFocused(): boolean {\n return this._focused;\n }\n\n /**\n * Renders the row's `<div>` with its current class set. The label content\n * lives in the renderer child, appended by {@link init}.\n *\n * @returns The created element handle.\n */\n protected render(): Handle {\n const element = super.render();\n this.applyRowClass();\n\n return element;\n }\n\n /**\n * Appends the renderer's element to the row DOM. The renderer's own\n * children (label, optional glyph) are appended by the renderer's `init`.\n *\n * @param element - Optional element passed by the rendering pipeline;\n * falls back to getElement().\n *\n * @returns This row, for method chaining.\n */\n protected init(element?: Handle): this {\n super.init(element);\n\n const el = element || this.getElement();\n if (el) {\n DOM.sink.appendChild(el, this._renderer.getElement(true)!);\n }\n\n return this;\n }\n\n /**\n * Positions the renderer to fill the row's padding box, then lets it lay\n * out its own children. Only writes setters (no geometry reads), so it is\n * safe under the `commitBounds` auto-commit path that drives it.\n *\n * @returns This row, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n const inner = this.getInnerSize();\n if (!inner) {\n return this;\n }\n\n const perim = this.getPerimeterSize();\n\n this._renderer.setAutoCommitStyle(false);\n this._renderer.setX(perim.left);\n this._renderer.setY(perim.top);\n this._renderer.setWidth(inner.width);\n this._renderer.setHeight(inner.height);\n this._renderer.setAutoCommitStyle(true);\n\n this._renderer.layoutChildren(inner.width, inner.height);\n\n return this;\n }\n\n /**\n * Computes the row's class list from the cached selected/focused\n * state. Writes via `setElementAttribute(\"class\", …)` so the framework\n * defer-write seam owns the DOM write.\n */\n private applyRowClass(): void {\n const classes = [\"SelectableListRow\"];\n\n if (this._selected) {\n classes.push(\"selected\");\n }\n\n if (this._focused) {\n classes.push(\"focused\");\n }\n\n this.setElementAttribute(\"class\", classes.join(\" \"));\n }\n\n /**\n * Suppresses focus loss when the row is pointed at. Without this,\n * clicking a row while the list root has focus would blur the list,\n * and the keyboard model would lose its focus position before the\n * click handler runs. The same pattern guards the AutoComplete row\n * pool against blurring the host input on click.\n *\n * @param e - The pointerdown event.\n */\n private onPointerDown(e: PointerEvent): void {\n e.preventDefault();\n }\n\n /**\n * Forwards the row's index and the raw mouse event to the owner-supplied\n * click callback.\n *\n * @param e - The click event.\n */\n private onClick(e: MouseEvent): void {\n this._handlers.onClick(this._index, e);\n }\n\n /**\n * Forwards the row's index and the raw mouse event to the owner-supplied\n * context-menu callback.\n *\n * @param e - The contextmenu event.\n */\n private onContextMenu(e: MouseEvent): void {\n this._handlers.onContextMenu(this._index, e);\n }\n\n /**\n * Forwards the row's index and the raw mouse event to the owner-supplied\n * double-click callback.\n *\n * @param e - The dblclick event.\n */\n private onDblClick(e: MouseEvent): void {\n this._handlers.onDblClick(this._index, e);\n }\n}\n\n/**\n * The row stack inside a list's scroll panel: a `VBox` that additionally knows\n * how wide its rows want to be.\n *\n * Exists for one reason — the framework's cross-axis overflow inflation reads\n * the children's *minimum* width (`LayoutManager.inflateForOverflow` →\n * {@link computeTotalMinSize}), but a row's natural width must not become a\n * minimum. A minimum propagates outward — `VBox.getMinSize` → the scroll\n * `Panel` → the list's `Fit` → `Component.clampWidth` — and would inflate the\n * `List` element itself inside its host, so a long label would widen the whole\n * rail instead of scrolling inside it.\n *\n * Overriding `computeTotalMinSize` alone separates the two: the inflation\n * target picks up the widest row's natural width, while `getMinSize` (untouched\n * from `VBox`) keeps reporting the rows' real minimum of zero. So the column\n * lays out wide and scrolls, and nothing outside the scroll panel ever learns\n * of the content's width.\n *\n * Inert unless the host has opted into horizontal overflow — `inflateForOverflow`\n * ignores an axis the host does not scroll, so with `horizontalScrolling` off\n * this behaves exactly as a plain `VBox` and never measures a renderer.\n */\nclass ListRowColumn extends _VBox {\n\n /**\n * Widens the inflation target to the widest row's natural width, so a list\n * scrolling horizontally lays its rows out at full content width.\n *\n * @returns The children's combined min size, with the width raised to the\n * widest row's natural width.\n */\n protected computeTotalMinSize(): Size {\n const total = super.computeTotalMinSize();\n const container = this.getContainer();\n\n // Only the X-overflow path consumes the width, and the scan below makes\n // every row measure its renderer — so skip it whenever the host isn't\n // scrolling X. Not merely an optimisation: a list has always scrolled Y,\n // so `inflateForOverflow` calls this on every layout of every list, and\n // an ungated scan would put a per-row text measure into all of them.\n if (!container || !this.isOverflowingX()) {\n return total;\n }\n\n let natural = 0;\n\n for (const component of container.getLaidOutComponents()) {\n // The empty-state placeholder shares the column with the rows and has\n // no natural width to contribute — it tracks the viewport instead.\n if (component instanceof SelectableListRow) {\n natural = Math.max(natural, component.getNaturalWidth());\n }\n }\n\n return { width: Math.max(total.width, natural), height: total.height };\n }\n}\n\n/**\n * Owner-supplied gesture callbacks for a {@link SelectableListRow}, each invoked\n * with the row's current pool index and the originating mouse event. The row\n * owns no selection or event-dispatch logic itself — it forwards to the list.\n */\ninterface RowHandlers {\n /** Invoked on a left-click of the row. */\n onClick: (index: number, event: MouseEvent) => void;\n /** Invoked on a right-click of the row (before the list suppresses the native menu). */\n onContextMenu: (index: number, event: MouseEvent) => void;\n /** Invoked on a double-click of the row. */\n onDblClick: (index: number, event: MouseEvent) => void;\n}\n\n/**\n * Abstract base for the framework's custom selectable list controls.\n *\n * Owns the item array, the store binding, the row pool (one\n * {@link SelectableListRow} per visible item), the selection set, the\n * keyboard model (ArrowUp/Down, Home/End, PageUp/Down, Enter/Space,\n * type-ahead), and the ARIA listbox wiring. Concrete subclasses\n * ({@link List}, {@link MultiSelectList}) supply the\n * {@link AbstractSelectableList.reduceSelection} reducer that translates a\n * click or keyboard gesture into a new selection set, and the\n * {@link AbstractSelectableList.setValue} / `getValue` round-trip used by\n * [`Bindable`](/api/core/interfaces/Bindable).\n *\n * Not wrapped with `callable()` — abstract classes are never instantiated;\n * the wrapping rule applies only to concrete component subclasses.\n *\n * @category Components\n */\nabstract class AbstractSelectableList<\n TValue,\n TOptions extends AbstractSelectableListOptions = AbstractSelectableListOptions\n>\n extends AbstractInput<TValue, TOptions>\n{\n protected _items: Array<SelectableListItem> = [];\n protected _rowPool: Array<SelectableListRow> = [];\n protected _selectedSet: Set<number> = new Set();\n protected _anchorIndex: number | null = null;\n protected _focusedIndex: number = -1;\n /** Lower-cased type-ahead buffer; cleared on Escape or timeout. */\n protected _typeAheadBuf: string = \"\";\n /** Timestamp (ms) of the last printable keypress; used to time out the buffer. */\n protected _typeAheadAt: number = 0;\n /**\n * When true, {@link handleRowClick} pulls DOM focus to the list root\n * after the gesture commits so subsequent keystrokes route through\n * `handleKeyDown`. Hosts that own their own focus surface and forward\n * keystrokes (e.g. the ComboBox dropdown, which calls\n * {@link handleKey} from the ComboBox's own `keydown`) set this to\n * `false` so the embedded list never steals focus from the wrapping\n * input.\n */\n protected _focusOnRowClick: boolean = true;\n /**\n * When true (default), keyboard navigation\n * (ArrowUp/Down/Home/End/PageUp/Down) commits the focused row as the\n * selection — the \"selection follows focus\" pattern most listbox\n * controls use. Hosts that want a navigable highlight without\n * committing the row as the selected value (the WAI-ARIA\n * combobox-with-list-autocomplete pattern, exercised by\n * [`AutoCompleteField`](/api/component/input/classes/AutoCompleteField))\n * call `setSelectFollowsFocus(false)` so ArrowUp/Down moves only the\n * focus highlight; Enter / Space / click still commit.\n */\n protected _selectFollowsFocus: boolean = true;\n /**\n * Whether over-long rows scroll horizontally instead of ellipsising.\n * Written only by {@link setHorizontalScrolling}, dispatched from the\n * constructor body once `_innerPanel` exists, so the field-initializer\n * default survives the `super()` cascade without a `declare`.\n */\n private _horizontalScrolling: boolean = false;\n protected _innerPanel: Panel;\n /** Cached empty-state placeholder, built lazily on first need; null until then. */\n private _emptyPlaceholder: Component | null = null;\n /** Whether `_emptyPlaceholder` is currently a child of `_innerPanel`. */\n private _placeholderAttached: boolean = false;\n private _storeRefresh: (() => void) | null = null;\n /**\n * Factory producing each row's content renderer. Defaults to a label\n * renderer reproducing the plain-text rows. Written only by\n * {@link setRendererFactory}, dispatched from the constructor body, so the\n * field-initializer default survives the `super()` cascade without a\n * `declare`.\n */\n private _rendererFactory: () => ListItemRenderer = () => new LabelListItemRenderer();\n /**\n * Listeners for the row-gesture events that carry a row index payload\n * (`contextmenu`, `dblclick`) — kept off the DOM `Event` bus, which fires\n * bare DOM events without the index. `change` / `action` stay on the DOM\n * bus via {@link on}. Mirrors [`Tree`](/api/component/tree/classes/Tree)'s\n * `contextmenu` / `dblclick` wiring.\n */\n private _rowListeners: ListenerBag<\"contextmenu\" | \"dblclick\"> = new ListenerBag();\n\n /**\n * @param options - Caller-supplied options bag.\n * @param subclassDefaults - Per-subclass default bag layered over this\n * class's defaults; subclasses forward their `_defaultXxxOptions`\n * constant here.\n */\n constructor(options?: TOptions, subclassDefaults?: Partial<TOptions>) {\n // `Fit` makes the inner panel fill the list root; the Component\n // default `Absolute` would size every child to its preferredSize\n // (none here) and collapse the inner panel to 0×0. Layered into\n // the defaults bag so concrete subclass defaults and user options\n // can still override, alongside the shared list chrome.\n super(\n options,\n {\n ..._defaultAbstractSelectableListOptions,\n layoutManager: new Fit(),\n ...(subclassDefaults ?? {}),\n } as Partial<TOptions>,\n );\n\n // Element-level chrome: the list root carries the `listbox` role\n // and is focusable. The framework auto-adds the leaf-class name\n // (`\"List\"` / `\"MultiSelectList\"`) to the surface's classList in\n // `Component.init()`; the shared `.List, .MultiSelectList`\n // style rule registered at module init picks both up without an\n // extra opt-in.\n this.getAria().setRole(\"listbox\");\n this.getAria().setTabIndex(0);\n\n // Inner panel is the scrollable row stack. `autoScroll: \"y\"` opts\n // it into the framework's native-overflow path; `VBox` lays the\n // rows out vertically full-width with no gap.\n this._innerPanel = new Panel({\n layoutManager: new ListRowColumn({ spacing: 0, stretching: true }),\n autoScroll: \"y\",\n insets: new Insets(0, 0, 0, 0),\n });\n this.addComponent(this._innerPanel);\n\n // Default floor, but let a caller-supplied minSize option win. The\n // super() cascade writes `_options.minSize` only when the caller passed\n // one (the class default {0,0} lives in the defaults bag, not `_options`),\n // so its presence means \"caller set it\". maxSize stays unbounded.\n if (this._options.minSize === undefined) {\n // 100×100 keeps a short empty/placeholder list a usable size.\n this.setMinSize(100, 100);\n }\n\n Event.addListener(this, \"keydown\", this.handleKeyDown);\n\n // Late-built state: `rendererFactory` / `store` / `items` / `enabled` /\n // `readOnly` were written pure to `_options` by the super-time cascade.\n // Dispatch them now that `_innerPanel` and `_rowPool` exist. The\n // factory is dispatched first so the row pool is built (by `setStore` /\n // `setItems` below) with the caller's renderer on first paint.\n if (this._options.rendererFactory !== undefined) {\n this.setRendererFactory(this._options.rendererFactory);\n }\n\n if (this._options.horizontalScrolling !== undefined) {\n this.setHorizontalScrolling(this._options.horizontalScrolling);\n }\n\n if (this._options.store !== undefined && this._options.displayField !== undefined) {\n this.setStore(this._options.store, this._options.displayField, this._options.valueField, this._options.glyphField, this._options.tooltipField);\n }\n\n if (this._options.items !== undefined) {\n this.setItems(this._options.items);\n }\n\n if (this._options.enabled !== undefined) {\n this.applyEnabled(this._options.enabled);\n }\n\n if (this._options.readOnly !== undefined) {\n this.applyReadOnly(this._options.readOnly);\n }\n\n // Dispatch the empty-state options last, after items/store — a list built\n // *with* items must show no placeholder. Needed because an empty list\n // configured with only `emptyText`/`emptyComponent` never calls syncRows\n // during construction.\n if (this._options.emptyText !== undefined || this._options.emptyComponent !== undefined) {\n this.syncEmptyPlaceholder();\n }\n }\n\n /**\n * Reflects the enabled flag on the ARIA tree, the tabindex, and the\n * cursor. Disabling the list parks the focus index at -1 so a\n * subsequent enable starts fresh, mirroring the native `<select>`\n * the framework replaces. Concrete subclasses can still override\n * for additional behaviour.\n *\n * @param value - The new enabled state.\n */\n protected applyEnabled(value: boolean): void {\n this.getAria().setDisabled(!value);\n this.getAria().setTabIndex(value ? 0 : -1);\n this.setCursor(value ? \"default\" : \"not-allowed\");\n\n if (!value) {\n this._focusedIndex = -1;\n this.refreshRowVisualState();\n this.updateActiveDescendant();\n }\n }\n\n /**\n * Reflects the read-only flag on the ARIA tree. Read-only lists\n * stay focusable and announce their state; the click / keyboard\n * reducers are gated separately in {@link handleRowClick} /\n * {@link handleKeyDown}.\n *\n * @param value - The new read-only state.\n */\n protected applyReadOnly(value: boolean): void {\n this.getAria().setReadOnly(value);\n }\n\n /**\n * Applies an {@link AbstractSelectableListOptions} bag. Item / store\n * fields are written pure into `_options` here and dispatched from the\n * constructor body — the row pool and inner panel only exist after\n * `super()` returns.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This component, for method chaining.\n */\n protected applyOptions(options: TOptions): this {\n super.applyOptions(options);\n\n if (options.items !== undefined) this._options.items = options.items;\n if (options.store !== undefined) this._options.store = options.store;\n if (options.displayField !== undefined) this._options.displayField = options.displayField;\n if (options.valueField !== undefined) this._options.valueField = options.valueField;\n if (options.glyphField !== undefined) this._options.glyphField = options.glyphField;\n if (options.tooltipField !== undefined) this._options.tooltipField = options.tooltipField;\n if (options.rendererFactory !== undefined) this._options.rendererFactory = options.rendererFactory;\n if (options.emptyText !== undefined) this._options.emptyText = options.emptyText;\n if (options.emptyComponent !== undefined) this._options.emptyComponent = options.emptyComponent;\n\n if (options.horizontalScrolling !== undefined) this._options.horizontalScrolling = options.horizontalScrolling;\n\n return this;\n }\n\n /**\n * Sets whether a row wider than the viewport scrolls horizontally instead of\n * ellipsising.\n *\n * Off by default, which is the behaviour every list has always had: rows track\n * the viewport width and a label too long for its row clips with an ellipsis.\n * That is the right default for a narrow rail, and for the dropdown surfaces\n * (`ComboBox`, `AutoCompleteField`) where a horizontal bar under a popup reads\n * as a glitch. Turn it on for a list whose labels carry meaning past the\n * truncation point — a file path, a query — and whose host is too narrow to\n * show them.\n *\n * When on, every row is sized to the widest bound row's natural width (see\n * {@link ListItemRenderer.getContentWidth}) or the viewport, whichever is\n * larger, and the scroll area raises a horizontal scrollbar once the content\n * exceeds it. Rows stay full-width relative to each other, so the selection\n * wash still spans the whole row when scrolled. A custom renderer that does\n * not override `getContentWidth` reports no intrinsic width, so its rows stay\n * at the viewport width and nothing scrolls.\n *\n * @param value - True to scroll over-long rows horizontally.\n *\n * @returns This component, for method chaining.\n */\n setHorizontalScrolling(value: boolean): this {\n if (this._horizontalScrolling === value) {\n return this;\n }\n\n this._horizontalScrolling = value;\n\n // `\"auto\"` adds the X axis to the Y the list has always scrolled. That\n // alone drives everything: it is what lets `ListRowColumn` inflate past\n // the viewport, and what raises the bar once the rows do.\n this._innerPanel.setAutoScroll(value ? \"auto\" : \"y\");\n this.scheduleLayout();\n\n return this;\n }\n\n /**\n * Returns whether over-long rows scroll horizontally rather than ellipsising.\n *\n * @returns True when horizontal scrolling is on.\n */\n isHorizontalScrolling(): boolean {\n return this._horizontalScrolling;\n }\n\n /**\n * Sets the muted placeholder text shown inside the scroll area when the list\n * is empty. Pass `null` to clear it. Takes effect immediately: an already\n * empty list shows (or drops) the placeholder on the next layout.\n *\n * @param text - The placeholder text, or `null` to remove it.\n *\n * @returns This component, for method chaining.\n */\n setEmptyText(text: string | null): this {\n this._options.emptyText = text ?? undefined;\n this.resetEmptyPlaceholder();\n this.syncEmptyPlaceholder();\n\n return this;\n }\n\n /**\n * Returns the configured empty-state placeholder text.\n *\n * @returns The placeholder text, or `null` if none is set.\n */\n getEmptyText(): string | null {\n return this._options.emptyText ?? null;\n }\n\n /**\n * Sets a factory for a custom empty-state placeholder, shown inside the\n * scroll area when the list is empty. Takes precedence over\n * {@link setEmptyText}. Pass `null` to clear it. The returned component\n * should report an unbounded max so the empty list still fills its region.\n *\n * @param factory - The placeholder factory, or `null` to remove it.\n *\n * @returns This component, for method chaining.\n */\n setEmptyComponent(factory: (() => Component) | null): this {\n this._options.emptyComponent = factory ?? undefined;\n this.resetEmptyPlaceholder();\n this.syncEmptyPlaceholder();\n\n return this;\n }\n\n /**\n * Returns the configured empty-state placeholder factory.\n *\n * @returns The placeholder factory, or `null` if none is set.\n */\n getEmptyComponent(): (() => Component) | null {\n return this._options.emptyComponent ?? null;\n }\n\n /**\n * Adds or removes the empty-state placeholder against the current item\n * count. Attaches the placeholder (building it lazily on first need) to\n * `_innerPanel` when an empty-state is configured and the list is empty;\n * detaches it otherwise. A no-op when no empty-state is configured — an\n * opted-out empty list keeps no placeholder child.\n *\n * Called at the tail of {@link syncRows}, from the empty-state setters, and\n * once at construction for a list built empty with an empty-state option.\n */\n protected syncEmptyPlaceholder(): void {\n const configured = this._options.emptyComponent !== undefined || this._options.emptyText !== undefined;\n const wants = configured && this._items.length === 0;\n\n if (wants) {\n if (!this._emptyPlaceholder) {\n this._emptyPlaceholder = this.buildEmptyPlaceholder();\n }\n\n if (!this._placeholderAttached) {\n // weight: 1 makes the single child absorb all leftover main-axis\n // (height) space so the placeholder fills the scroll area; the\n // inner VBox's stretching:true fills the width.\n this._innerPanel.addComponent(this._emptyPlaceholder, { weight: 1 });\n this._placeholderAttached = true;\n }\n } else if (this._placeholderAttached && this._emptyPlaceholder) {\n this._innerPanel.removeComponent(this._emptyPlaceholder);\n this._placeholderAttached = false;\n }\n }\n\n /**\n * Builds the empty-state placeholder: the `emptyComponent` factory's output\n * if set, otherwise a muted, horizontally-centered single-line `Text` from\n * `emptyText` using the list-scoped disabled token.\n *\n * @returns The placeholder component.\n */\n private buildEmptyPlaceholder(): Component {\n const factory = this._options.emptyComponent;\n if (factory) {\n return factory();\n }\n\n const text = new Text(this._options.emptyText ?? \"\", { textAlign: \"center\" });\n text.setForegroundColor(\"var(--ts-ui-list-row-disabled-color, rgb(170, 170, 170))\");\n\n return text;\n }\n\n /**\n * Detaches the cached placeholder (if attached) and drops it, so the next\n * {@link syncEmptyPlaceholder} rebuilds it from the current options. Used by\n * the empty-state setters when the configuration changes.\n */\n private resetEmptyPlaceholder(): void {\n if (this._placeholderAttached && this._emptyPlaceholder) {\n this._innerPanel.removeComponent(this._emptyPlaceholder);\n this._placeholderAttached = false;\n }\n\n this._emptyPlaceholder = null;\n }\n\n /**\n * Returns `null` so a multi-line list surface is treated as a\n * replaced/graphical element by horizontal layouts — matches the\n * behaviour the prior native `<select>`-backed `List` preserved.\n *\n * @returns Always `null`.\n */\n getBaseline(): number | null {\n return null;\n }\n\n /**\n * Returns a shallow copy of the current item array.\n *\n * @returns The items in display order.\n */\n getItems(): Array<SelectableListItem> {\n return this._items.slice();\n }\n\n /**\n * Replaces all items with the given specs. Each entry is either a plain\n * string — keyed by the string itself (`{ key: label }`), so `getValue` /\n * `setValue` round-trip the visible text for the common \"list of names\"\n * case — or a pre-formed {@link SelectableListItem} whose explicit key is kept\n * verbatim. Selection and focus are reset; the row pool is reconciled\n * against the new length.\n *\n * @param items - A single spec or an array of specs. Each spec is a string\n * (keyed by its own value) or a `{ key, label }` object (explicit key).\n *\n * @remarks The caller owns key uniqueness — repeated strings, or an explicit\n * key colliding with a string value, produce duplicate keys, and\n * `getValue` / `setValue` resolve to the first row whose `key` matches, so\n * a duplicate key is merely addressed by its lowest matching row.\n *\n * @returns This component, for method chaining.\n */\n setItems(items: SelectableListItemSpec | Array<SelectableListItemSpec>): this {\n if (!Type.isArray(items)) {\n items = [items as SelectableListItemSpec];\n }\n\n const list = items as Array<SelectableListItemSpec>;\n const built: Array<SelectableListItem> = [];\n\n for (const entry of list) {\n built.push(\n typeof entry === \"string\"\n ? { key: entry, label: entry }\n : { key: (entry as SelectableListItem).key, label: (entry as SelectableListItem).label, glyph: (entry as SelectableListItem).glyph, tooltip: (entry as SelectableListItem).tooltip },\n );\n }\n\n return this.setItemsArray(built);\n }\n\n /**\n * Replaces all items with the given pre-formed `{key, label}` pairs.\n * Mirrors {@link setItems} but skips the auto-keying step so a host\n * that already owns typed items (e.g. the [`ComboBox`](/api/component/input/classes/ComboBox)\n * dropdown pushing a `SelectableListItem` array) can hand them over\n * without the keys being clobbered to stringified indices. Selection and focus are reset; the row pool\n * is reconciled against the new length.\n *\n * Protected on the abstract base so each concrete subclass decides\n * whether to widen it into the public surface — {@link List} does;\n * `MultiSelectList` does not (the multi-select consumers haven't\n * needed the typed-array entry point so far).\n *\n * @param items - The pre-formed item pairs, in display order.\n *\n * @returns This component, for method chaining.\n */\n protected setItemsArray(items: Array<SelectableListItem>): this {\n this._items = items.slice();\n\n this._selectedSet.clear();\n this._anchorIndex = null;\n this._focusedIndex = -1;\n\n this.pauseLayout();\n this.syncRows();\n this.resumeLayout();\n this.updateActiveDescendant();\n\n return this;\n }\n\n /**\n * Appends a new item to the end of the list. A plain string is keyed by the\n * string itself (`{ key: label }`), so `getValue` / `setValue` round-trip the\n * visible text; a pre-formed {@link SelectableListItem} keeps its explicit key\n * verbatim.\n *\n * @param item - A string (keyed by its own value) or a `{ key, label }`\n * object (explicit key).\n *\n * @remarks The caller owns key uniqueness — appending a string equal to an\n * earlier string or explicit key produces a duplicate key, and `getValue` /\n * `setValue` resolve to the first matching row.\n *\n * @returns This component, for method chaining.\n */\n addItem(item: SelectableListItemSpec): this {\n this._items.push(\n typeof item === \"string\"\n ? { key: item, label: item }\n : { key: (item as SelectableListItem).key, label: (item as SelectableListItem).label, glyph: (item as SelectableListItem).glyph, tooltip: (item as SelectableListItem).tooltip },\n );\n\n this.pauseLayout();\n this.syncRows();\n this.resumeLayout();\n\n return this;\n }\n\n /**\n * Binds this list to a store. Records are pulled via `displayField` /\n * `valueField` whenever the store fires `load` / `add` / `remove` /\n * `datachange` / `sync`. Re-binding to a new store de-registers the\n * previous handlers first.\n *\n * @param store - The store to bind to.\n * @param displayField - The record field whose value becomes the row label.\n * @param valueField - Optional. The record field used as the row key;\n * defaults to the record's primary key when omitted.\n * @param glyphField - Optional. The record field whose value becomes each\n * item's `glyph` (read by the glyph renderer); omitted leaves items\n * glyph-less.\n * @param tooltipField - Optional. The record field whose value becomes each\n * item's hover `tooltip`; omitted leaves items tooltip-less.\n *\n * @returns This component, for method chaining.\n */\n setStore(store: AbstractStore, displayField: string, valueField?: string, glyphField?: string, tooltipField?: string): this {\n const oldStore = this._options.store;\n\n if (this._storeRefresh && oldStore) {\n (['load', 'add', 'remove', 'datachange', 'sync'] as const)\n .forEach(e => oldStore.off(e, this._storeRefresh!));\n }\n\n this._options.store = store;\n this._options.displayField = displayField;\n this._options.valueField = valueField;\n this._options.glyphField = glyphField;\n this._options.tooltipField = tooltipField;\n\n const refresh = (): void => this.refreshFromStore();\n this._storeRefresh = refresh;\n\n store.on('load', refresh);\n store.on('add', refresh);\n store.on('remove', refresh);\n store.on('datachange', refresh);\n store.on('sync', refresh);\n\n this.refreshFromStore();\n\n return this;\n }\n\n /**\n * Returns the currently bound store, or `null` when none is set.\n *\n * @returns The bound store, or `null`.\n */\n getStore(): AbstractStore | null {\n return this._options.store ?? null;\n }\n\n /**\n * Replaces the renderer factory. Every existing pool row swaps to a fresh\n * renderer from the new factory and the pool is re-synced so each renderer\n * rebinds to its item before the next layout. New rows built afterwards use\n * the new factory too.\n *\n * @param factory - Zero-argument factory producing a renderer per row.\n *\n * @returns This component, for method chaining.\n */\n setRendererFactory(factory: () => ListItemRenderer): this {\n this._rendererFactory = factory;\n\n for (const row of this._rowPool) {\n row.setRenderer(factory());\n }\n\n this.pauseLayout();\n this.syncRows();\n this.resumeLayout();\n\n return this;\n }\n\n /**\n * Returns the renderer factory currently in use.\n *\n * @returns The zero-argument renderer factory.\n */\n getRendererFactory(): () => ListItemRenderer {\n return this._rendererFactory;\n }\n\n /**\n * Returns the store record corresponding to the most recent\n * single-selection anchor — matches the prior `<select>`-backed\n * behaviour where `getSelectedRecord()` returned the active option's\n * record. For {@link MultiSelectList} consumers wanting the full set\n * of selected records, use [`getSelectedRecords`](/api/component/list/classes/MultiSelectList#getselectedrecords).\n *\n * @returns The selected [`ModelRecord`](/api/data/classes/ModelRecord), or `undefined` when no\n * store is bound or nothing is selected.\n */\n getSelectedRecord(): ModelRecord | undefined {\n const store = this._options.store;\n\n if (!store) {\n return undefined;\n }\n\n const idx = this.getSelectedIndex();\n\n if (idx < 0) {\n return undefined;\n }\n\n return store.getRecords()[idx];\n }\n\n /**\n * Returns the index of the most recent single-selection anchor, or\n * `-1` when nothing is selected.\n *\n * @returns The anchor index.\n */\n getSelectedIndex(): number {\n if (this._anchorIndex !== null && this._selectedSet.has(this._anchorIndex)) {\n return this._anchorIndex;\n }\n\n if (this._selectedSet.size === 0) {\n return -1;\n }\n\n // Fallback when the anchor is gone (e.g. after a programmatic\n // setValues that bypassed the click reducer): return the lowest\n // selected index so the contract stays single-valued.\n return Math.min(...this._selectedSet);\n }\n\n /**\n * Sets the single-selection anchor. The selection set becomes\n * exactly `{idx}` (or empty for `idx < 0`); `_focusedIndex` follows.\n * Optionally fires the `change` event so binding listeners run.\n *\n * @param idx - The zero-based index to select, or a negative value\n * to clear the selection.\n * @param fireEvent - When `true` (default), fires the `change` event\n * after updating; pass `false` for programmatic writes.\n *\n * @returns This component, for method chaining.\n */\n setSelectedIndex(idx: number, fireEvent: boolean = true): this {\n this._selectedSet.clear();\n\n if (idx >= 0 && idx < this._items.length) {\n this._selectedSet.add(idx);\n this._anchorIndex = idx;\n this._focusedIndex = idx;\n } else {\n this._anchorIndex = null;\n this._focusedIndex = -1;\n }\n\n this.refreshRowVisualState();\n this.updateActiveDescendant();\n\n if (fireEvent) {\n this.fireChange();\n }\n\n return this;\n }\n\n /**\n * Registers a listener for one of this list's events. `\"action\"` is a\n * typed semantic shorthand over {@link Event.addListener} for the DOM\n * change event — fired only on user-driven (click / keyboard) selection\n * changes, never on programmatic `setValue` / `setValues`, matching the\n * prior native `<select>`-backed semantics. `\"change\"` and `\"binding\"`\n * are the inherited {@link AbstractInput} listener-bag events.\n *\n * `\"contextmenu\"` fires when a row is right-clicked, with the row index and\n * the raw {@link MouseEvent} (the native menu is suppressed); `\"dblclick\"`\n * fires on a row double-click with the same payload. Both carry the row\n * index directly, so hosts need not walk the row DOM to resolve it.\n *\n * @param event - The event name.\n * @param listener - The callback to invoke when the event fires.\n *\n * @returns This component, for method chaining.\n */\n on(event: \"action\", listener: Function): this;\n on(event: \"change\", listener: (value: TValue) => void): this;\n on(event: \"binding\", listener: () => void): this;\n on(event: \"contextmenu\", listener: (index: number, event: MouseEvent) => void): this;\n on(event: \"dblclick\", listener: (index: number, event: MouseEvent) => void): this;\n on(event: \"action\" | \"change\" | \"binding\" | \"contextmenu\" | \"dblclick\", listener: Function): this {\n if (event === \"action\") {\n Event.addListener(this, \"change\", listener);\n\n return this;\n }\n\n if (event === \"contextmenu\" || event === \"dblclick\") {\n this._rowListeners.add(event, listener);\n\n return this;\n }\n\n return super.on(event as \"change\", listener as (value: TValue) => void);\n }\n\n /**\n * Removes a previously registered listener. The exact callback\n * reference must match the one passed to {@link on}.\n *\n * @param event - The event the listener was registered for.\n * @param listener - The callback to remove.\n *\n * @returns This component, for method chaining.\n */\n off(event: \"action\" | \"change\" | \"binding\" | \"contextmenu\" | \"dblclick\", listener: Function): this {\n if (event === \"action\") {\n Event.removeListener(this, \"change\", listener);\n\n return this;\n }\n\n if (event === \"contextmenu\" || event === \"dblclick\") {\n this._rowListeners.remove(event, listener);\n\n return this;\n }\n\n return super.off(event as \"change\" | \"binding\", listener);\n }\n\n /**\n * Subclass hook: translate a click or keyboard gesture at `idx` into\n * a new selection set. The modifier-key flags are normalised so the\n * same reducer runs for mouse and keyboard origins.\n *\n * @param idx - The row index targeted by the gesture.\n * @param ev - Normalised modifier flags. `ctrl` covers both Ctrl and\n * Cmd (macOS).\n */\n protected abstract reduceSelection(idx: number, ev: { ctrl: boolean, shift: boolean }): void;\n\n /**\n * Subclass hook used by user-driven gestures: encode the current\n * selection set into the subclass's `TValue` shape and fire change /\n * binding listeners. Called from the click and keyboard reducers\n * after {@link reduceSelection} mutates the selection set.\n */\n protected abstract notifyUserChange(): void;\n\n /**\n * Rebuilds `_items` from the bound store's current records. Preserves\n * the previously-selected key when possible — if the key still maps\n * to an item, the selection survives; otherwise the selection is\n * cleared and the focus collapses to row 0 (matching the native\n * `<select>` refresh behaviour the prior `List` inherited).\n */\n protected refreshFromStore(): void {\n const store = this._options.store;\n const displayField = this._options.displayField;\n const valueField = this._options.valueField;\n const glyphField = this._options.glyphField;\n const tooltipField = this._options.tooltipField;\n\n if (!store || !displayField) {\n return;\n }\n\n // Remember the active selection key so we can re-locate it in\n // the new item set; survives partial reorderings / additions.\n const previousAnchorKey = this._anchorIndex !== null && this._items[this._anchorIndex]\n ? this._items[this._anchorIndex].key\n : null;\n\n this._items = [];\n this._selectedSet.clear();\n this._anchorIndex = null;\n\n const records = store.getRecords();\n let restoredAnchor = -1;\n\n for (let i = 0; i < records.length; i++) {\n const record = records[i];\n const label = String(record.get(displayField));\n const key = valueField\n ? String(record.get(valueField))\n : String(record.getId());\n const glyph = glyphField ? String(record.get(glyphField)) : undefined;\n const tooltip = tooltipField ? String(record.get(tooltipField)) : undefined;\n\n this._items.push({ key, label, glyph, tooltip });\n\n if (previousAnchorKey !== null && key === previousAnchorKey) {\n restoredAnchor = i;\n }\n }\n\n if (restoredAnchor >= 0) {\n this._selectedSet.add(restoredAnchor);\n this._anchorIndex = restoredAnchor;\n this._focusedIndex = restoredAnchor;\n } else {\n this._focusedIndex = this._items.length > 0 ? 0 : -1;\n }\n\n this.pauseLayout();\n this.syncRows();\n this.resumeLayout();\n this.updateActiveDescendant();\n }\n\n /**\n * Reconciles the row pool with `_items`. Overlapping rows have their\n * label, index, selected, and focused state updated; surplus items\n * spawn new rows; surplus rows are removed.\n */\n protected syncRows(): void {\n const newLen = this._items.length;\n const oldLen = this._rowPool.length;\n const overlap = Math.min(newLen, oldLen);\n\n for (let i = 0; i < overlap; i++) {\n const row = this._rowPool[i];\n row.updateItem(this._items[i], i);\n row.setIndex(i);\n row.setSelected(this._selectedSet.has(i));\n row.setFocused(i === this._focusedIndex);\n }\n\n if (newLen > oldLen) {\n for (let i = oldLen; i < newLen; i++) {\n const row = new SelectableListRow(\n {\n onClick: (idx, e) => this.handleRowClick(idx, e),\n onContextMenu: (idx, e) => this.handleRowContextMenu(idx, e),\n onDblClick: (idx, e) => this.handleRowDblClick(idx, e),\n },\n i,\n this._rendererFactory,\n );\n row.updateItem(this._items[i], i);\n row.setSelected(this._selectedSet.has(i));\n row.setFocused(i === this._focusedIndex);\n this._innerPanel.addComponent(row);\n this._rowPool.push(row);\n }\n } else if (newLen < oldLen) {\n for (let i = newLen; i < oldLen; i++) {\n // Drop the row's tooltip attachment (a static id-keyed map) before\n // discarding the row, so a shrinking pool doesn't leak entries.\n Tooltip.detach(this._rowPool[i]);\n this._innerPanel.removeComponent(this._rowPool[i]);\n }\n this._rowPool.splice(newLen);\n }\n\n // Toggle the empty-state placeholder against the new item count. Runs\n // after the row reconciliation, so on a 0→N transition the rows are in\n // place before the placeholder is dropped (and on N→0, dropped rows are\n // gone before the placeholder attaches). syncRows runs inside a paused\n // layout, so the transient coexistence never renders.\n this.syncEmptyPlaceholder();\n }\n\n /**\n * Pushes the cached selection / focus state into every pool row.\n * Called after a selection mutation (click / keyboard reducer or\n * programmatic write) so the visible chrome catches up without a\n * full `syncRows` reconciliation.\n */\n protected refreshRowVisualState(): void {\n for (let i = 0; i < this._rowPool.length; i++) {\n const row = this._rowPool[i];\n row.setSelected(this._selectedSet.has(i));\n row.setFocused(i === this._focusedIndex);\n }\n }\n\n /**\n * Mirrors `_focusedIndex` onto `aria-activedescendant` so assistive\n * tech tracks the keyboard-focus row. Clears the attribute when no\n * row holds focus. Points at the row's framework-generated id — the\n * Event system already keys listeners off that id, so rewriting the\n * DOM id to a synthetic value (e.g. `ListRow-N`) would break row\n * click / pointerdown delivery.\n */\n protected updateActiveDescendant(): void {\n if (this._focusedIndex < 0 || this._focusedIndex >= this._rowPool.length) {\n this.getAria().setActiveDescendant(\"\");\n\n return;\n }\n\n this.getAria().setActiveDescendant(this._rowPool[this._focusedIndex].getId());\n }\n\n /**\n * Fires the `change` event so `on(\"change\", fn)` subscribers and\n * `notifyChange`-fed bindings run. Subclasses route their own\n * `notifyUserChange` through this after the reducer commits.\n */\n protected fireChange(): void {\n const element = this.getElement();\n\n if (element) {\n Event.fireEvent(this, \"change\");\n }\n\n this.notifyChange(this.getValue());\n }\n\n /**\n * Handles a click on a pool row: dispatches the gesture through the\n * subclass's {@link reduceSelection}, syncs the visible chrome, and\n * fires user-change notifications. The list root takes focus on\n * click (rows are not focusable) so subsequent keyboard navigation\n * starts from the clicked row.\n *\n * @param idx - The row index that was clicked.\n * @param e - The original mouse event — modifier-key flags drive the\n * reducer's multi-select branch.\n */\n protected handleRowClick(idx: number, e: MouseEvent): void {\n if (!this.isEnabled() || this.isReadOnly()) {\n return;\n }\n\n if (idx < 0 || idx >= this._items.length) {\n return;\n }\n\n this.reduceSelection(idx, { ctrl: e.ctrlKey || e.metaKey, shift: e.shiftKey });\n this.refreshRowVisualState();\n this.updateActiveDescendant();\n\n if (this._focusOnRowClick) {\n // Pull DOM focus back to the list root so subsequent keystrokes\n // route through `handleKeyDown` — rows themselves are not\n // focusable, only the listbox surface is. Suppressed when the\n // list is hosted by a focus-managing parent (e.g. the\n // ComboBox dropdown) so a programmatic focus shift can't\n // tear down a wrapping cell editor's input.\n this.focus();\n }\n\n this.notifyUserChange();\n }\n\n /**\n * Handles a right-click on a pool row: suppresses the browser's native\n * context menu and fires the `\"contextmenu\"` event with the row index and\n * the raw event. Deliberately does not change the selection — a host that\n * wants the right-clicked row highlighted calls {@link setSelectedIndex}\n * from its listener (mirrors [`Tree`](/api/component/tree/classes/Tree)'s\n * contextmenu contract). Out-of-range indices are ignored.\n *\n * @param idx - The row index that was right-clicked.\n * @param e - The original contextmenu event.\n */\n protected handleRowContextMenu(idx: number, e: MouseEvent): void {\n if (idx < 0 || idx >= this._items.length) {\n return;\n }\n\n e.preventDefault();\n this._rowListeners.fire(\"contextmenu\", idx, e);\n }\n\n /**\n * Handles a double-click on a pool row: fires the `\"dblclick\"` event with\n * the row index and the raw event. The first click of the pair already ran\n * through {@link handleRowClick} and set the selection, so this only layers\n * an activation signal on top. Out-of-range indices are ignored.\n *\n * @param idx - The row index that was double-clicked.\n * @param e - The original dblclick event.\n */\n protected handleRowDblClick(idx: number, e: MouseEvent): void {\n if (idx < 0 || idx >= this._items.length) {\n return;\n }\n\n this._rowListeners.fire(\"dblclick\", idx, e);\n }\n\n /**\n * Toggles whether a row-click gesture pulls DOM focus to the list\n * root after the commit. Hosts that own their own focus surface\n * (the ComboBox dropdown is the canonical example) call\n * `setFocusOnRowClick(false)` so the embedded list never steals\n * focus from a wrapping input or cell editor.\n *\n * @param value - `false` to suppress the focus call.\n *\n * @returns This component, for method chaining.\n */\n setFocusOnRowClick(value: boolean): this {\n this._focusOnRowClick = value;\n\n return this;\n }\n\n /**\n * Toggles whether keyboard navigation (ArrowUp/Down/Home/End/PageUp/Down)\n * commits the focused row as the selection. When `false`, the focus\n * highlight moves but the selection set is untouched and the `change`\n * event does not fire. Enter / Space / click still commit. The\n * [`AutoCompleteField`](/api/component/input/classes/AutoCompleteField)\n * dropdown sets this to `false` so ArrowUp/Down previews a row\n * without writing it into the host TextField.\n *\n * @param value - `false` to disable the selection-follows-focus\n * coupling on keyboard navigation.\n *\n * @returns This component, for method chaining.\n */\n setSelectFollowsFocus(value: boolean): this {\n this._selectFollowsFocus = value;\n\n return this;\n }\n\n /**\n * Returns the current keyboard-focus index, or `-1` when no row\n * holds focus.\n *\n * @returns The zero-based focus index, or `-1`.\n */\n getFocusedIndex(): number {\n return this._focusedIndex;\n }\n\n /**\n * Returns the framework-generated DOM element id of the keyboard-focus\n * row, suitable for writing into a host input's `aria-activedescendant`.\n * Returns `null` when no row holds focus or the focused row hasn't\n * been instantiated in the pool yet (rows materialise lazily as the\n * pool reconciles against the item array).\n *\n * @returns The focused row's element id, or `null`.\n */\n getFocusedRowId(): string | null {\n if (this._focusedIndex < 0 || this._focusedIndex >= this._rowPool.length) {\n return null;\n }\n\n return this._rowPool[this._focusedIndex].getId();\n }\n\n /**\n * Public entry point used by hosts that keep DOM focus on their own\n * surface while embedding this list (e.g. the [`ComboBox`](/api/component/input/classes/ComboBox)\n * dropdown forwarding keystrokes from the ComboBox surface).\n * Returns `true` when the list consumed the key — the caller\n * should then `e.preventDefault()` and stop further processing.\n * Escape is intentionally NOT consumed here so the host can use\n * it to close the wrapping overlay; the list-focused entry point\n * (the protected `handleKeyDown` registered as the list's own\n * `keydown` listener) still handles Escape inline.\n *\n * @param e - The keyboard event captured by the host.\n *\n * @returns `true` when the list consumed the key.\n */\n handleKey(e: KeyboardEvent): boolean {\n if (!this.isEnabled() || this.isReadOnly()) {\n return false;\n }\n\n if (this._items.length === 0) {\n return false;\n }\n\n if (e.key === \"Escape\") {\n return false;\n }\n\n const ctrl = e.ctrlKey || e.metaKey;\n\n if (this.handleNavigationKey(e, ctrl)) {\n return true;\n }\n\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n this.commitFocusedRow(ctrl, e.shiftKey);\n\n return true;\n }\n\n if (!ctrl && !e.altKey && e.key.length === 1) {\n this.handleTypeAhead(e.key);\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Handles keydown on the list root: ArrowUp/Down/Home/End move the\n * focus index, PageUp/Down move by visible-row count, Enter/Space\n * commits the focused row, Ctrl+A in subclasses extends to select\n * all (handled in {@link MultiSelectList}), Escape clears the\n * type-ahead buffer, and printable characters feed the type-ahead\n * search.\n *\n * @param e - The keyboard event.\n */\n protected handleKeyDown(e: KeyboardEvent): void {\n if (!this.isEnabled() || this.isReadOnly()) {\n return;\n }\n\n if (this._items.length === 0) {\n return;\n }\n\n const ctrl = e.ctrlKey || e.metaKey;\n\n if (e.key === \"Escape\") {\n this._typeAheadBuf = \"\";\n\n return;\n }\n\n if (this.handleNavigationKey(e, ctrl)) {\n return;\n }\n\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n this.commitFocusedRow(ctrl, e.shiftKey);\n\n return;\n }\n\n // Printable single-character key — feed the type-ahead buffer.\n // `key.length === 1` filters out named keys (`\"Tab\"`, `\"Shift\"`,\n // `\"ArrowDown\"`, …) without an explicit allow-list.\n if (!ctrl && !e.altKey && e.key.length === 1) {\n this.handleTypeAhead(e.key);\n }\n }\n\n /**\n * Subset of `handleKeyDown` that processes the arrow / Home / End /\n * Page-* navigation keys. Returns `true` when a key was handled so\n * the caller can skip the remaining branches.\n *\n * @param e - The keyboard event.\n * @param ctrl - Pre-computed Ctrl-or-Cmd flag.\n *\n * @returns `true` when the key was handled.\n */\n protected handleNavigationKey(e: KeyboardEvent, ctrl: boolean): boolean {\n const navigable = new Set([\n \"ArrowDown\", \"ArrowUp\", \"Home\", \"End\", \"PageDown\", \"PageUp\",\n ]);\n\n if (!navigable.has(e.key)) {\n return false;\n }\n\n e.preventDefault();\n\n const viewportH = this.getHeight() || ROW_HEIGHT_PX;\n const pageSize = Math.max(1, Math.floor(viewportH / ROW_HEIGHT_PX));\n const curr = this._focusedIndex < 0 ? 0 : this._focusedIndex;\n let next: number;\n\n if (e.key === \"ArrowDown\") {\n next = Math.min(curr + 1, this._items.length - 1);\n } else if (e.key === \"ArrowUp\") {\n next = Math.max(curr - 1, 0);\n } else if (e.key === \"PageDown\") {\n next = Math.min(curr + pageSize, this._items.length - 1);\n } else if (e.key === \"PageUp\") {\n next = Math.max(curr - pageSize, 0);\n } else if (e.key === \"Home\") {\n next = 0;\n } else {\n next = this._items.length - 1;\n }\n\n this.moveFocus(next, ctrl, e.shiftKey);\n\n return true;\n }\n\n /**\n * Moves the keyboard focus to `idx`. By default the move also runs\n * the subclass's {@link reduceSelection} so single-select lists track\n * the focus highlight; pass `ctrl: true` to move the focus without\n * touching the selection (the standard \"browse without committing\"\n * gesture). `shift: true` forwards the range-extend hint to the\n * reducer. When the list-wide {@link setSelectFollowsFocus} flag is\n * `false`, the commit branch is suppressed entirely — the focus\n * highlight moves but the selection set is untouched and\n * `notifyUserChange` does not fire.\n *\n * @param idx - The new focus index.\n * @param ctrl - When `true`, skip the selection update.\n * @param shift - When `true`, ask the reducer to extend the\n * selection from `_anchorIndex` to `idx`.\n */\n protected moveFocus(idx: number, ctrl: boolean, shift: boolean): void {\n this._focusedIndex = idx;\n\n const commit = !ctrl && this._selectFollowsFocus;\n\n if (commit) {\n this.reduceSelection(idx, { ctrl: false, shift });\n }\n\n this.refreshRowVisualState();\n this.updateActiveDescendant();\n this.scrollIndexIntoView(idx);\n\n if (commit) {\n this.notifyUserChange();\n }\n }\n\n /**\n * Commits the focused row through {@link reduceSelection}. Mirrors\n * the gesture of clicking that row with the same modifier keys.\n *\n * @param ctrl - Ctrl-or-Cmd modifier flag at the time of the keypress.\n * @param shift - Shift modifier flag at the time of the keypress.\n */\n protected commitFocusedRow(ctrl: boolean, shift: boolean): void {\n if (this._focusedIndex < 0) {\n return;\n }\n\n this.reduceSelection(this._focusedIndex, { ctrl, shift });\n this.refreshRowVisualState();\n this.updateActiveDescendant();\n this.notifyUserChange();\n }\n\n /**\n * Appends `ch` to the type-ahead buffer (after timing out the\n * previous buffer when more than {@link TYPE_AHEAD_TIMEOUT_MS}\n * elapsed since the last key) and jumps the focus to the first item\n * whose lower-cased label starts with the buffer.\n *\n * @param ch - The character key pressed.\n */\n protected handleTypeAhead(ch: string): void {\n const now = Date.now();\n\n if (now - this._typeAheadAt > TYPE_AHEAD_TIMEOUT_MS) {\n this._typeAheadBuf = \"\";\n }\n\n this._typeAheadBuf += ch.toLowerCase();\n this._typeAheadAt = now;\n\n const buf = this._typeAheadBuf;\n const idx = this._items.findIndex(item => item.label.toLowerCase().startsWith(buf));\n\n if (idx < 0) {\n return;\n }\n\n // Type-ahead moves only the focus highlight; selection is\n // unaffected (same behaviour as the native `<select>` it\n // replaces — typing a letter previews the row without\n // committing).\n this._focusedIndex = idx;\n this.refreshRowVisualState();\n this.updateActiveDescendant();\n this.scrollIndexIntoView(idx);\n }\n\n /**\n * Scrolls the inner panel so the row at `idx` is fully visible, with\n * no movement when it already is. Reads / writes the panel's native\n * `scrollTop` directly — the framework's typed scroll setter only\n * lives on the `VirtualScroller`-backed components (`Table.Body`,\n * `Tree`); the `Panel`-with-`autoScroll: \"y\"` surface relies on\n * native browser overflow and exposes no setter.\n *\n * @param idx - The row index to scroll into view.\n */\n protected scrollIndexIntoView(idx: number): void {\n if (idx < 0 || idx >= this._items.length) {\n return;\n }\n\n const panelEl = this._innerPanel.getElement();\n\n if (!panelEl) {\n return;\n }\n\n const metrics = DOM.source.getScrollMetrics(panelEl);\n const top = idx * ROW_HEIGHT_PX;\n const bottom = top + ROW_HEIGHT_PX;\n const scrollTop = metrics.scrollTop;\n const visibleBottom = scrollTop + metrics.clientHeight;\n\n if (top < scrollTop) {\n DOM.sink.apply(panelEl, { scrollTop: top });\n } else if (bottom > visibleBottom) {\n DOM.sink.apply(panelEl, { scrollTop: bottom - metrics.clientHeight });\n }\n }\n}\n\nexport { AbstractSelectableList, SelectableListRow };\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { AbstractSelectableList, AbstractSelectableListOptions, SelectableListItem } from \"~/component/list/AbstractSelectableList.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link List}.\n *\n * @category Components\n */\nexport interface ListOptions extends AbstractSelectableListOptions {\n selectedIndex?: number;\n value?: string;\n selectedItem?: string;\n}\n\n/**\n * A scrollable single-selection list box.\n *\n * Rendered as a `<div role=\"listbox\">` populated with `<div role=\"option\">`\n * rows. Implements [`Bindable<string>`](/api/core/interfaces/Bindable) so it can be plugged into a\n * [`Binding`](/api/core/classes/Binding) directly. Keyboard model mirrors the WAI-ARIA listbox\n * pattern: ArrowUp/Down moves and selects, Home/End jump to the\n * extremes, PageUp/Down jump by viewport-row count, Enter / Space\n * commits the focused row, and printable characters drive a 700ms\n * type-ahead search.\n *\n * @category Components\n */\nclass List extends AbstractSelectableList<string, ListOptions> {\n\n /**\n * @param options - Optional. Construction-time options applied to\n * the list.\n */\n constructor(options?: ListOptions) {\n super(options);\n\n // Late-built state: `selectedIndex` / `value` / `selectedItem`\n // were written pure to `_options` by the super-time cascade.\n // Dispatch them now that the row pool and selection set exist\n // (`super()` already populated `_items` from `items` / `store`,\n // and applied `enabled` / `readOnly`).\n if (this._options.selectedIndex !== undefined) {\n this.setSelectedIndex(this._options.selectedIndex, false);\n }\n\n if (this._options.value !== undefined) {\n this.setValue(this._options.value);\n }\n\n if (this._options.selectedItem !== undefined) {\n this.setValue(this._options.selectedItem);\n }\n }\n\n /**\n * Applies a {@link ListOptions} bag. Single-select state fields are\n * written pure into `_options` here and dispatched from the constructor\n * body after the row pool is built.\n *\n * @param options - The options bag carrying the values to apply.\n *\n * @returns This component, for method chaining.\n */\n protected applyOptions(options: ListOptions): this {\n super.applyOptions(options);\n\n if (options.selectedIndex !== undefined) this._options.selectedIndex = options.selectedIndex;\n if (options.value !== undefined) this._options.value = options.value;\n if (options.selectedItem !== undefined) this._options.selectedItem = options.selectedItem;\n\n return this;\n }\n\n /**\n * Pushes pre-formed {@link SelectableListItem} pairs into the list,\n * bypassing the auto-keying that {@link setItems} applies to a\n * label-only array. Intended for hosts that already own typed\n * `{key, label}` data (e.g. the [`ComboBox`](/api/component/input/classes/ComboBox)\n * dropdown forwarding its item array). Selection and focus are\n * reset; the row pool is reconciled against the new length.\n *\n * @param items - The pre-formed item pairs, in display order.\n *\n * @returns This component, for method chaining.\n */\n setItemsArray(items: Array<SelectableListItem>): this {\n return super.setItemsArray(items);\n }\n\n /**\n * Selects the row whose `key` matches `value`. No-op when the value\n * doesn't appear in the current item set — same behaviour the prior\n * native `<select>` had when assigned an unknown `value`.\n *\n * @param value - The row key to select.\n *\n * @returns This component, for method chaining.\n */\n setValue(value: string): this {\n const idx = this._items.findIndex(item => item.key === value);\n\n this.setSelectedIndex(idx, false);\n\n return this;\n }\n\n /**\n * Returns the key of the currently selected row.\n *\n * @returns The selected row's key, or an empty string when nothing\n * is selected.\n */\n getValue(): string {\n const idx = this.getSelectedIndex();\n\n if (idx < 0 || idx >= this._items.length) {\n return \"\";\n }\n\n return this._items[idx].key;\n }\n\n /**\n * Reduces a click / keyboard gesture into the single-select shape:\n * the modifier keys are ignored, the selection becomes exactly\n * `{idx}`, and the anchor / focus collapse to the clicked row.\n *\n * @param idx - The row index the gesture targeted.\n * @param _ev - Ignored; single-select doesn't branch on modifier keys.\n */\n protected reduceSelection(idx: number, _ev: { ctrl: boolean, shift: boolean }): void {\n this._selectedSet.clear();\n this._selectedSet.add(idx);\n this._anchorIndex = idx;\n this._focusedIndex = idx;\n }\n\n /**\n * User-driven selection commit. Mirrors the prior native `<select>`\n * `change` event semantics — fired from the click / keyboard\n * reducers, not from programmatic `setValue` / `setSelectedIndex(idx,\n * false)`.\n */\n protected notifyUserChange(): void {\n this.fireChange();\n }\n\n}\n\nconst ListCallable = callable(List);\ntype ListCallable = List;\nexport {\n List as _List,\n ListCallable as List,\n};\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./DOM-DeF_dWe2.js";import{l as t,n}from"./Position-DJKB0N3l.js";import{f as r,t as i,y as a}from"./Component-Bs6TvaGZ.js";import{t as o}from"./Glyph-6Ki3A1_a.js";import{a as s,i as c,n as l}from"./Tooltip-5BR7yYEX.js";import{n as u,r as d}from"./AnimatedDropdown-BFz-DO7R.js";import{t as f}from"./Text-DJoUHukb.js";import{n as p}from"./VBox-CdV9VGxR.js";var m=r(class MenuItem extends i{static HEIGHT=24;static SEPARATOR_HEIGHT=9;static TEXT_INSET=8;static ICON_ZONE=24;static CHEVRON_ZONE=16;static RIGHT_PAD=8;static TEXT_GAP=10;_config;_onActivate;_onOpenSubmenu;_cssVarPrefix;_iconText=null;_iconGlyph=null;_titleText=null;_shortcutText=null;_chevronText=null;_iconStart=null;_titleColumn=null;_submenuTimer=null;_onMouseOver;_onMouseOut;_onClick;constructor(e,t,n,r=`menu-bar`,i){if(super(),this._config=e,this._onActivate=t,this._onOpenSubmenu=n,this._cssVarPrefix=r,e.separator){this.setHeight(MenuItem.SEPARATOR_HEIGHT),this.setPreferredSize(0,MenuItem.SEPARATOR_HEIGHT),this.setBackgroundColor(`transparent`),this.setElementCSSRule(`borderTop`,`1px solid var(--ts-ui-${r}-separator-color, rgb(220, 220, 220))`),this.setElementCSSRule(`margin`,`4px 0`),this.getAria().setRole(`separator`),this._onMouseOver=()=>{},this._onMouseOut=()=>{},this._onClick=()=>{},i&&this.applyOptions(i);return}let s=e.enabled!==!1;this.setHeight(MenuItem.HEIGHT),this.setPreferredSize(0,MenuItem.HEIGHT),this.setBackgroundColor(`transparent`),this.setElementCSSRule(`fontSize`,`var(--ts-ui-button-font-size, var(--ts-ui-font-size))`),this.setCursor(s?`pointer`:`default`),this.getAria().setRole(`menuitem`),s||(this.setForegroundColor(`var(--ts-ui-${r}-item-disabled-color, rgb(170, 170, 170))`),this.getAria().setDisabled(!0)),e.glyph?(this._iconGlyph=new o(e.glyph),this._iconGlyph.setPointerEvents(`none`),this._iconGlyph.setPreferredSize(16,16),e.glyphColor&&this._iconGlyph.setForegroundColor(e.glyphColor),this.addComponent(this._iconGlyph)):(this._iconText=new f(e.icon??``),this._iconText.setPointerEvents(`none`),this._iconText.centerInHeight(MenuItem.HEIGHT),this._iconText.setVisible(!!e.icon),this.addComponent(this._iconText)),this._titleText=new f(e.text??``),this._titleText.setPointerEvents(`none`),this._titleText.centerInHeight(MenuItem.HEIGHT),this._titleText.setWhiteSpace(`nowrap`),this._titleText.setOverflow(`hidden`),this._titleText.setTextOverflow(`ellipsis`),this.addComponent(this._titleText),e.shortcut&&(this._shortcutText=new f(e.shortcut),this._shortcutText.setPointerEvents(`none`),this._shortcutText.centerInHeight(MenuItem.HEIGHT),this._shortcutText.setTextAlign(`left`),this._shortcutText.setForegroundColor(`var(--ts-ui-${r}-item-shortcut-color, rgb(140, 140, 140))`),this.addComponent(this._shortcutText)),this.hasSubmenu()&&(this._chevronText=new f(`▶`),this._chevronText.setPointerEvents(`none`),this._chevronText.centerInHeight(MenuItem.HEIGHT),this._chevronText.setTextAlign(`center`),this._chevronText.setForegroundColor(`var(--ts-ui-${r}-item-shortcut-color, rgb(140, 140, 140))`),this.addComponent(this._chevronText),this.getAria().setHasPopup(`menu`),this.getAria().setExpanded(!1)),this._onMouseOver=()=>{this.setFocused(!0),s&&this.hasSubmenu()?this._submenuTimer=setTimeout(()=>{this._submenuTimer=null,this._onOpenSubmenu(this)},150):this._onOpenSubmenu(this)},this._onMouseOut=()=>{this.setFocused(!1),this._submenuTimer!==null&&(clearTimeout(this._submenuTimer),this._submenuTimer=null)},this._onClick=()=>{s&&!this.hasSubmenu()&&this._onActivate()},a.addListener(this,`mouseover`,this._onMouseOver),a.addListener(this,`mouseout`,this._onMouseOut),a.addListener(this,`click`,this._onClick),i&&this.applyOptions(i)}applyOptions(e){return super.applyOptions(e),e.text!==void 0&&this._titleText&&this._titleText.setText(e.text),e.enabled!==void 0&&(this.getAria().setDisabled(!e.enabled),this.setCursor(e.enabled?`pointer`:`default`)),e.focused!==void 0&&this.setFocused(e.focused),this}hasSubmenu(){return!!this._config.submenu}hasIcon(){return!!this._config.icon||!!this._config.glyph}titleTextWidth(){return Math.ceil(this._titleText?.getPreferredSize()?.width??0)}shortcutTextWidth(){return!this._config.shortcut||this._shortcutText===null?0:Math.ceil(this._shortcutText.getPreferredSize()?.width??0)}setColumns(e,t){this._iconStart=e,this._titleColumn=t,this.scheduleLayout()}getSubmenuConfig(){return this._config.submenu??null}setFocused(e){return this.setBackgroundColor(e?`var(--ts-ui-${this._cssVarPrefix}-item-hover-bg, rgba(30, 100, 200, 0.12))`:`transparent`),this}isSeparator(){return!!this._config.separator}isEnabled(){return this._config.enabled!==!1}activate(){this.isSeparator()||!this.isEnabled()||(this.hasSubmenu()?this._onOpenSubmenu(this):this._onActivate())}dispose(){this._submenuTimer!==null&&(clearTimeout(this._submenuTimer),this._submenuTimer=null),a.removeListener(this,`mouseover`,this._onMouseOver),a.removeListener(this,`mouseout`,this._onMouseOut),a.removeListener(this,`click`,this._onClick)}getBaseline(){return this._titleText===null?null:this.wrapInnerBaseline(this._titleText.getBaseline())}doLayout(){if(super.doLayout(),this._config.separator)return this;let e=MenuItem.HEIGHT,t=this.getWidth(),n=!!this._config.shortcut&&this._shortcutText!==null,r=this.hasSubmenu(),i=this._iconStart??(this.hasIcon()?MenuItem.ICON_ZONE:MenuItem.TEXT_INSET),a=this._titleColumn??this.titleTextWidth();if(this._iconGlyph){let t=this._iconGlyph.getPreferredSize()??{width:16,height:16},n=Math.max(0,Math.floor((e-t.height)/2));this._iconGlyph.setX(4),this._iconGlyph.setY(n),this._iconGlyph.setWidth(t.width),this._iconGlyph.setHeight(t.height)}else this._iconText&&(this._iconText.setX(4),this._iconText.setY(0),this._iconText.setWidth(20),this._iconText.setHeight(e));return this._titleText&&(this._titleText.setX(i),this._titleText.setY(0),this._titleText.setWidth(a),this._titleText.setHeight(e)),r&&this._chevronText&&(this._chevronText.setX(t-MenuItem.RIGHT_PAD-MenuItem.CHEVRON_ZONE),this._chevronText.setY(0),this._chevronText.setWidth(MenuItem.CHEVRON_ZONE),this._chevronText.setHeight(e)),n&&this._shortcutText&&(this._shortcutText.setX(i+a+MenuItem.TEXT_GAP),this._shortcutText.setY(0),this._shortcutText.setWidth(this.shortcutTextWidth()),this._shortcutText.setHeight(e)),this}}),h={},g=r(class MenuSeparator extends i{static HEIGHT=9;constructor(e=`menu-bar`,t){super(t,h),this.setHeight(MenuSeparator.HEIGHT),this.setPreferredSize(0,MenuSeparator.HEIGHT),this.setBackgroundColor(`transparent`),this.setElementCSSRule(`borderTop`,`1px solid var(--ts-ui-${e}-separator-color, rgb(220, 220, 220))`),this.setElementCSSRule(`margin`,`4px 0`),this.getAria().setRole(`separator`)}}),_=120,v=360,y=120,b=4;function pointRect(e,t){return{x:e,y:t,left:e,top:t,right:e,bottom:t,width:0,height:0}}function resolvePlacement(e,t,n){let r=c(e.top,e.bottom,t.height,n.height,b);return{x:l(e.left,e.right,t.width,n.width,b),y:r.start,available:r.available}}var x=r(class Menu extends i{_persistent;_itemsProvider;_onClose;_menuItems=[];_focusedIndex=-1;_openSubmenuPanel=null;_openSubmenuItem=null;_excludedEl=null;_menuWidth=null;_rebuildOnClose=null;_currentOpener=null;_scrollToBottomOnShow=!1;constructor(e,t){super(),this._persistent=e!==void 0,this._itemsProvider=typeof e==`function`?e:null,this._onClose=t??null;let n=new p;n.setComponentSpacing(0),n.setStretching(!0),this.setLayoutManager(n),this._persistent?(this.applyPersistentChrome(),this._itemsProvider===null&&this.buildPersistentItems(e)):this.applyRebuildChrome()}layOutColumns(){let e=this._menuItems.filter(e=>e instanceof m&&!e.isSeparator()),t=e.some(e=>e.hasIcon())?m.ICON_ZONE:m.TEXT_INSET,n=e.reduce((e,t)=>Math.max(e,t.titleTextWidth()),0),r=e.reduce((e,t)=>Math.max(e,t.shortcutTextWidth()),0),i=e.some(e=>e.hasSubmenu()),a=Math.max(r,i?m.CHEVRON_ZONE:0),o=a>0?m.TEXT_GAP+a:0,s=t+n+o+m.RIGHT_PAD,c=Math.min(v,Math.max(_,s)),l=Math.min(n,c-t-o-m.RIGHT_PAD);for(let n of e)n.setColumns(t,l);return c}show(n,r,i,a,o){this.assertRebuildMode(`show`);let s=e.source.getViewportSize();return this.showAnchored(pointRect(t.clamp(n,0,s.width),t.clamp(r,0,s.height)),i,a??null,o??null)}showAnchored(t,r,i,a){this._rebuildOnClose=i,this._excludedEl=a;for(let e of this._menuItems)e instanceof m&&e.dispose();this._menuItems=[],this.removeAllComponents(),this.pauseLayout();for(let e of r){let t=e.separator===!0?new g(`context-menu`):new m(e,()=>{e.action?.(),this.hide()},e=>{this.handleItemOpenSubmenu(e)},`context-menu`);this.addComponent(t),this._menuItems.push(t)}this.resumeLayout();let o=this.layOutColumns(),c=this._menuWidth??o;this.setWidth(c),this.setMaxSize(Number.MAX_VALUE,Number.MAX_VALUE);let l=this.getPreferredSize()?.height??0,u=this.getElement(!0),d=e.source.getViewportSize(),f=l>resolvePlacement(t,{width:c,height:l},d).available?e.source.getScrollBarWidth():0;this.setInsets(new n(4,f,4,0)),this.setWidth(c+f);let p=resolvePlacement(t,{width:this.getWidth(),height:l},d);return this.setX(p.x),this.setY(p.y),this.applyViewportHeightClamp(p.available,l),this.scheduleLayout(),s.mount(u),this.setVisible(!0),this.fadeIn(),s.register(this),this.setZIndex(s.getZIndex(this)),this._scrollToBottomOnShow&&(this.flushLayout(),this.setScrollTop(this.getMaxScrollTop())),this}toggleFor(e,t,n,r){return this.assertRebuildMode(`toggleFor`),this._currentOpener===e?(this.hide(),this):n.length===0?(r?.(),this):(this.showAnchored(t,n,r??null,e),this._currentOpener=e,this)}hide(){this.assertRebuildMode(`hide`),this.closeOpenSubmenu(),s.unregister(this),this.fadeOutAndDetach(),this._currentOpener=null;let e=this._rebuildOnClose;return this._rebuildOnClose=null,e?.(),this}getMenuWidth(){return this.getWidth()}setMenuWidth(e){return this.assertRebuildMode(`setMenuWidth`),this._menuWidth=e,this}setScrollToBottomOnShow(e){return this.assertRebuildMode(`setScrollToBottomOnShow`),this._scrollToBottomOnShow=e,this}open(t,n){this.assertPersistentMode(`open`),this._itemsProvider&&this.rebuildPersistentItems(this._itemsProvider()),this.setMaxSize(Number.MAX_VALUE,Number.MAX_VALUE);let r=this.getPreferredSize()?.height??this._menuItems.length*m.HEIGHT+8,i=this.getWidth(),a=this.getElement(!0);s.mount(a);let o=e.source.getViewportSize();if(n){let a=n.getElement(),s=a?e.source.getElementRect(a):{left:0,right:0,top:0,bottom:0},c=e.source.getElementRect(t),l=s.right;l+i>o.width&&(l=s.left-i);let u=this.placeVertically(c.top,c.top,r,o.height);this.setAutoCommitStyle(!1),this.setX(Math.max(0,l)),this.setY(Math.max(0,u)),this.setAutoCommitStyle(!0)}else{let n=e.source.getElementRect(t),a=n.left;a+i>o.width&&(a=o.width-i);let s=this.placeVertically(n.bottom,n.top,r,o.height);this.setAutoCommitStyle(!1),this.setX(Math.max(0,a)),this.setY(Math.max(0,s)),this.setAutoCommitStyle(!0)}let c=e.source.getId(t);return c&&this.getAria().setLabelledBy(c),this.setVisible(!0),this.doLayout(),this.fadeIn(),s.register(this),this.setZIndex(s.getZIndex(this)),this}close(){return this.assertPersistentMode(`close`),this.closeOpenSubmenu(),this.setFocusedIndex(-1),this.clearItemHighlights(),s.unregister(this),this.fadeOutAndDetach(),this}clearItemHighlights(){for(let e of this._menuItems)e instanceof m&&e.setFocused(!1)}fadeIn(){d(this,{durationMs:y})}fadeOutAndDetach(){u(this,{durationMs:y})}focusItem(e){return this.assertPersistentMode(`focusItem`),this.setFocusedIndex(e),this}focusNext(){this.assertPersistentMode(`focusNext`);let e=this._focusedIndex+1;for(;e<this._menuItems.length&&this.isItemSeparator(e);)e++;if(e>=this._menuItems.length)for(e=0;e<this._menuItems.length&&this.isItemSeparator(e);)e++;return this.setFocusedIndex(e),this}focusPrev(){this.assertPersistentMode(`focusPrev`);let e=this._focusedIndex-1;for(;e>=0&&this.isItemSeparator(e);)e--;if(e<0)for(e=this._menuItems.length-1;e>=0&&this.isItemSeparator(e);)e--;return this.setFocusedIndex(e),this}activateFocused(){if(this.assertPersistentMode(`activateFocused`),this._focusedIndex<0||this._focusedIndex>=this._menuItems.length)return;let e=this._menuItems[this._focusedIndex];e instanceof m&&!e.isSeparator()&&e.isEnabled()&&e.activate()}getFocusedIndex(){return this.assertPersistentMode(`getFocusedIndex`),this._focusedIndex}setExcludedElement(e){return this.assertPersistentMode(`setExcludedElement`),this._excludedEl=e,this}getExcludedElement(){return this._excludedEl}dispose(){this.assertPersistentMode(`dispose`);for(let e of this._menuItems)e instanceof m&&e.dispose()}applyPersistentChrome(){this.setBackgroundColor(`var(--ts-ui-menu-bar-panel-bg, rgb(255, 255, 255))`),this.setInsets(new n(4,0,4,0)),this.setBorder({border:`1px solid var(--ts-ui-menu-bar-panel-border, rgb(200, 200, 200))`}),this.setBorderRadius(`var(--ts-ui-border-radius, 4px)`),this.setShadow(`var(--ts-ui-menu-bar-panel-shadow, 2px 4px 8px rgba(0, 0, 0, 0.15))`),this.getAria().setRole(`menu`),this.setContain(`layout`),this.enableVerticalScroll()}applyRebuildChrome(){this.setVisible(!1),this.setBackgroundColor(`var(--ts-ui-context-menu-bg, rgb(255, 255, 255))`),this.setInsets(new n(4,0,4,0)),this.setBorder({border:`1px solid var(--ts-ui-context-menu-border, rgb(200, 200, 200))`}),this.setBorderRadius(`var(--ts-ui-border-radius, 4px)`),this.setShadow(`var(--ts-ui-context-menu-shadow, 2px 4px 8px rgba(0, 0, 0, 0.15))`),this.setContain(`layout`),this.enableVerticalScroll()}enableVerticalScroll(){this.setOverflowX(`hidden`),this.setOverflowY(`auto`),this.getLayoutManager().setOverflowing(!1,!0)}applyViewportHeightClamp(e,t){this.setMaxSize(Number.MAX_VALUE,Math.max(0,e)),this.setHeight(t)}placeVertically(e,t,n,r){let{start:i,available:a}=c(t,e,n,r,b);return this.applyViewportHeightClamp(a,n),i}rebuildPersistentItems(e){this.closeOpenSubmenu();for(let e of this._menuItems)e instanceof m&&e.dispose();this._menuItems=[],this._focusedIndex=-1,this.removeAllComponents(),this.buildPersistentItems(e)}buildPersistentItems(e){this.pauseLayout();for(let t of e){let e=new m(t,()=>{t.action?.(),this._onClose()},e=>{this.handleItemOpenSubmenu(e)});this.addComponent(e),this._menuItems.push(e)}this.resumeLayout(),this.setWidth(this.layOutColumns())}setFocusedIndex(e){if(this._focusedIndex>=0&&this._focusedIndex<this._menuItems.length){let e=this._menuItems[this._focusedIndex];e instanceof m&&e.setFocused(!1)}if(this._focusedIndex=e,e>=0&&e<this._menuItems.length){let t=this._menuItems[e];t instanceof m&&t.setFocused(!0)}}isItemSeparator(e){let t=this._menuItems[e];return t instanceof g||t instanceof m&&t.isSeparator()}closeOpenSubmenu(){this._openSubmenuPanel&&(this._openSubmenuPanel.close(),this._openSubmenuItem?.getAria().setExpanded(!1),this._openSubmenuPanel=null,this._openSubmenuItem=null)}dismissAll(){this._persistent?this._onClose():this.hide()}getLayerElement(){return this.getElement()??null}getDismissMode(){return`click-outside`}requestClose(){this.dismissAll()}getAnchorElement(){return this._excludedEl}getBand(){return s.Band.Dropdown}onZIndexChanged(e){this.setZIndex(e)}handleItemOpenSubmenu(e){if(!e.hasSubmenu()||!e.isEnabled()){this.closeOpenSubmenu();return}if(this._openSubmenuItem===e)return;this.closeOpenSubmenu();let t=new Menu(e.getSubmenuConfig().items,()=>{this.dismissAll()});this._openSubmenuPanel=t,this._openSubmenuItem=e,e.getAria().setExpanded(!0),t.setExcludedElement(this.getElement(!0)),t.open(e.getElement(!0),this)}assertRebuildMode(e){if(this._persistent)throw Error(`Menu.${e}() is only valid in rebuild mode (constructed without arguments).`)}assertPersistentMode(e){if(!this._persistent)throw Error(`Menu.${e}() is only valid in persistent mode (constructed with items and onClose).`)}});export{g as n,m as r,x as t};
|
|
2
|
-
//# sourceMappingURL=Menu-BdnwRY0M.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Menu-BdnwRY0M.js","names":[],"sources":["../../src/typescript/lib/component/container/MenuItem.ts","../../src/typescript/lib/component/container/MenuSeparator.ts","../../src/typescript/lib/overlay/Menu.ts"],"sourcesContent":["// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport { Event } from \"~/core/Event.js\";\nimport { Text } from \"~/component/input/Text.js\";\nimport { Glyph } from \"~/component/display/Glyph.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time component-level options for {@link MenuItem}. Use this\n * for cosmetic/layout overrides on the item row; the menu data (text, action,\n * shortcut, submenu) still comes through {@link MenuItemConfig}.\n *\n * @category Components\n */\nexport interface MenuItemOptions extends ComponentOptions {\n text?: string;\n enabled?: boolean;\n focused?: boolean;\n}\n\n/**\n * Selects which CSS-variable family a `MenuItem` reads its colours from.\n *\n * `'menu-bar'` resolves to `--ts-ui-menu-bar-item-*` tokens; `'context-menu'`\n * resolves to `--ts-ui-context-menu-item-*` tokens. The [`Menu`](/api/overlay/classes/Menu) class chooses\n * the prefix based on its mode.\n */\nexport type MenuItemCSSVarPrefix = \"menu-bar\" | \"context-menu\";\n\n/**\n * Describes a leaf action item, submenu trigger, or separator row inside a [`Menu`](/api/overlay/classes/Menu).\n *\n * @remarks When `separator` is true all other fields are ignored; the item renders as\n * a thin horizontal rule. When `submenu` is set the item opens a child [`Menu`](/api/overlay/classes/Menu)\n * instead of calling `action`.\n *\n * @category Components\n */\nexport interface MenuItemConfig {\n /** Display label shown in the menu row. */\n text?: string;\n /** Called when the item is activated (click or Enter). Ignored when `submenu` is set. */\n action?: () => void;\n /** Defaults to `true`. Disabled items are dimmed and non-interactive. */\n enabled?: boolean;\n /** Keyboard shortcut hint displayed on the right (e.g. `\"Ctrl+S\"`). */\n shortcut?: string;\n /**\n * Icon displayed on the left (e.g. a Unicode character).\n *\n * @remarks Prefer `glyph` for crisp SVG output from the framework's glyph\n * registry; `icon` remains supported for callers that want to embed a raw\n * character.\n */\n icon?: string;\n /**\n * Registry glyph name displayed on the left of the item row. Takes\n * precedence over `icon` when both are provided.\n */\n glyph?: string;\n /**\n * Optional colour for the leading `glyph`, overriding the item's default\n * foreground (e.g. a severity tint). Any CSS colour string. Ignored when the\n * row has no `glyph`.\n */\n glyphColor?: string;\n /** When present this item opens a submenu rather than calling `action`. */\n submenu?: MenuConfig;\n /** When `true` the item renders as a separator; all other fields are ignored. */\n separator?: true;\n}\n\n/**\n * Describes one top-level entry in a [`MenuBar`](/api/component/menubar/classes/MenuBar).\n *\n * @category Components\n */\nexport interface MenuConfig {\n /** Label shown in the bar button (e.g. `\"File\"`). */\n label: string;\n /**\n * The items in the dropdown panel: either a fixed array, or a provider called\n * to produce them when the panel is built. A submenu panel is (re)built each\n * time it opens, so a provider on a submenu's `items` is re-invoked on every\n * open — letting the submenu reflect current state (e.g. an export chooser\n * whose labels track the active tab) without the parent menu rebuilding.\n */\n items: MenuItemConfig[] | (() => MenuItemConfig[]);\n /**\n * Optional registry glyph name displayed to the left of the bar button's\n * label (e.g. `\"file\"`, `\"eye\"`, `\"info-circle\"`). Omit for a text-only\n * menu button.\n */\n glyph?: string;\n}\n\n/**\n * A single row inside a [`Menu`](/api/overlay/classes/Menu) panel.\n *\n * Renders a four-zone layout: icon | text | shortcut | chevron. When\n * `config.separator` is true the item renders instead as a thin horizontal\n * rule and ignores all other config fields.\n *\n * Mouse hover triggers the submenu open callback after a 150 ms delay.\n * Keyboard focus is applied programmatically via `setFocused`.\n *\n * @category Components\n */\nclass MenuItem extends Component {\n\n /** Fixed pixel height for every non-separator menu item. */\n static readonly HEIGHT: number = 24;\n\n private static readonly SEPARATOR_HEIGHT: number = 9;\n /** Title left inset when the menu reserves no icon column. */\n static readonly TEXT_INSET: number = 8;\n /** Title left offset when the menu reserves an icon column. */\n static readonly ICON_ZONE: number = 24;\n /** Width reserved for a submenu chevron in the right zone. */\n static readonly CHEVRON_ZONE: number = 16;\n /** Padding after the right zone, at the panel's inner edge. */\n static readonly RIGHT_PAD: number = 8;\n /**\n * Gap between the title column and the shortcut/chevron right zone, so a\n * content-sized menu never butts the label against them.\n */\n static readonly TEXT_GAP: number = 10;\n\n private readonly _config: MenuItemConfig;\n private readonly _onActivate: () => void;\n private readonly _onOpenSubmenu: (item: MenuItem) => void;\n private readonly _cssVarPrefix: MenuItemCSSVarPrefix;\n\n private _iconText: Text | null = null;\n private _iconGlyph: Glyph | null = null;\n private _titleText: Text | null = null;\n private _shortcutText: Text | null = null;\n private _chevronText: Text | null = null;\n\n // Column geometry applied by the parent Menu so every item lines up: the\n // title's left offset and the shared title-column width the right zone begins\n // after. Null until the menu sets them (a standalone item falls back to its\n // own natural metrics).\n private _iconStart: number | null = null;\n private _titleColumn: number | null = null;\n\n private _submenuTimer: ReturnType<typeof setTimeout> | null = null;\n\n private readonly _onMouseOver: () => void;\n private readonly _onMouseOut: () => void;\n private readonly _onClick: () => void;\n\n /**\n * Constructs a MenuItem from a config descriptor.\n *\n * @param config - The item descriptor including text, action, shortcut, icon, and submenu.\n * @param onActivate - Called when this item is activated (click or Enter on a leaf item).\n * @param onOpenSubmenu - Called when this item's submenu should open (or close others when no submenu).\n * @param cssVarPrefix - Selects which CSS-variable family supplies disabled, hover, and shortcut colours. Defaults to `'menu-bar'`.\n */\n constructor(\n config: MenuItemConfig,\n onActivate: () => void,\n onOpenSubmenu: (item: MenuItem) => void,\n cssVarPrefix: MenuItemCSSVarPrefix = \"menu-bar\",\n options?: MenuItemOptions\n ) {\n // Child components are built first; options are applied via applyOptions at the constructor tail.\n // eslint-disable-next-line local/forward-super-options\n super();\n\n this._config = config;\n this._onActivate = onActivate;\n this._onOpenSubmenu = onOpenSubmenu;\n this._cssVarPrefix = cssVarPrefix;\n\n if (config.separator) {\n this.setHeight(MenuItem.SEPARATOR_HEIGHT);\n this.setPreferredSize(0, MenuItem.SEPARATOR_HEIGHT);\n this.setBackgroundColor(\"transparent\");\n this.setElementCSSRule(\n \"borderTop\",\n `1px solid var(--ts-ui-${cssVarPrefix}-separator-color, rgb(220, 220, 220))`\n );\n this.setElementCSSRule(\"margin\", \"4px 0\");\n this.getAria().setRole(\"separator\");\n\n this._onMouseOver = () => {};\n this._onMouseOut = () => {};\n this._onClick = () => {};\n\n if (options) {\n this.applyOptions(options);\n }\n\n return;\n }\n\n const enabled = config.enabled !== false;\n\n this.setHeight(MenuItem.HEIGHT);\n this.setPreferredSize(0, MenuItem.HEIGHT);\n this.setBackgroundColor(\"transparent\");\n this.setElementCSSRule(\"fontSize\", \"var(--ts-ui-button-font-size, var(--ts-ui-font-size))\");\n this.setCursor(enabled ? \"pointer\" : \"default\");\n this.getAria().setRole(\"menuitem\");\n\n if (!enabled) {\n this.setForegroundColor(\n `var(--ts-ui-${cssVarPrefix}-item-disabled-color, rgb(170, 170, 170))`\n );\n this.getAria().setDisabled(true);\n }\n\n if (config.glyph) {\n this._iconGlyph = new Glyph(config.glyph);\n this._iconGlyph.setPointerEvents(\"none\");\n this._iconGlyph.setPreferredSize(16, 16);\n\n if (config.glyphColor) {\n this._iconGlyph.setForegroundColor(config.glyphColor);\n }\n\n this.addComponent(this._iconGlyph);\n } else {\n this._iconText = new Text(config.icon ?? \"\");\n this._iconText.setPointerEvents(\"none\");\n this._iconText.centerInHeight(MenuItem.HEIGHT);\n this._iconText.setVisible(!!config.icon);\n this.addComponent(this._iconText);\n }\n\n this._titleText = new Text(config.text ?? \"\");\n this._titleText.setPointerEvents(\"none\");\n this._titleText.centerInHeight(MenuItem.HEIGHT);\n this._titleText.setWhiteSpace(\"nowrap\");\n this._titleText.setOverflow(\"hidden\");\n this._titleText.setTextOverflow(\"ellipsis\");\n this.addComponent(this._titleText);\n\n if (config.shortcut) {\n this._shortcutText = new Text(config.shortcut);\n this._shortcutText.setPointerEvents(\"none\");\n this._shortcutText.centerInHeight(MenuItem.HEIGHT);\n this._shortcutText.setTextAlign(\"left\");\n this._shortcutText.setForegroundColor(\n `var(--ts-ui-${cssVarPrefix}-item-shortcut-color, rgb(140, 140, 140))`\n );\n this.addComponent(this._shortcutText);\n }\n\n if (this.hasSubmenu()) {\n this._chevronText = new Text(\"▶\");\n this._chevronText.setPointerEvents(\"none\");\n this._chevronText.centerInHeight(MenuItem.HEIGHT);\n this._chevronText.setTextAlign(\"center\");\n this._chevronText.setForegroundColor(\n `var(--ts-ui-${cssVarPrefix}-item-shortcut-color, rgb(140, 140, 140))`\n );\n this.addComponent(this._chevronText);\n this.getAria().setHasPopup(\"menu\");\n this.getAria().setExpanded(false);\n }\n\n this._onMouseOver = () => {\n this.setFocused(true);\n\n // A disabled item never opens its own submenu; it still signals the\n // parent (the else branch) so hovering it closes any sibling submenu.\n if (enabled && this.hasSubmenu()) {\n this._submenuTimer = setTimeout(() => {\n this._submenuTimer = null;\n this._onOpenSubmenu(this);\n }, 150);\n } else {\n this._onOpenSubmenu(this);\n }\n };\n\n this._onMouseOut = () => {\n this.setFocused(false);\n\n if (this._submenuTimer !== null) {\n clearTimeout(this._submenuTimer);\n this._submenuTimer = null;\n }\n };\n\n this._onClick = () => {\n if (enabled && !this.hasSubmenu()) {\n this._onActivate();\n }\n };\n\n Event.addListener(this, \"mouseover\", this._onMouseOver);\n Event.addListener(this, \"mouseout\", this._onMouseOut);\n Event.addListener(this, \"click\", this._onClick);\n\n if (options) {\n this.applyOptions(options);\n }\n }\n\n /**\n * Applies a {@link MenuItemOptions} bag, dispatching menu item label,\n * enabled state, and focus highlight after inherited Component fields.\n *\n * @param options - The options bag carrying the values to apply.\n */\n protected applyOptions(options: MenuItemOptions): this {\n super.applyOptions(options);\n\n if (options.text !== undefined && this._titleText) {\n this._titleText.setText(options.text);\n }\n\n if (options.enabled !== undefined) {\n this.getAria().setDisabled(!options.enabled);\n this.setCursor(options.enabled ? \"pointer\" : \"default\");\n }\n\n if (options.focused !== undefined) {\n this.setFocused(options.focused);\n }\n\n return this;\n }\n\n /**\n * Returns `true` if this item has a child submenu.\n *\n * @returns Whether a `submenu` config was provided.\n */\n hasSubmenu(): boolean {\n return !!this._config.submenu;\n }\n\n /** True when the item reserves an icon column (has a glyph or icon slot). */\n hasIcon(): boolean {\n return !!this._config.icon || !!this._config.glyph;\n }\n\n /** The item's measured title width, feeding the menu's shared title column. */\n titleTextWidth(): number {\n return Math.ceil(this._titleText?.getPreferredSize()?.width ?? 0);\n }\n\n /** The item's measured shortcut width, or 0 when it has no shortcut. */\n shortcutTextWidth(): number {\n if (!this._config.shortcut || this._shortcutText === null) {\n return 0;\n }\n\n return Math.ceil(this._shortcutText.getPreferredSize()?.width ?? 0);\n }\n\n /**\n * Applies the menu-computed column geometry so every item lines up: the title\n * left offset (icon column, or the bare inset) and the shared title-column\n * width the shortcut/chevron right zone begins after.\n *\n * @param iconStart - The title's left offset in pixels.\n * @param titleColumn - The shared title-column width across the menu's items.\n */\n setColumns(iconStart: number, titleColumn: number): void {\n this._iconStart = iconStart;\n this._titleColumn = titleColumn;\n\n this.scheduleLayout();\n }\n\n /**\n * Returns the submenu config, or `null` if this item has no submenu.\n *\n * @returns The [`MenuConfig`](/api/component/container/interfaces/MenuConfig) for the child panel, or null.\n */\n getSubmenuConfig(): MenuConfig | null {\n return this._config.submenu ?? null;\n }\n\n /**\n * Applies or removes the keyboard-focus highlight background.\n *\n * @param focused - `true` to highlight, `false` to clear.\n */\n setFocused(focused: boolean): this {\n this.setBackgroundColor(\n focused\n ? `var(--ts-ui-${this._cssVarPrefix}-item-hover-bg, rgba(30, 100, 200, 0.12))`\n : \"transparent\"\n );\n\n return this;\n }\n\n /**\n * Returns `true` when this item was constructed with `separator: true`.\n *\n * @returns Whether this item is a non-interactive separator row.\n */\n isSeparator(): boolean {\n return !!this._config.separator;\n }\n\n /**\n * Returns `true` when the item is interactive (not disabled).\n *\n * @returns Whether `enabled` was not explicitly set to `false`.\n */\n isEnabled(): boolean {\n return this._config.enabled !== false;\n }\n\n /**\n * Activates this item as if the user clicked or pressed Enter.\n *\n * For submenu items, calls `onOpenSubmenu` immediately (skipping the hover delay).\n * For leaf items, calls `onActivate`. Does nothing for disabled or separator items.\n */\n activate(): void {\n if (this.isSeparator() || !this.isEnabled()) {\n return;\n }\n\n if (this.hasSubmenu()) {\n this._onOpenSubmenu(this);\n } else {\n this._onActivate();\n }\n }\n\n /**\n * Removes all Event listeners registered by this item and cancels any pending submenu timer.\n */\n dispose(): void {\n if (this._submenuTimer !== null) {\n clearTimeout(this._submenuTimer);\n this._submenuTimer = null;\n }\n\n Event.removeListener(this, \"mouseover\", this._onMouseOver);\n Event.removeListener(this, \"mouseout\", this._onMouseOut);\n Event.removeListener(this, \"click\", this._onClick);\n }\n\n /**\n * Returns the offset from the top of the menu item to the title text's baseline.\n *\n * @returns The baseline offset in pixels, or `null` when the title has no baseline\n * (e.g. for a separator row).\n */\n getBaseline(): number | null {\n if (this._titleText === null) {\n return null;\n }\n\n return this.wrapInnerBaseline(this._titleText.getBaseline());\n }\n\n /**\n * Positions the four label zones within the item's bounds.\n *\n * @returns This component, for method chaining.\n */\n doLayout(): this {\n super.doLayout();\n\n if (this._config.separator) {\n return this;\n }\n\n const H = MenuItem.HEIGHT;\n const totalWidth = this.getWidth();\n const hasShortcut = !!this._config.shortcut && this._shortcutText !== null;\n const hasSub = this.hasSubmenu();\n\n // The menu aligns every item into shared columns; a standalone item falls\n // back to its own icon offset and natural title width.\n const iconStart = this._iconStart ?? (this.hasIcon() ? MenuItem.ICON_ZONE : MenuItem.TEXT_INSET);\n const titleColumn = this._titleColumn ?? this.titleTextWidth();\n\n if (this._iconGlyph) {\n const size = this._iconGlyph.getPreferredSize() ?? { width: 16, height: 16 };\n const iconY = Math.max(0, Math.floor((H - size.height) / 2));\n\n this._iconGlyph.setX(4);\n this._iconGlyph.setY(iconY);\n this._iconGlyph.setWidth(size.width);\n this._iconGlyph.setHeight(size.height);\n } else if (this._iconText) {\n this._iconText.setX(4);\n this._iconText.setY(0);\n this._iconText.setWidth(20);\n this._iconText.setHeight(H);\n }\n\n // Title fills the shared title column, left-aligned from the icon offset.\n if (this._titleText) {\n this._titleText.setX(iconStart);\n this._titleText.setY(0);\n this._titleText.setWidth(titleColumn);\n this._titleText.setHeight(H);\n }\n\n // Chevron: right-justified at the panel's inner edge.\n if (hasSub && this._chevronText) {\n this._chevronText.setX(totalWidth - MenuItem.RIGHT_PAD - MenuItem.CHEVRON_ZONE);\n this._chevronText.setY(0);\n this._chevronText.setWidth(MenuItem.CHEVRON_ZONE);\n this._chevronText.setHeight(H);\n }\n\n // Shortcut: left-justified in the right zone, one gap past the title column.\n if (hasShortcut && this._shortcutText) {\n this._shortcutText.setX(iconStart + titleColumn + MenuItem.TEXT_GAP);\n this._shortcutText.setY(0);\n this._shortcutText.setWidth(this.shortcutTextWidth());\n this._shortcutText.setHeight(H);\n }\n\n return this;\n }\n}\n\nconst MenuItemCallable = callable(MenuItem);\ntype MenuItemCallable = MenuItem;\nexport {\n MenuItem as _MenuItem,\n MenuItemCallable as MenuItem\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component, ComponentOptions } from \"~/core/Component.js\";\nimport type { MenuItemCSSVarPrefix } from \"~/component/container/MenuItem.js\";\nimport { callable } from \"~/core/Callable.js\";\n\n/**\n * Construction-time options for {@link MenuSeparator}.\n *\n * @category Components\n */\nexport interface MenuSeparatorOptions extends ComponentOptions {\n}\n\n/**\n * Empty subclass-default const so the super call follows the framework's\n * `(options, defaults)` shape uniformly. MenuSeparator owns no class-level\n * option overrides today; the const exists so a future field has a place to\n * land without reshuffling the super call.\n */\nconst _defaultMenuSeparatorOptions: Partial<MenuSeparatorOptions> = {};\n\n/**\n * A horizontal separator rule used inside a [`Menu`](/api/overlay/classes/Menu) panel to visually group menu items.\n *\n * Renders as a thin border line. Width is set externally by `Menu.doLayout()`.\n *\n * @category Components\n */\nclass MenuSeparator extends Component<MenuSeparatorOptions> {\n\n /** Fixed pixel height of every separator. */\n static readonly HEIGHT: number = 9;\n\n /**\n * Constructs a MenuSeparator with a thin horizontal rule appearance.\n *\n * @param cssVarPrefix - Selects which CSS-variable family supplies the border colour. Defaults to `'menu-bar'`.\n */\n constructor(cssVarPrefix: MenuItemCSSVarPrefix = \"menu-bar\", options?: MenuSeparatorOptions) {\n super(options, _defaultMenuSeparatorOptions);\n\n this.setHeight(MenuSeparator.HEIGHT);\n this.setPreferredSize(0, MenuSeparator.HEIGHT);\n this.setBackgroundColor(\"transparent\");\n this.setElementCSSRule(\n \"borderTop\",\n `1px solid var(--ts-ui-${cssVarPrefix}-separator-color, rgb(220, 220, 220))`\n );\n this.setElementCSSRule(\"margin\", \"4px 0\");\n this.getAria().setRole(\"separator\");\n }\n}\n\nconst MenuSeparatorCallable = callable(MenuSeparator);\ntype MenuSeparatorCallable = MenuSeparator;\nexport {\n MenuSeparator as _MenuSeparator,\n MenuSeparatorCallable as MenuSeparator\n};\n","// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0\n\nimport { Component } from \"~/core/Component.js\";\nimport { DOM } from \"~/core/DOM.js\";\nimport type { Handle, Rect } from \"~/core/DOM.js\";\nimport { LayerManager, DismissableLayer, LayerDismissMode } from \"~/core/LayerManager.js\";\nimport { positionAligned, positionFlexibleAnchored } from \"~/core/OverlayPosition.js\";\nimport { fadeShow, fadeHideAndDetach } from \"~/core/AnimatedDropdown.js\";\nimport { Insets } from \"~/primitive/Insets.js\";\nimport type { Size } from \"~/primitive/Size.js\";\nimport { VBox } from \"~/layout/VBox.js\";\nimport { MenuItem, MenuItemConfig } from \"~/component/container/MenuItem.js\";\nimport { MenuSeparator } from \"~/component/container/MenuSeparator.js\";\nimport { callable } from \"~/core/Callable.js\";\nimport { Util } from \"~/core/Util.js\";\n\n/**\n * Pixel bounds a content-sized menu panel clamps to. Menus size to their widest\n * item (via the layout manager's preferred width); the floor keeps a short menu\n * from looking cramped, and the ceiling stops a very long label running off-screen\n * (the item's title ellipsizes past it).\n */\nconst MIN_MENU_WIDTH = 120;\nconst MAX_MENU_WIDTH = 360;\n\n/** Duration (ms) of the fade-in / fade-out transitions on every menu panel. */\nconst MENU_ANIM_DURATION_MS = 120;\n\n/** Pixels kept between a clamped menu and the viewport edge so the panel\n * border and shadow are never flush against the screen. Mirrors the small\n * inset used by other floating panels; purely cosmetic breathing room. */\nconst VIEWPORT_MARGIN = 4;\n\n/** A resolved rebuild-mode placement: the panel's top-left plus the height room at it. */\ninterface MenuPlacement { x: number; y: number; available: number; }\n\n/**\n * A zero-size rect at a cursor point. A cursor is a degenerate anchor: with\n * `left === right` and `top === bottom`, the adjacency and alignment flips\n * collapse to the same operation — grow down-right from the point, or end at it.\n * That is native context-menu behaviour, so a pointer needs no separate path.\n */\nfunction pointRect(x: number, y: number): Rect {\n return { x, y, left: x, top: y, right: x, bottom: y, width: 0, height: 0 };\n}\n\n/**\n * Resolves a rebuild-mode panel's placement against `anchorRect` at `size`.\n * Vertically the panel is size-flexible (it caps its height and scrolls), so it\n * grows below the anchor and flips to end at the anchor's top when the room below\n * is short. Horizontally it is fixed-size (a natural width, no horizontal scroll),\n * so its left edge aligns with the anchor's left and flips to align its right edge\n * with the anchor's right when the left alignment overflows.\n *\n * @param anchorRect - The trigger rect, or a zero-size rect at the cursor.\n * @param size - The panel's current width/height to place.\n * @param vp - The viewport size to flip/clamp within.\n * @returns The resolved top-left coordinate and the vertical room available there.\n */\nfunction resolvePlacement(anchorRect: Rect, size: Size, vp: Size): MenuPlacement {\n const v = positionFlexibleAnchored(anchorRect.top, anchorRect.bottom, size.height, vp.height, VIEWPORT_MARGIN);\n const x = positionAligned(anchorRect.left, anchorRect.right, size.width, vp.width, VIEWPORT_MARGIN);\n\n // `available` is the room on the side the panel actually landed on — never\n // re-derive it from `v.start`, which measures the wrong side for a flipped\n // panel and lets an over-tall menu grow back across the cursor.\n return { x, y: v.start, available: v.available };\n}\n\n/**\n * A floating menu panel that operates in one of two modes:\n *\n * - **Rebuild mode** (`new Menu()`): a right-click context menu. Items are passed per\n * `show(x, y, items)` call and disposed on the next show or hide.\n * - **Persistent mode** (`new Menu(items, onClose)`): a [`MenuBar`](/api/component/menubar/classes/MenuBar) dropdown. Items are\n * built once in the constructor and reused across `open()` / `close()` cycles.\n *\n * The two API surfaces are disjoint by mode:\n * `show()` / `hide()` / `setMenuWidth()` are valid only in rebuild mode;\n * `open()` / `close()` / focus and submenu methods are valid only in persistent mode.\n *\n * @example\n * ```typescript\n * // Rebuild mode — right-click context menu\n * const menu = new Menu();\n * Event.addListener(myComponent, 'contextmenu', (e: MouseEvent) => {\n * e.preventDefault();\n * menu.show(e.clientX, e.clientY, [\n * { text: 'Cut', action: () => cut() },\n * { separator: true },\n * { text: 'Paste', action: () => paste() },\n * ]);\n * });\n *\n * // Persistent mode — MenuBar dropdown\n * const panel = new Menu(\n * [{ text: 'Save', shortcut: 'Ctrl+S', action: () => save() }],\n * () => bar.closeMenu()\n * );\n * panel.open(buttonElement);\n * ```\n *\n * @category Components\n */\nclass Menu extends Component implements DismissableLayer {\n\n private readonly _persistent: boolean;\n // A persistent menu's items provider, kept so each open() re-resolves it —\n // null when the items were passed as a fixed array (built once, never rebuilt).\n private readonly _itemsProvider: (() => MenuItemConfig[]) | null;\n private readonly _onClose: (() => void) | null;\n private _menuItems: Array<MenuItem | MenuSeparator> = [];\n private _focusedIndex: number = -1;\n private _openSubmenuPanel: Menu | null = null;\n private _openSubmenuItem: MenuItem | null = null;\n private _excludedEl: Handle | null = null;\n // An explicit rebuild-mode width set via setMenuWidth; null means size to\n // content (the widest item, clamped to [MIN_MENU_WIDTH, MAX_MENU_WIDTH]).\n private _menuWidth: number | null = null;\n private _rebuildOnClose: (() => void) | null = null;\n private _currentOpener: Handle | null = null;\n // Rebuild-mode: when true, each show() scrolls the menu to the bottom after\n // layout so the latest (bottom-most) items are visible on open.\n private _scrollToBottomOnShow: boolean = false;\n\n /**\n * Constructs a Menu. With no arguments, a rebuild-mode (right-click context)\n * menu whose items are supplied per `show()` call. With `items` (and `onClose`),\n * a persistent-mode ([`MenuBar`](/api/component/menubar/classes/MenuBar) dropdown)\n * menu whose items are built immediately and reused across `open()` / `close()`\n * cycles.\n *\n * A single optional-parameter signature, deliberately not an overload pair: the\n * `callable()` export's construct/call type is derived via TS `ConstructorParameters`,\n * which captures only the *last* overload — an overload pair would drop the\n * no-arg form and make `Menu()` a type error for consumers.\n *\n * @param items - The menu item configurations, or a provider called once now\n * to produce them. A submenu is rebuilt on each open, so a provider passed\n * for a submenu's items is re-invoked every time that submenu opens. Omit for\n * a rebuild-mode context menu.\n * @param onClose - Callback invoked when the panel should close (item activated or outside click).\n */\n constructor(items?: MenuItemConfig[] | (() => MenuItemConfig[]), onClose?: () => void) {\n super();\n\n this._persistent = items !== undefined;\n this._itemsProvider = typeof items === \"function\" ? items : null;\n this._onClose = onClose ?? null;\n\n const vbox = new VBox();\n\n vbox.setComponentSpacing(0);\n vbox.setStretching(true);\n\n this.setLayoutManager(vbox);\n\n if (this._persistent) {\n this.applyPersistentChrome();\n // Fixed array: build once now. A provider defers to open(), which\n // re-resolves it on every open (so the sole resolution is per-open).\n if (this._itemsProvider === null) {\n this.buildPersistentItems(items as MenuItemConfig[]);\n }\n } else {\n this.applyRebuildChrome();\n }\n }\n\n /**\n * Lays the menu's items into aligned columns and returns the panel width.\n * Every title shares one column sized to the widest title; the shortcut/chevron\n * right zone is the widest of the shortcut column and a submenu chevron, so\n * shortcuts left-justify in a column and chevrons right-justify at the edge.\n * The width is clamped to `[MIN_MENU_WIDTH, MAX_MENU_WIDTH]`; when the ceiling\n * bites, the title column shrinks and its titles ellipsize. Measured from the\n * items directly so a menu reused across shows re-measures its new content.\n *\n * @returns The clamped panel width in pixels.\n */\n private layOutColumns(): number {\n const items = this._menuItems.filter(\n (i): i is MenuItem => i instanceof MenuItem && !i.isSeparator()\n );\n\n const iconStart = items.some(i => i.hasIcon()) ? MenuItem.ICON_ZONE : MenuItem.TEXT_INSET;\n const maxTitle = items.reduce((m, i) => Math.max(m, i.titleTextWidth()), 0);\n const maxShortcut = items.reduce((m, i) => Math.max(m, i.shortcutTextWidth()), 0);\n const hasChevron = items.some(i => i.hasSubmenu());\n const rightZone = Math.max(maxShortcut, hasChevron ? MenuItem.CHEVRON_ZONE : 0);\n const rightReserve = rightZone > 0 ? MenuItem.TEXT_GAP + rightZone : 0;\n\n const natural = iconStart + maxTitle + rightReserve + MenuItem.RIGHT_PAD;\n const width = Math.min(MAX_MENU_WIDTH, Math.max(MIN_MENU_WIDTH, natural));\n\n // When the ceiling bites, the title column absorbs the shortfall (titles\n // ellipsize); otherwise it is the full widest-title width.\n const titleColumn = Math.min(maxTitle, width - iconStart - rightReserve - MenuItem.RIGHT_PAD);\n\n for (const item of items) {\n item.setColumns(iconStart, titleColumn);\n }\n\n return width;\n }\n\n /**\n * Shows the menu at the given viewport coordinates, replacing any previously\n * displayed items with the new list. **Rebuild-mode only.**\n *\n * The menu grows down-right from the cursor; when there is no room it flips\n * so its bottom / right edge ends at the cursor, never covering it. A menu\n * taller than the room on the side it lands on is capped there and scrolls.\n *\n * @param x - Horizontal viewport coordinate (e.g. `MouseEvent.clientX`).\n * @param y - Vertical viewport coordinate (e.g. `MouseEvent.clientY`).\n * @param configs - Ordered list of item descriptors to render.\n * @param onClose - Optional callback invoked once when the menu next closes\n * (item activated or dismissed by an outside click), letting the opener\n * revert an open-state affordance such as a rotated dropdown chevron.\n * @param excludeEl - Optional element whose subtree is exempt from the\n * outside-click-to-close check. Pass the trigger that opened the menu (e.g.\n * a [`SplitButton`](/api/component/button/classes/SplitButton) chevron) so a\n * pointerdown on it does not self-close the menu before the trigger's own\n * click can toggle it shut — mirroring [`MenuBar`](/api/component/menubar/classes/MenuBar)'s\n * dropdown-button exclusion.\n */\n show(x: number, y: number, configs: MenuItemConfig[], onClose?: () => void, excludeEl?: Handle | null): this {\n this.assertRebuildMode(\"show\");\n\n // The placement primitives only guarantee an on-screen result for an anchor\n // inside the viewport. A cursor from a real event always is; show() is public,\n // so pin it here rather than in showAnchored, which also takes real trigger\n // rects that may legitimately extend past a viewport edge.\n const vp = DOM.source.getViewportSize();\n\n return this.showAnchored(\n pointRect(Util.clamp(x, 0, vp.width), Util.clamp(y, 0, vp.height)),\n configs, onClose ?? null, excludeEl ?? null,\n );\n }\n\n /**\n * Rebuild-mode geometry-and-content core shared by {@link show} (pointer-anchored)\n * and {@link toggleFor} (rect-anchored). Both grow down-right from `anchorRect`\n * and flip per axis when the room runs short. Tears down and rebuilds the item\n * list, then resolves `anchorRect` to a placement and applies the resulting\n * position and height clamp.\n *\n * @param anchorRect - The cursor point (as a zero-size rect) or trigger rect to\n * place the panel against.\n * @param configs - Ordered list of item descriptors to render.\n * @param onClose - Callback invoked once when the menu next closes, or `null`.\n * @param excludeEl - Element whose subtree is exempt from the outside-click\n * check, or `null`.\n */\n private showAnchored(anchorRect: Rect, configs: MenuItemConfig[], onClose: (() => void) | null, excludeEl: Handle | null): this {\n this._rebuildOnClose = onClose;\n this._excludedEl = excludeEl;\n\n for (const item of this._menuItems) {\n if (item instanceof MenuItem) {\n item.dispose();\n }\n }\n\n this._menuItems = [];\n this.removeAllComponents();\n\n this.pauseLayout();\n\n for (const config of configs) {\n const item: MenuItem | MenuSeparator = config.separator === true\n ? new MenuSeparator(\"context-menu\")\n : new MenuItem(\n config,\n () => {\n config.action?.();\n this.hide();\n },\n (hoveredItem) => { this.handleItemOpenSubmenu(hoveredItem); },\n \"context-menu\"\n );\n\n this.addComponent(item);\n this._menuItems.push(item);\n }\n\n this.resumeLayout();\n\n const contentWidth = this.layOutColumns();\n const naturalWidth = this._menuWidth ?? contentWidth;\n\n this.setWidth(naturalWidth);\n\n // A reused menu still carries the previous show's max-height — set by\n // `applyViewportHeightClamp` to that show's available room. Because\n // `getPreferredSize` clamps to the component's own max, leaving it in\n // place would cap the new content at the old height, so a menu that\n // grew past its previous size would neither expand nor reserve a\n // scrollbar gutter on reopen. Clear it before measuring.\n this.setMaxSize(Number.MAX_VALUE, Number.MAX_VALUE);\n\n const totalHeight = this.getPreferredSize()?.height ?? 0;\n\n const el = this.getElement(true)!;\n\n const vp = DOM.source.getViewportSize();\n\n // First pass at the natural width resolves the vertical room. Width does not\n // affect the vertical placement, so `available` stays correct after the\n // scrollbar-gutter widening below; only `x` needs the second pass.\n const available = resolvePlacement(anchorRect, { width: naturalWidth, height: totalHeight }, vp).available;\n\n // When the content is taller than the room on the side the menu lands on,\n // `applyViewportHeightClamp` caps the height and the `overflow-y: auto`\n // scrollbar engages. Reserve its width as a right inset — and widen the\n // content-sized panel to match — so items lay out beside the native\n // scrollbar instead of beneath it. `Component.getInnerSize` subtracts the\n // inset, so the VBox stretches items to `naturalWidth`, unchanged from the\n // no-scroll case.\n const gutter = totalHeight > available ? DOM.source.getScrollBarWidth() : 0;\n\n this.setInsets(new Insets(4, gutter, 4, 0));\n this.setWidth(naturalWidth + gutter);\n\n const placement = resolvePlacement(anchorRect, { width: this.getWidth(), height: totalHeight }, vp);\n\n this.setX(placement.x);\n this.setY(placement.y);\n this.applyViewportHeightClamp(placement.available, totalHeight);\n\n this.scheduleLayout();\n\n LayerManager.mount(el);\n\n this.setVisible(true);\n this.fadeIn();\n\n // Join the central layer tree: the manager owns the outside-pointerdown /\n // window-blur / Escape dismissal and stamps the z-index. Registering while\n // whatever opened the menu is topmost links it under that layer, so a\n // context menu raised over a modal dialog inherits the dialog band and\n // paints in front of it.\n LayerManager.register(this);\n this.setZIndex(LayerManager.getZIndex(this));\n\n // Optionally reveal the bottom of the list on open (e.g. a history menu\n // whose latest entries sit at the bottom). Flush the scheduled layout\n // first so the item heights are committed and `getMaxScrollTop` is\n // accurate; when nothing overflows the offset is 0 and this is a no-op.\n if (this._scrollToBottomOnShow) {\n this.flushLayout();\n this.setScrollTop(this.getMaxScrollTop());\n }\n\n return this;\n }\n\n /**\n * Opens or closes the menu for a given opener element. **Rebuild-mode only.**\n *\n * This is the toggle form of {@link show} for a left-click dropdown trigger\n * (e.g. a [`SplitButton`](/api/component/button/classes/SplitButton) chevron\n * or a [`ToolBar`](/api/component/menubar/classes/ToolBar) overflow button):\n * the opener is excluded from the outside-click dismissal (so its own\n * pointerdown does not self-close the menu) and remembered, so a second press\n * of the *same* opener closes the menu instead of reopening it. Pressing a\n * *different* opener while the menu is open switches it to that opener. The\n * menu opens below `anchorRect` and flips above it when the room below is\n * short, right-aligning to it when the left alignment overflows. Plain\n * {@link show} stays the right call for right-click context menus, which\n * should reposition — not close — on a repeat trigger; the remaining\n * distinction from `toggleFor` is the toggle identity, the opener exclusion,\n * and the empty-list suppression below.\n *\n * An empty `configs` opens nothing: the menu is not shown, `onClose` fires once\n * so the opener can revert an open-state affordance, and no opener state is\n * recorded. A repeat press of the *same* opener still closes an open menu — the\n * toggle-shut branch runs before the empty check.\n *\n * @param openerEl - The element that triggers the menu; excluded from the\n * outside-click check and used as the toggle identity.\n * @param anchorRect - The trigger's viewport rect (e.g.\n * `DOM.source.getViewportRect(button)`); the menu opens below it and flips\n * above it when the room below is short.\n * @param configs - Ordered list of item descriptors to render. An empty list\n * opens nothing (see above).\n * @param onClose - Optional callback invoked once when the menu next closes,\n * or when an empty `configs` suppresses the open.\n *\n * @returns This menu, for method chaining.\n */\n toggleFor(openerEl: Handle, anchorRect: Rect, configs: MenuItemConfig[], onClose?: () => void): this {\n this.assertRebuildMode(\"toggleFor\");\n\n // Same opener fired again while its menu is open: close it. Its pointerdown\n // was excluded from the dismissal above, so this click is the toggle-shut.\n // MUST stay ahead of the empty check below — a provider that has since gone\n // empty must still be able to close the panel it opened.\n if (this._currentOpener === openerEl) {\n this.hide();\n\n return this;\n }\n\n // Nothing to show: an empty panel is never useful, so open nothing. `onClose`\n // still fires so the opener reverts an optimistic open-state affordance (e.g.\n // SplitButton's spun-up chevron), and `_currentOpener` stays untouched, so the\n // next press of this opener is a fresh open rather than a stale toggle-shut.\n if (configs.length === 0) {\n onClose?.();\n\n return this;\n }\n\n // Closed, or open for a different opener: (re)show anchored for this one.\n this.showAnchored(anchorRect, configs, onClose ?? null, openerEl);\n this._currentOpener = openerEl;\n\n return this;\n }\n\n /**\n * Hides the menu and detaches it from the DOM. **Rebuild-mode only.**\n *\n * The instance remains alive and can be shown again by calling `show()`.\n */\n hide(): this {\n this.assertRebuildMode(\"hide\");\n\n this.closeOpenSubmenu();\n\n LayerManager.unregister(this);\n\n this.fadeOutAndDetach();\n\n // Forget the toggle opener so the next `toggleFor` for it opens rather\n // than seeing a stale match and closing an already-closed menu.\n this._currentOpener = null;\n\n // Fire the per-show close callback exactly once, clearing it first so a\n // later bare `hide()` (or re-show) can't re-invoke a stale opener's hook.\n const onClose = this._rebuildOnClose;\n this._rebuildOnClose = null;\n onClose?.();\n\n return this;\n }\n\n /**\n * Returns the current pixel width of the rebuild-mode menu panel (the\n * content-fit width, or an explicit override set via {@link setMenuWidth}).\n *\n * @returns The current panel width in pixels.\n */\n getMenuWidth(): number {\n return this.getWidth();\n }\n\n /**\n * Pins the rebuild-mode menu panel to an explicit width, overriding the\n * default content-fit sizing. **Rebuild-mode only.**\n *\n * @param width - Width in pixels.\n */\n setMenuWidth(width: number): this {\n this.assertRebuildMode(\"setMenuWidth\");\n\n this._menuWidth = width;\n\n return this;\n }\n\n /**\n * Controls whether the menu scrolls to the bottom of its item list each time\n * it is shown. **Rebuild-mode only.**\n *\n * Use this for a menu whose latest entries sit at the bottom (e.g. a\n * chronological notification history), so the most recent items are visible\n * on open rather than the user having to scroll down. A no-op when the list\n * fits without scrolling.\n *\n * @param value - `true` to scroll to the bottom on every show.\n *\n * @returns This menu, for method chaining.\n */\n setScrollToBottomOnShow(value: boolean): this {\n this.assertRebuildMode(\"setScrollToBottomOnShow\");\n\n this._scrollToBottomOnShow = value;\n\n return this;\n }\n\n /**\n * Opens this panel positioned below the anchor element (top-level) or to the\n * right of the parent panel (submenu). Appends to `document.documentElement`.\n * **Persistent-mode only.**\n *\n * @param anchorEl - The `HTMLElement` of the triggering button or menu item.\n * @param parentPanel - When set, positions the panel as a submenu of this parent.\n */\n open(anchorEl: Handle, parentPanel?: Menu): this {\n this.assertPersistentMode(\"open\");\n\n // A provider-sourced menu re-resolves its items on every open, so labels\n // and enabled state reflect current state each time it is shown (a submenu\n // is a fresh panel per open, but a reused top-level dropdown is not).\n if (this._itemsProvider) {\n this.rebuildPersistentItems(this._itemsProvider());\n }\n\n // Clear the previous open's max-height (set by `applyViewportHeightClamp`)\n // before measuring, so a provider-sourced dropdown that grew reflects its\n // new content height rather than being capped at the old available room.\n this.setMaxSize(Number.MAX_VALUE, Number.MAX_VALUE);\n\n const totalHeight = this.getPreferredSize()?.height ?? (this._menuItems.length * MenuItem.HEIGHT + 8);\n const width = this.getWidth();\n\n const el = this.getElement(true)!;\n LayerManager.mount(el);\n\n const vp = DOM.source.getViewportSize();\n\n if (parentPanel) {\n const parentEl = parentPanel.getElement();\n const parentRect = parentEl\n ? DOM.source.getElementRect(parentEl)\n : { left: 0, right: 0, top: 0, bottom: 0 };\n const anchorRect = DOM.source.getElementRect(anchorEl);\n\n let x = parentRect.right;\n\n if (x + width > vp.width) {\n x = parentRect.left - width;\n }\n\n // A submenu grows down from the anchor item's top, and flips up\n // against that same top edge when there is more room above.\n const y = this.placeVertically(anchorRect.top, anchorRect.top, totalHeight, vp.height);\n\n this.setAutoCommitStyle(false);\n this.setX(Math.max(0, x));\n this.setY(Math.max(0, y));\n this.setAutoCommitStyle(true);\n } else {\n const anchorRect = DOM.source.getElementRect(anchorEl);\n\n let x = anchorRect.left;\n\n if (x + width > vp.width) {\n x = vp.width - width;\n }\n\n // A top-level dropdown grows down from the anchor's bottom, and\n // flips up against the anchor's top when there is more room above.\n const y = this.placeVertically(anchorRect.bottom, anchorRect.top, totalHeight, vp.height);\n\n this.setAutoCommitStyle(false);\n this.setX(Math.max(0, x));\n this.setY(Math.max(0, y));\n this.setAutoCommitStyle(true);\n }\n\n const anchorId = DOM.source.getId(anchorEl);\n\n if (anchorId) {\n this.getAria().setLabelledBy(anchorId);\n }\n\n this.setVisible(true);\n this.doLayout();\n this.fadeIn();\n\n // Join the central layer tree so the manager owns dismissal and the\n // z-stamp. A submenu opened via `open(item, this)` registers while its\n // parent menu is topmost, so it links under the parent — a pointerdown\n // inside the submenu counts as inside the parent (cross-portal\n // containment) and keeps the whole chain open.\n LayerManager.register(this);\n this.setZIndex(LayerManager.getZIndex(this));\n\n return this;\n }\n\n /**\n * Closes this panel and any open child submenus. Detaches from the DOM.\n * **Persistent-mode only.**\n */\n close(): this {\n this.assertPersistentMode(\"close\");\n\n this.closeOpenSubmenu();\n\n this.setFocusedIndex(-1);\n this.clearItemHighlights();\n\n LayerManager.unregister(this);\n\n this.fadeOutAndDetach();\n\n return this;\n }\n\n /**\n * Clears the hover highlight from every item. A click that activates an item\n * dismisses the menu, detaching the panel from the DOM under the pointer; the\n * browser fires no `mouseout` for an element removed under a stationary\n * pointer, so the hovered item's `setFocused(true)` is never undone by\n * `_onMouseOut`. `setFocusedIndex(-1)` only resets the keyboard-tracked item,\n * not one highlighted purely by hover. Persistent-mode menus reuse these item\n * elements across open/close, so without this sweep the stale highlight\n * reappears the next time the menu opens.\n */\n private clearItemHighlights(): void {\n for (const item of this._menuItems) {\n if (item instanceof MenuItem) {\n item.setFocused(false);\n }\n }\n }\n\n /**\n * Plays the standard menu-panel entrance fade. Cancels any in-flight\n * fade-out so the deferred detach skips removing the still-visible panel.\n */\n private fadeIn(): void {\n fadeShow(this, { durationMs: MENU_ANIM_DURATION_MS });\n }\n\n /**\n * Plays the standard menu-panel exit fade, then hides and detaches the\n * panel from the DOM when the transition completes. A fresh `show()` /\n * `open()` during the fade cancels the deferred detach.\n */\n private fadeOutAndDetach(): void {\n fadeHideAndDetach(this, { durationMs: MENU_ANIM_DURATION_MS });\n }\n\n /**\n * Moves keyboard focus to the item at the given index. Pass `-1` to clear focus.\n * **Persistent-mode only.**\n *\n * @param index - Zero-based item index, or `-1` to clear.\n */\n focusItem(index: number): this {\n this.assertPersistentMode(\"focusItem\");\n\n this.setFocusedIndex(index);\n\n return this;\n }\n\n /**\n * Moves focus to the next focusable item, wrapping around and skipping separators.\n * **Persistent-mode only.**\n */\n focusNext(): this {\n this.assertPersistentMode(\"focusNext\");\n\n let next = this._focusedIndex + 1;\n\n while (next < this._menuItems.length && this.isItemSeparator(next)) {\n next++;\n }\n\n if (next >= this._menuItems.length) {\n next = 0;\n\n while (next < this._menuItems.length && this.isItemSeparator(next)) {\n next++;\n }\n }\n\n this.setFocusedIndex(next);\n\n return this;\n }\n\n /**\n * Moves focus to the previous focusable item, wrapping around and skipping separators.\n * **Persistent-mode only.**\n */\n focusPrev(): this {\n this.assertPersistentMode(\"focusPrev\");\n\n let prev = this._focusedIndex - 1;\n\n while (prev >= 0 && this.isItemSeparator(prev)) {\n prev--;\n }\n\n if (prev < 0) {\n prev = this._menuItems.length - 1;\n\n while (prev >= 0 && this.isItemSeparator(prev)) {\n prev--;\n }\n }\n\n this.setFocusedIndex(prev);\n\n return this;\n }\n\n /**\n * Activates the currently focused item. No-op when no item is focused or the item\n * is disabled or a separator. **Persistent-mode only.**\n */\n activateFocused(): void {\n this.assertPersistentMode(\"activateFocused\");\n\n if (this._focusedIndex < 0 || this._focusedIndex >= this._menuItems.length) {\n return;\n }\n\n const item = this._menuItems[this._focusedIndex];\n\n if (item instanceof MenuItem && !item.isSeparator() && item.isEnabled()) {\n item.activate();\n }\n }\n\n /**\n * Returns the index of the currently focused item, or `-1` if no item is focused.\n * **Persistent-mode only.**\n *\n * @returns The focused item index, or -1.\n */\n getFocusedIndex(): number {\n this.assertPersistentMode(\"getFocusedIndex\");\n\n return this._focusedIndex;\n }\n\n /**\n * Sets an element whose subtree is excluded from the outside-click-to-close check.\n * **Persistent-mode only.**\n *\n * Used by [`MenuBar`](/api/component/menubar/classes/MenuBar) to prevent a pointerdown on its own buttons from closing the panel\n * before the button's click handler has a chance to toggle the menu.\n *\n * @param el - The element to exclude, or `null` to clear.\n */\n setExcludedElement(el: Handle | null): this {\n this.assertPersistentMode(\"setExcludedElement\");\n\n this._excludedEl = el;\n\n return this;\n }\n\n /**\n * Returns the element whose subtree is currently excluded from the\n * outside-click-to-close check, or `null` if none is set.\n *\n * @returns The excluded element, or null.\n */\n getExcludedElement(): Handle | null {\n return this._excludedEl;\n }\n\n /**\n * Disposes all [`MenuItem`](/api/component/container/classes/MenuItem) children, removing their Event listeners.\n * **Persistent-mode only.**\n */\n dispose(): void {\n this.assertPersistentMode(\"dispose\");\n\n for (const item of this._menuItems) {\n if (item instanceof MenuItem) {\n item.dispose();\n }\n }\n }\n\n /**\n * Applies the persistent-mode chrome (MenuBar dropdown CSS variables, aria role).\n */\n private applyPersistentChrome(): void {\n this.setBackgroundColor(\"var(--ts-ui-menu-bar-panel-bg, rgb(255, 255, 255))\");\n this.setInsets(new Insets(4, 0, 4, 0));\n this.setBorder({ border: \"1px solid var(--ts-ui-menu-bar-panel-border, rgb(200, 200, 200))\" });\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n this.setShadow(\"var(--ts-ui-menu-bar-panel-shadow, 2px 4px 8px rgba(0, 0, 0, 0.15))\");\n this.getAria().setRole(\"menu\");\n this.setContain(\"layout\");\n\n this.enableVerticalScroll();\n }\n\n /**\n * Applies the rebuild-mode chrome (right-click context-menu CSS variables).\n */\n private applyRebuildChrome(): void {\n this.setVisible(false);\n this.setBackgroundColor(\"var(--ts-ui-context-menu-bg, rgb(255, 255, 255))\");\n this.setInsets(new Insets(4, 0, 4, 0));\n this.setBorder({ border: \"1px solid var(--ts-ui-context-menu-border, rgb(200, 200, 200))\" });\n this.setBorderRadius(\"var(--ts-ui-border-radius, 4px)\");\n this.setShadow(\"var(--ts-ui-context-menu-shadow, 2px 4px 8px rgba(0, 0, 0, 0.15))\");\n this.setContain(\"layout\");\n\n this.enableVerticalScroll();\n }\n\n /**\n * Wires the native vertical-scroll primitives so an over-tall menu scrolls\n * its items instead of overflowing the clamped panel height. This is the\n * `\"y\"` case of [`Panel.setAutoScroll`](/api/core/classes/Panel#setautoscroll)\n * replicated directly: `overflow-x: hidden` (so a `visible` x-axis does not\n * compute to `auto` and sprout a spurious horizontal scrollbar), `overflow-y:\n * auto`, and the layout manager's vertical overflow flag so the `VBox` lays\n * items out past the clamped inner height rather than compressing them.\n */\n private enableVerticalScroll(): void {\n this.setOverflowX(\"hidden\");\n this.setOverflowY(\"auto\");\n\n (this.getLayoutManager() as VBox).setOverflowing(false, true);\n }\n\n /**\n * Caps the menu's height to the vertical room available at its anchored\n * position, then applies the content height. Because `Menu` is a\n * `clampsToContentSize()` component, `setHeight` runs `contentHeight` through\n * `clampHeight`, which caps it at `availableHeight`; when the content fits no\n * clamp fires, and when it overflows the height is capped and the\n * `overflow-y: auto` scrollbar engages. Width is left unconstrained via the\n * `Number.MAX_VALUE` \"no constraint\" sentinel — horizontal scroll is a\n * non-goal.\n *\n * @param availableHeight - Vertical room (px) the menu may occupy at its anchor.\n * @param contentHeight - The unclamped preferred height (px) of the menu's items.\n */\n private applyViewportHeightClamp(availableHeight: number, contentHeight: number): void {\n this.setMaxSize(Number.MAX_VALUE, Math.max(0, availableHeight));\n this.setHeight(contentHeight);\n }\n\n /**\n * Resolves the persistent-mode panel's top coordinate and applies the\n * matching height clamp. The menu grows downward from `growTop` unless the\n * content overflows the room below *and* the room above the anchor is\n * larger, in which case it flips to grow upward from `anchorTop`. Delegates\n * the room/flip arithmetic to {@link positionFlexibleAnchored}, which measures\n * the room against the side the menu actually grows toward — never re-derived\n * from a clamped top, which would let a flipped, viewport-pinned menu grow\n * back down across its anchor.\n *\n * @param growTop - The viewport edge the menu grows downward from.\n * @param anchorTop - The anchor edge the menu flips up against.\n * @param totalHeight - The unclamped preferred height (px) of the menu's items.\n * @param viewportHeight - The current viewport height (px).\n * @returns The resolved top coordinate (px) for the panel.\n */\n private placeVertically(growTop: number, anchorTop: number, totalHeight: number, viewportHeight: number): number {\n const { start, available } = positionFlexibleAnchored(anchorTop, growTop, totalHeight, viewportHeight, VIEWPORT_MARGIN);\n\n this.applyViewportHeightClamp(available, totalHeight);\n\n return start;\n }\n\n /**\n * Tear down the current persistent items and rebuild them from `configs`.\n * Used by `open()` to re-resolve a provider-sourced dropdown each time it\n * shows, so its labels / enabled state track current state. Mirrors the\n * rebuild-mode `show()` teardown, then defers to {@link buildPersistentItems}.\n *\n * @param configs - The freshly-resolved item configurations.\n */\n private rebuildPersistentItems(configs: MenuItemConfig[]): void {\n this.closeOpenSubmenu();\n\n for (const item of this._menuItems) {\n if (item instanceof MenuItem) {\n item.dispose();\n }\n }\n\n this._menuItems = [];\n this._focusedIndex = -1;\n this.removeAllComponents();\n\n this.buildPersistentItems(configs);\n }\n\n /**\n * Builds the item list for persistent mode and sets the panel width.\n *\n * @param items - The item configs from the constructor.\n */\n private buildPersistentItems(items: MenuItemConfig[]): void {\n this.pauseLayout();\n\n for (const config of items) {\n const item = new MenuItem(\n config,\n () => { config.action?.(); this._onClose!(); },\n (hoveredItem) => { this.handleItemOpenSubmenu(hoveredItem); }\n );\n\n this.addComponent(item);\n this._menuItems.push(item);\n }\n\n this.resumeLayout();\n\n this.setWidth(this.layOutColumns());\n }\n\n /**\n * Updates the focused-item highlight, clearing the previous item and setting the new one.\n *\n * @param index - The new focused index, or -1 to clear.\n */\n private setFocusedIndex(index: number): void {\n if (this._focusedIndex >= 0 && this._focusedIndex < this._menuItems.length) {\n const prev = this._menuItems[this._focusedIndex];\n\n if (prev instanceof MenuItem) {\n prev.setFocused(false);\n }\n }\n\n this._focusedIndex = index;\n\n if (index >= 0 && index < this._menuItems.length) {\n const next = this._menuItems[index];\n\n if (next instanceof MenuItem) {\n next.setFocused(true);\n }\n }\n }\n\n /**\n * Returns `true` if the item at the given index should be skipped during focus traversal.\n *\n * @param index - Zero-based item index.\n * @returns Whether the item is a separator (or [`MenuSeparator`](/api/component/container/classes/MenuSeparator) instance).\n */\n private isItemSeparator(index: number): boolean {\n const item = this._menuItems[index];\n\n if (item instanceof MenuSeparator) {\n return true;\n }\n\n if (item instanceof MenuItem) {\n return item.isSeparator();\n }\n\n return false;\n }\n\n /**\n * Closes and forgets this menu's open child submenu, if any. Shared by the\n * teardown paths (`hide` / `close`) and by the submenu switch in\n * `handleItemOpenSubmenu` when a different item takes the child slot.\n */\n private closeOpenSubmenu(): void {\n if (this._openSubmenuPanel) {\n this._openSubmenuPanel.close();\n this._openSubmenuItem?.getAria().setExpanded(false);\n this._openSubmenuPanel = null;\n this._openSubmenuItem = null;\n }\n }\n\n /**\n * Closes the whole menu chain through the current mode's close path: the\n * persistent-mode `onClose` callback (a menubar dropdown), or `hide()` for a\n * rebuild-mode context menu — which also tears down any open child submenu.\n * Used by an activated submenu leaf and the outside-click / window-blur\n * dismissals, so a submenu selection closes the parent context menu too.\n */\n private dismissAll(): void {\n if (this._persistent) {\n this._onClose!();\n } else {\n this.hide();\n }\n }\n\n // ----- DismissableLayer -----\n\n /**\n * Returns the menu panel's root element for the central layer tree.\n *\n * @returns The menu's element, or null when not yet rendered.\n */\n getLayerElement(): Handle | null {\n return this.getElement() ?? null;\n }\n\n /**\n * Returns the dismiss mode the document-level handlers consult. A menu is\n * `\"click-outside\"`: dismissed by a `pointerdown` outside its layer subtree\n * (and its excluded trigger) and by a window blur, but never by a focus\n * move — exactly the pointerdown + window-blur behaviour the menu owned\n * before it joined the layer tree.\n *\n * @returns The layer dismiss mode.\n */\n getDismissMode(): LayerDismissMode {\n return \"click-outside\";\n }\n\n /**\n * Advisory close request from the manager (an outside `pointerdown`, a\n * window blur, or Escape). Routes through the mode-aware close chain: a\n * persistent (MenuBar) menu fires its `onClose`, a rebuild (context) menu\n * calls `hide()`; either way any open submenu is torn down.\n */\n requestClose(): void {\n this.dismissAll();\n }\n\n /**\n * Returns the trigger element excluded from the manager's outside-pointerdown\n * test — the context-menu opener passed to {@link show} / {@link toggleFor},\n * or the [`MenuBar`](/api/component/menubar/classes/MenuBar) button set via\n * {@link setExcludedElement} — so the gesture that opened the menu does not\n * immediately re-close it.\n *\n * @returns The excluded element, or null.\n */\n getAnchorElement(): Handle | null {\n return this._excludedEl;\n }\n\n /**\n * Returns the dropdown z-index band so an unrelated top-level menu stacks in\n * the dropdown family. A submenu — or a menu opened while another layer is\n * topmost — inherits its opener's band instead, so it rises above the\n * surface it was opened from.\n *\n * @returns The dropdown band base.\n */\n getBand(): number {\n return LayerManager.Band.Dropdown;\n }\n\n /**\n * Mirrors a manager-allocated z-index onto the element when the layer this\n * menu was opened inside is raised via the manager's bring-to-front path,\n * so an open menu never falls behind the surface it belongs to.\n *\n * @param zIndex - The fresh z-index assigned by the manager.\n */\n onZIndexChanged(zIndex: number): void {\n this.setZIndex(zIndex);\n }\n\n /**\n * Handles the open-submenu signal from a hovered or activated [`MenuItem`](/api/component/container/classes/MenuItem).\n *\n * Closes any existing child submenu when the item has no submenu (or is\n * disabled); opens or switches to the child panel when it does.\n *\n * @param item - The [`MenuItem`](/api/component/container/classes/MenuItem) that triggered the signal.\n */\n private handleItemOpenSubmenu(item: MenuItem): void {\n // A disabled item opens no submenu (even though it has one); like a plain\n // item, hovering it just closes any sibling submenu that is open.\n if (!item.hasSubmenu() || !item.isEnabled()) {\n this.closeOpenSubmenu();\n\n return;\n }\n\n if (this._openSubmenuItem === item) {\n return;\n }\n\n this.closeOpenSubmenu();\n\n const submenuPanel = new Menu(\n item.getSubmenuConfig()!.items,\n () => { this.dismissAll(); }\n );\n\n this._openSubmenuPanel = submenuPanel;\n this._openSubmenuItem = item;\n item.getAria().setExpanded(true);\n\n submenuPanel.setExcludedElement(this.getElement(true)!);\n submenuPanel.open(item.getElement(true)!, this);\n }\n\n /**\n * Throws if the method called is not valid in the current mode (rebuild-only check).\n *\n * @param method - Name of the method that was invoked, used in the error message.\n */\n private assertRebuildMode(method: string): void {\n if (this._persistent) {\n throw new Error(`Menu.${method}() is only valid in rebuild mode (constructed without arguments).`);\n }\n }\n\n /**\n * Throws if the method called is not valid in the current mode (persistent-only check).\n *\n * @param method - Name of the method that was invoked, used in the error message.\n */\n private assertPersistentMode(method: string): void {\n if (!this._persistent) {\n throw new Error(`Menu.${method}() is only valid in persistent mode (constructed with items and onClose).`);\n }\n }\n}\n\nconst MenuCallable = callable(Menu);\ntype MenuCallable = Menu;\nexport {\n Menu as _Menu,\n MenuCallable as Menu\n};\n"],"mappings":"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"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{l as e,n as t,u as n}from"./Position-B-WfZYAJ.js";import{f as r,t as i,y as a}from"./Component-BR2f0Itb.js";import{t as o}from"./Glyph-WUqTF_Z9.js";import{a as s,i as c,n as l}from"./Tooltip-4lU_tf6r.js";import{n as u,r as d}from"./AnimatedDropdown-BbGCs70E.js";import{t as f}from"./Text-D1RrBY52.js";import{n as p}from"./VBox-D5B9ecw0.js";var m=r(class MenuItem extends i{static HEIGHT=24;static SEPARATOR_HEIGHT=9;static TEXT_INSET=8;static ICON_ZONE=24;static CHEVRON_ZONE=16;static RIGHT_PAD=8;static TEXT_GAP=10;_config;_onActivate;_onOpenSubmenu;_cssVarPrefix;_iconText=null;_iconGlyph=null;_titleText=null;_shortcutText=null;_chevronText=null;_iconStart=null;_titleColumn=null;_submenuTimer=null;_onMouseOver;_onMouseOut;_onClick;constructor(e,t,n,r=`menu-bar`,i){if(super(),this._config=e,this._onActivate=t,this._onOpenSubmenu=n,this._cssVarPrefix=r,e.separator){this.setHeight(MenuItem.SEPARATOR_HEIGHT),this.setPreferredSize(0,MenuItem.SEPARATOR_HEIGHT),this.setBackgroundColor(`transparent`),this.setElementCSSRule(`borderTop`,`1px solid var(--ts-ui-${r}-separator-color, rgb(220, 220, 220))`),this.setElementCSSRule(`margin`,`4px 0`),this.getAria().setRole(`separator`),this._onMouseOver=()=>{},this._onMouseOut=()=>{},this._onClick=()=>{},i&&this.applyOptions(i);return}let s=e.enabled!==!1;this.setHeight(MenuItem.HEIGHT),this.setPreferredSize(0,MenuItem.HEIGHT),this.setBackgroundColor(`transparent`),this.setElementCSSRule(`fontSize`,`var(--ts-ui-button-font-size, var(--ts-ui-font-size))`),this.setCursor(s?`pointer`:`default`),this.getAria().setRole(`menuitem`),s||(this.setForegroundColor(`var(--ts-ui-${r}-item-disabled-color, rgb(170, 170, 170))`),this.getAria().setDisabled(!0)),e.glyph?(this._iconGlyph=new o(e.glyph),this._iconGlyph.setPointerEvents(`none`),this._iconGlyph.setPreferredSize(16,16),e.glyphColor&&this._iconGlyph.setForegroundColor(e.glyphColor),this.addComponent(this._iconGlyph)):(this._iconText=new f(e.icon??``),this._iconText.setPointerEvents(`none`),this._iconText.centerInHeight(MenuItem.HEIGHT),this._iconText.setVisible(!!e.icon),this.addComponent(this._iconText)),this._titleText=new f(e.text??``),this._titleText.setPointerEvents(`none`),this._titleText.centerInHeight(MenuItem.HEIGHT),this._titleText.setWhiteSpace(`nowrap`),this._titleText.setOverflow(`hidden`),this._titleText.setTextOverflow(`ellipsis`),this.addComponent(this._titleText),e.shortcut&&(this._shortcutText=new f(e.shortcut),this._shortcutText.setPointerEvents(`none`),this._shortcutText.centerInHeight(MenuItem.HEIGHT),this._shortcutText.setTextAlign(`left`),this._shortcutText.setForegroundColor(`var(--ts-ui-${r}-item-shortcut-color, rgb(140, 140, 140))`),this.addComponent(this._shortcutText)),this.hasSubmenu()&&(this._chevronText=new f(`▶`),this._chevronText.setPointerEvents(`none`),this._chevronText.centerInHeight(MenuItem.HEIGHT),this._chevronText.setTextAlign(`center`),this._chevronText.setForegroundColor(`var(--ts-ui-${r}-item-shortcut-color, rgb(140, 140, 140))`),this.addComponent(this._chevronText),this.getAria().setHasPopup(`menu`),this.getAria().setExpanded(!1)),this._onMouseOver=()=>{this.setFocused(!0),s&&this.hasSubmenu()?this._submenuTimer=setTimeout(()=>{this._submenuTimer=null,this._onOpenSubmenu(this)},150):this._onOpenSubmenu(this)},this._onMouseOut=()=>{this.setFocused(!1),this._submenuTimer!==null&&(clearTimeout(this._submenuTimer),this._submenuTimer=null)},this._onClick=()=>{s&&!this.hasSubmenu()&&this._onActivate()},a.addListener(this,`mouseover`,this._onMouseOver),a.addListener(this,`mouseout`,this._onMouseOut),a.addListener(this,`click`,this._onClick),i&&this.applyOptions(i)}applyOptions(e){return super.applyOptions(e),e.text!==void 0&&this._titleText&&this._titleText.setText(e.text),e.enabled!==void 0&&(this.getAria().setDisabled(!e.enabled),this.setCursor(e.enabled?`pointer`:`default`)),e.focused!==void 0&&this.setFocused(e.focused),this}hasSubmenu(){return!!this._config.submenu}hasIcon(){return!!this._config.icon||!!this._config.glyph}titleTextWidth(){return Math.ceil(this._titleText?.getPreferredSize()?.width??0)}shortcutTextWidth(){return!this._config.shortcut||this._shortcutText===null?0:Math.ceil(this._shortcutText.getPreferredSize()?.width??0)}setColumns(e,t){this._iconStart=e,this._titleColumn=t,this.scheduleLayout()}getSubmenuConfig(){return this._config.submenu??null}setFocused(e){return this.setBackgroundColor(e?`var(--ts-ui-${this._cssVarPrefix}-item-hover-bg, rgba(30, 100, 200, 0.12))`:`transparent`),this}isSeparator(){return!!this._config.separator}isEnabled(){return this._config.enabled!==!1}activate(){this.isSeparator()||!this.isEnabled()||(this.hasSubmenu()?this._onOpenSubmenu(this):this._onActivate())}dispose(){this._submenuTimer!==null&&(clearTimeout(this._submenuTimer),this._submenuTimer=null),a.removeListener(this,`mouseover`,this._onMouseOver),a.removeListener(this,`mouseout`,this._onMouseOut),a.removeListener(this,`click`,this._onClick)}getBaseline(){return this._titleText===null?null:this.wrapInnerBaseline(this._titleText.getBaseline())}doLayout(){if(super.doLayout(),this._config.separator)return this;let e=MenuItem.HEIGHT,t=this.getWidth(),n=!!this._config.shortcut&&this._shortcutText!==null,r=this.hasSubmenu(),i=this._iconStart??(this.hasIcon()?MenuItem.ICON_ZONE:MenuItem.TEXT_INSET),a=this._titleColumn??this.titleTextWidth();if(this._iconGlyph){let t=this._iconGlyph.getPreferredSize()??{width:16,height:16},n=Math.max(0,Math.floor((e-t.height)/2));this._iconGlyph.setX(4),this._iconGlyph.setY(n),this._iconGlyph.setWidth(t.width),this._iconGlyph.setHeight(t.height)}else this._iconText&&(this._iconText.setX(4),this._iconText.setY(0),this._iconText.setWidth(20),this._iconText.setHeight(e));return this._titleText&&(this._titleText.setX(i),this._titleText.setY(0),this._titleText.setWidth(a),this._titleText.setHeight(e)),r&&this._chevronText&&(this._chevronText.setX(t-MenuItem.RIGHT_PAD-MenuItem.CHEVRON_ZONE),this._chevronText.setY(0),this._chevronText.setWidth(MenuItem.CHEVRON_ZONE),this._chevronText.setHeight(e)),n&&this._shortcutText&&(this._shortcutText.setX(i+a+MenuItem.TEXT_GAP),this._shortcutText.setY(0),this._shortcutText.setWidth(this.shortcutTextWidth()),this._shortcutText.setHeight(e)),this}}),h={},g=r(class MenuSeparator extends i{static HEIGHT=9;constructor(e=`menu-bar`,t){super(t,h),this.setHeight(MenuSeparator.HEIGHT),this.setPreferredSize(0,MenuSeparator.HEIGHT),this.setBackgroundColor(`transparent`),this.setElementCSSRule(`borderTop`,`1px solid var(--ts-ui-${e}-separator-color, rgb(220, 220, 220))`),this.setElementCSSRule(`margin`,`4px 0`),this.getAria().setRole(`separator`)}}),_=120,v=360,y=120,b=4;function pointRect(e,t){return{x:e,y:t,left:e,top:t,right:e,bottom:t,width:0,height:0}}function resolvePlacement(e,t,n){let r=c(e.top,e.bottom,t.height,n.height,b);return{x:l(e.left,e.right,t.width,n.width,b),y:r.start,available:r.available}}var x=r(class Menu extends i{_persistent;_itemsProvider;_onClose;_menuItems=[];_focusedIndex=-1;_openSubmenuPanel=null;_openSubmenuItem=null;_excludedEl=null;_menuWidth=null;_rebuildOnClose=null;_currentOpener=null;_scrollToBottomOnShow=!1;constructor(e,t){super(),this._persistent=e!==void 0,this._itemsProvider=typeof e==`function`?e:null,this._onClose=t??null;let n=new p;n.setComponentSpacing(0),n.setStretching(!0),this.setLayoutManager(n),this._persistent?(this.applyPersistentChrome(),this._itemsProvider===null&&this.buildPersistentItems(e)):this.applyRebuildChrome()}layOutColumns(){let e=this._menuItems.filter(e=>e instanceof m&&!e.isSeparator()),t=e.some(e=>e.hasIcon())?m.ICON_ZONE:m.TEXT_INSET,n=e.reduce((e,t)=>Math.max(e,t.titleTextWidth()),0),r=e.reduce((e,t)=>Math.max(e,t.shortcutTextWidth()),0),i=e.some(e=>e.hasSubmenu()),a=Math.max(r,i?m.CHEVRON_ZONE:0),o=a>0?m.TEXT_GAP+a:0,s=t+n+o+m.RIGHT_PAD,c=Math.min(v,Math.max(_,s)),l=Math.min(n,c-t-o-m.RIGHT_PAD);for(let n of e)n.setColumns(t,l);return c}show(t,r,i,a,o){this.assertRebuildMode(`show`);let s=n.source.getViewportSize();return this.showAnchored(pointRect(e.clamp(t,0,s.width),e.clamp(r,0,s.height)),i,a??null,o??null)}showAnchored(e,r,i,a){this._rebuildOnClose=i,this._excludedEl=a;for(let e of this._menuItems)e instanceof m&&e.dispose();this._menuItems=[],this.removeAllComponents(),this.pauseLayout();for(let e of r){let t=e.separator===!0?new g(`context-menu`):new m(e,()=>{e.action?.(),this.hide()},e=>{this.handleItemOpenSubmenu(e)},`context-menu`);this.addComponent(t),this._menuItems.push(t)}this.resumeLayout();let o=this.layOutColumns(),c=this._menuWidth??o;this.setWidth(c),this.setMaxSize(Number.MAX_VALUE,Number.MAX_VALUE);let l=this.getPreferredSize()?.height??0,u=this.getElement(!0),d=n.source.getViewportSize(),f=l>resolvePlacement(e,{width:c,height:l},d).available?n.source.getScrollBarWidth():0;this.setInsets(new t(4,f,4,0)),this.setWidth(c+f);let p=resolvePlacement(e,{width:this.getWidth(),height:l},d);return this.setX(p.x),this.setY(p.y),this.applyViewportHeightClamp(p.available,l),this.scheduleLayout(),s.mount(u),this.setVisible(!0),this.fadeIn(),s.register(this),this.setZIndex(s.getZIndex(this)),this._scrollToBottomOnShow&&(this.flushLayout(),this.setScrollTop(this.getMaxScrollTop())),this}toggleFor(e,t,n,r){return this.assertRebuildMode(`toggleFor`),this._currentOpener===e?(this.hide(),this):n.length===0?(r?.(),this):(this.showAnchored(t,n,r??null,e),this._currentOpener=e,this)}hide(){this.assertRebuildMode(`hide`),this.closeOpenSubmenu(),s.unregister(this),this.fadeOutAndDetach(),this._currentOpener=null;let e=this._rebuildOnClose;return this._rebuildOnClose=null,e?.(),this}getMenuWidth(){return this.getWidth()}setMenuWidth(e){return this.assertRebuildMode(`setMenuWidth`),this._menuWidth=e,this}setScrollToBottomOnShow(e){return this.assertRebuildMode(`setScrollToBottomOnShow`),this._scrollToBottomOnShow=e,this}open(e,t){this.assertPersistentMode(`open`),this._itemsProvider&&this.rebuildPersistentItems(this._itemsProvider()),this.setMaxSize(Number.MAX_VALUE,Number.MAX_VALUE);let r=this.getPreferredSize()?.height??this._menuItems.length*m.HEIGHT+8,i=this.getWidth(),a=this.getElement(!0);s.mount(a);let o=n.source.getViewportSize();if(t){let a=t.getElement(),s=a?n.source.getElementRect(a):{left:0,right:0,top:0,bottom:0},c=n.source.getElementRect(e),l=s.right;l+i>o.width&&(l=s.left-i);let u=this.placeVertically(c.top,c.top,r,o.height);this.setAutoCommitStyle(!1),this.setX(Math.max(0,l)),this.setY(Math.max(0,u)),this.setAutoCommitStyle(!0)}else{let t=n.source.getElementRect(e),a=t.left;a+i>o.width&&(a=o.width-i);let s=this.placeVertically(t.bottom,t.top,r,o.height);this.setAutoCommitStyle(!1),this.setX(Math.max(0,a)),this.setY(Math.max(0,s)),this.setAutoCommitStyle(!0)}let c=n.source.getId(e);return c&&this.getAria().setLabelledBy(c),this.setVisible(!0),this.doLayout(),this.fadeIn(),s.register(this),this.setZIndex(s.getZIndex(this)),this}close(){return this.assertPersistentMode(`close`),this.closeOpenSubmenu(),this.setFocusedIndex(-1),this.clearItemHighlights(),s.unregister(this),this.fadeOutAndDetach(),this}clearItemHighlights(){for(let e of this._menuItems)e instanceof m&&e.setFocused(!1)}fadeIn(){d(this,{durationMs:y})}fadeOutAndDetach(){u(this,{durationMs:y})}focusItem(e){return this.assertPersistentMode(`focusItem`),this.setFocusedIndex(e),this}focusNext(){this.assertPersistentMode(`focusNext`);let e=this._focusedIndex+1;for(;e<this._menuItems.length&&this.isItemSeparator(e);)e++;if(e>=this._menuItems.length)for(e=0;e<this._menuItems.length&&this.isItemSeparator(e);)e++;return this.setFocusedIndex(e),this}focusPrev(){this.assertPersistentMode(`focusPrev`);let e=this._focusedIndex-1;for(;e>=0&&this.isItemSeparator(e);)e--;if(e<0)for(e=this._menuItems.length-1;e>=0&&this.isItemSeparator(e);)e--;return this.setFocusedIndex(e),this}activateFocused(){if(this.assertPersistentMode(`activateFocused`),this._focusedIndex<0||this._focusedIndex>=this._menuItems.length)return;let e=this._menuItems[this._focusedIndex];e instanceof m&&!e.isSeparator()&&e.isEnabled()&&e.activate()}getFocusedIndex(){return this.assertPersistentMode(`getFocusedIndex`),this._focusedIndex}setExcludedElement(e){return this.assertPersistentMode(`setExcludedElement`),this._excludedEl=e,this}getExcludedElement(){return this._excludedEl}dispose(){this.assertPersistentMode(`dispose`);for(let e of this._menuItems)e instanceof m&&e.dispose()}applyPersistentChrome(){this.setBackgroundColor(`var(--ts-ui-menu-bar-panel-bg, rgb(255, 255, 255))`),this.setInsets(new t(4,0,4,0)),this.setBorder({border:`1px solid var(--ts-ui-menu-bar-panel-border, rgb(200, 200, 200))`}),this.setBorderRadius(`var(--ts-ui-border-radius, 4px)`),this.setShadow(`var(--ts-ui-menu-bar-panel-shadow, 2px 4px 8px rgba(0, 0, 0, 0.15))`),this.getAria().setRole(`menu`),this.setContain(`layout`),this.enableVerticalScroll()}applyRebuildChrome(){this.setVisible(!1),this.setBackgroundColor(`var(--ts-ui-context-menu-bg, rgb(255, 255, 255))`),this.setInsets(new t(4,0,4,0)),this.setBorder({border:`1px solid var(--ts-ui-context-menu-border, rgb(200, 200, 200))`}),this.setBorderRadius(`var(--ts-ui-border-radius, 4px)`),this.setShadow(`var(--ts-ui-context-menu-shadow, 2px 4px 8px rgba(0, 0, 0, 0.15))`),this.setContain(`layout`),this.enableVerticalScroll()}enableVerticalScroll(){this.setOverflowX(`hidden`),this.setOverflowY(`auto`),this.getLayoutManager().setOverflowing(!1,!0)}applyViewportHeightClamp(e,t){this.setMaxSize(Number.MAX_VALUE,Math.max(0,e)),this.setHeight(t)}placeVertically(e,t,n,r){let{start:i,available:a}=c(t,e,n,r,b);return this.applyViewportHeightClamp(a,n),i}rebuildPersistentItems(e){this.closeOpenSubmenu();for(let e of this._menuItems)e instanceof m&&e.dispose();this._menuItems=[],this._focusedIndex=-1,this.removeAllComponents(),this.buildPersistentItems(e)}buildPersistentItems(e){this.pauseLayout();for(let t of e){let e=new m(t,()=>{t.action?.(),this._onClose()},e=>{this.handleItemOpenSubmenu(e)});this.addComponent(e),this._menuItems.push(e)}this.resumeLayout(),this.setWidth(this.layOutColumns())}setFocusedIndex(e){if(this._focusedIndex>=0&&this._focusedIndex<this._menuItems.length){let e=this._menuItems[this._focusedIndex];e instanceof m&&e.setFocused(!1)}if(this._focusedIndex=e,e>=0&&e<this._menuItems.length){let t=this._menuItems[e];t instanceof m&&t.setFocused(!0)}}isItemSeparator(e){let t=this._menuItems[e];return t instanceof g||t instanceof m&&t.isSeparator()}closeOpenSubmenu(){this._openSubmenuPanel&&(this._openSubmenuPanel.close(),this._openSubmenuItem?.getAria().setExpanded(!1),this._openSubmenuPanel=null,this._openSubmenuItem=null)}dismissAll(){this._persistent?this._onClose():this.hide()}getLayerElement(){return this.getElement()??null}getDismissMode(){return`click-outside`}requestClose(){this.dismissAll()}getAnchorElement(){return this._excludedEl}getBand(){return s.Band.Dropdown}onZIndexChanged(e){this.setZIndex(e)}handleItemOpenSubmenu(e){if(!e.hasSubmenu()||!e.isEnabled()){this.closeOpenSubmenu();return}if(this._openSubmenuItem===e)return;this.closeOpenSubmenu();let t=new Menu(e.getSubmenuConfig().items,()=>{this.dismissAll()});this._openSubmenuPanel=t,this._openSubmenuItem=e,e.getAria().setExpanded(!0),t.setExcludedElement(this.getElement(!0)),t.open(e.getElement(!0),this)}assertRebuildMode(e){if(this._persistent)throw Error(`Menu.${e}() is only valid in rebuild mode (constructed without arguments).`)}assertPersistentMode(e){if(!this._persistent)throw Error(`Menu.${e}() is only valid in persistent mode (constructed with items and onClose).`)}});export{g as n,m as r,x as t};
|
|
2
|
-
//# sourceMappingURL=Menu-Wz4MK6sA.js.map
|