jsl-ui 10.0.5 → 10.1.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.
@@ -0,0 +1,22 @@
1
+ # DropdownMenuComponent
2
+
3
+ ```ts
4
+ import { ButtonComponent, DropdownMenuComponent } from "jsl-ui";
5
+
6
+ const actions = new DropdownMenuComponent({
7
+ content: "Actions", class: "jsl-button jsl-button-outline",
8
+ children: [
9
+ new ButtonComponent({ content: "Edit", type: "button", click: () => edit() }),
10
+ new ButtonComponent({ content: "Archive", type: "button", click: () => archive() })
11
+ ],
12
+ closeOnClick: true
13
+ });
14
+ ```
15
+
16
+ - `children`, `content`, `rawContent`, `class`, `style`, `tag`, `width`, `offsetX`, `disabled`, `title`, `closeOnClick`, `maxHeight`, `maxWidth`, `height`, and `onOpen` retain the corresponding dropdown-menu options. `DropdownMenuButtonComponent` is also exported as an alias.
17
+ - The menu anchors to the trigger, opens below its start edge by default, flips above when needed, and stays within the viewport. `placement` selects `bottom-start`, `bottom-end`, `top-start`, or `top-end`; `offsetX` and `offsetY` adjust its position. Width defaults to `12rem`; height defaults to `max-content`.
18
+ - `onOpen(options)` runs before each opening, so it can replace `options.children` with current items. Return `false` to cancel opening. `closeOnClick` defaults to `false` for menus that contain multiple toggles; set it to `true` to close after a menu click. Call `close()` to close programmatically.
19
+ - A decorative chevron appears after the trigger by default and points upward while open. Set `showIndicator: false` when the trigger already has an overflow or dropdown indicator.
20
+ - The trigger is a native `button` with `type="button"` by default. It exposes `aria-haspopup`, `aria-expanded`, and `aria-controls`. Native buttons and anchors in `children` become menu items; use `data-menu-item` on a custom interactive root to include it in menu keyboard navigation.
21
+ - Enter or Space opens the trigger; Arrow Up/Down opens and focuses the last/first item. In the menu, Arrow keys move between enabled items, Home/End jump to the edges, type-ahead searches labels, Escape closes and restores trigger focus, and Tab exits the menu. Outside pointer or focus closes it without stealing focus. The popup repositions when the page scrolls or resizes.
22
+ - `menuClass`, `menuStyle`, `menuLabel`, and `ariaLabel` customize the popup and trigger. A VNode is preferred for rich trigger content. Use `rawContent` only for trusted HTML strings.
package/docs/README.md CHANGED
@@ -18,7 +18,9 @@ Guides for the public components exported by `jsl-ui`:
18
18
  - [StackPanelComponent](StackPanelComponent.md), [SplitterComponent](SplitterComponent.md)
19
19
  - [TreeViewComponent](TreeViewComponent.md), [NavigationComponent](NavigationComponent.md), [RibbonMenuComponent](RibbonMenuComponent.md)
20
20
  - [DialogComponent](DialogComponent.md), [OverlayComponent](OverlayComponent.md)
21
+ - [DropdownMenuComponent](DropdownMenuComponent.md)
21
22
  - [Notification](Notification.md), [NotificationComponent](NotificationComponent.md)
22
23
  - [ProgressComponent](ProgressComponent.md), [LoadingComponent](LoadingComponent.md)
24
+ - [SliderComponent](SliderComponent.md)
23
25
  - [GridComponent](GridComponent.md)
24
26
  - [IntervalComponent](IntervalComponent.md)
@@ -0,0 +1,20 @@
1
+ # SliderComponent
2
+
3
+ ```ts
4
+ import { SliderComponent } from "jsl-ui";
5
+
6
+ const priceRange = new SliderComponent({
7
+ start: [25, 75], connect: true, step: 5,
8
+ range: { min: 0, max: 100 },
9
+ ariaLabel: ["Minimum price", "Maximum price"],
10
+ tooltips: true,
11
+ onChange: value => console.log(value)
12
+ });
13
+ ```
14
+
15
+ - `start` accepts one number or an array of handle values. `range` uses noUiSlider's range syntax; other supported noUiSlider 14.6 options can be passed alongside it.
16
+ - By default, slider values are formatted and parsed with `numeric.format()` and `numeric.parse()`, using the active culture. Supply noUiSlider's `format` option to override this behavior.
17
+ - `getValue()` returns a number for one handle and a numeric array for multiple handles. `setValue(value)` updates silently; pass `true` as the second argument to fire noUiSlider's `set` event.
18
+ - `onUpdate`, `onSlide`, `onChange`, `onSet`, `onStart`, and `onEnd` receive `(value, handleIndex, allValues)`. `onUpdate` also runs during initialization; programmatic `setValue` does not trigger `onChange`.
19
+ - `ariaLabel` names a single handle. Supply an array to name every handle in a multi-handle slider. `enabled` can be changed with `setEnabled()`.
20
+ - `getRef()` returns the underlying noUiSlider instance for advanced use. The component destroys the slider when it is removed. Override `--jsl-slider-track` and `--jsl-slider-connect` to customize its colors.
@@ -1,5 +1,5 @@
1
- import { IJSLComponent, IJSLVNode } from "jsl-render";
2
- import { noUiSlider } from "../../components/slider/nouislider";
1
+ import { IJSLComponent, IJSLVNode, JSLRender } from "jsl-render";
2
+ import { SliderComponent, ISliderValue } from "../../components/slider/SliderComponent";
3
3
  import { DateComponent } from "../../components/date/DateComponent";
4
4
  import { DateRangeComponent } from "../../components/date-range/DateRangeComponent";
5
5
  import { DateTimeComponent } from "../../components/date-time/DateTimeComponent";
@@ -20,40 +20,12 @@ const field = (id: string, label: string, control: Content) => group([
20
20
  { tag: "label", content: label, attr: { for: id } }, control
21
21
  ], "date-showcase-field");
22
22
 
23
- class SliderSample implements IJSLComponent {
24
- private slider: any;
25
- constructor(private options: { start: number[]; disabled?: boolean; vertical?: boolean; tooltips?: boolean; }) {}
26
- render(): IJSLVNode {
27
- return { tag: "div", attr: {
28
- class: "showcase-slider" + (this.options.vertical ? " showcase-slider-vertical" : ""),
29
- disabled: this.options.disabled ? "" : undefined
30
- } };
31
- }
32
- onCreate(node: IJSLVNode) {
33
- const count = this.options.start.length;
34
- this.slider = noUiSlider.create(node.dom, {
35
- start: this.options.start, range: { min: 0, max: 100 }, step: 1,
36
- connect: count === 2 ? true : [true, false],
37
- orientation: this.options.vertical ? "vertical" : "horizontal",
38
- direction: this.options.vertical ? "rtl" : "ltr",
39
- tooltips: this.options.tooltips ? { to: value => Math.round(value) + "%" } : false
40
- });
41
- node.dom.querySelectorAll<HTMLElement>("[role=slider]").forEach((handle, index) => {
42
- handle.setAttribute("aria-label", count === 2 ? (index === 0 ? "Lower value" : "Upper value") : "Value");
43
- if (this.options.disabled) {
44
- handle.setAttribute("tabindex", "-1");
45
- handle.setAttribute("aria-disabled", "true");
46
- }
47
- });
48
- // Sibling nodes are available after the renderer finishes mounting.
49
- Promise.resolve().then(() => this.slider?.on("update", values => {
50
- const readout = node.dom.parentElement?.querySelector<HTMLElement>(".slider-readout");
51
- if (readout) { readout.textContent = values.map(value => Math.round(+value) + "%").join(" – "); }
52
- }));
53
- }
54
- onRemove() { this.slider?.destroy(); this.slider = null; }
55
- }
56
-
23
+ const sliderValueText = (value: ISliderValue, suffix = "%") =>
24
+ (Array.isArray(value) ? value : [value]).map(part => Math.round(part) + suffix).join(" - ");
25
+ const sliderChange = (target: IJSLVNode, prefix = "Current value: ", suffix = "%") => (value: ISliderValue) => {
26
+ target.content = prefix + sliderValueText(value, suffix);
27
+ JSLRender.lastCreatedRenderer?.refresh();
28
+ };
57
29
  class CalendarSample implements IJSLComponent {
58
30
  private picker: IDatePicker;
59
31
  render(): IJSLVNode { return group([], "showcase-calendar"); }
@@ -81,22 +53,46 @@ function section(id: string, number: string, title: string, description: string,
81
53
  { tag: "details", attr: { class: "usage" }, children: [text("summary", title + " usage & compatibility"), ...usage] }
82
54
  ] };
83
55
  }
84
- const sliderPreview = group([new SliderSample({ start: [30, 75] }), text("span", "", "slider-readout")], "slider-sample");
56
+ const sliderPreviewStatus = status("Current range: 30% - 75%");
57
+ const sliderSingleStatus = status("Current value: 45%");
58
+ const sliderRangeStatus = status("Selected budget: $25 - $70");
59
+ const sliderPreview = group([new SliderComponent({
60
+ start: [30, 75], range: { min: 0, max: 100 }, connect: true, step: 1,
61
+ tooltips: true, ariaLabel: ["Minimum value", "Maximum value"], class: "showcase-slider",
62
+ onChange: sliderChange(sliderPreviewStatus, "Current range: ")
63
+ }), sliderPreviewStatus], "slider-sample");
85
64
  export const sliderShowcase = section("sliders", "04", "Sliders",
86
65
  "A lighter track, a precise handle. Move a value or narrow a range.", sliderPreview, [
87
66
  card("slider-single", "01", "A single value", "Click the track, drag the handle, or use the arrow keys. Home and End reach the limits.", [
88
- group([new SliderSample({ start: [45], tooltips: true }), text("span", "", "slider-readout")], "slider-sample slider-with-tooltip")
67
+ group([new SliderComponent({
68
+ start: 45, range: { min: 0, max: 100 }, step: 5, tooltips: true,
69
+ ariaLabel: "Notification volume", class: "showcase-slider",
70
+ onChange: sliderChange(sliderSingleStatus)
71
+ }), sliderSingleStatus], "slider-sample slider-with-tooltip")
72
+ ]),
73
+ card("slider-range", "02", "Ranges and formatting", "Connect the handles and format their tooltips while values stay numeric in callbacks.", [
74
+ new SliderComponent({
75
+ start: [25, 70], range: { min: 0, max: 100 }, connect: true, step: 5,
76
+ tooltips: [{ to: value => "$" + Math.round(value) }, { to: value => "$" + Math.round(value) }],
77
+ ariaLabel: ["Minimum budget", "Maximum budget"], class: "showcase-slider",
78
+ onChange: sliderChange(sliderRangeStatus, "Selected budget: ", "")
79
+ }), sliderRangeStatus
89
80
  ]),
90
- card("slider-states", "02", "Different directions", "The same design supports vertical sliders and disabled controls.", [
91
- group([new SliderSample({ start: [65], vertical: true }),
92
- group([text("p", "Unavailable range", "inline-note"), new SliderSample({ start: [20, 80], disabled: true })], "slider-disabled-example")
81
+ card("slider-states", "03", "Different directions", "Vertical orientation and disabled state are available through component options.", [
82
+ group([new SliderComponent({
83
+ start: 65, range: { min: 0, max: 100 }, orientation: "vertical", direction: "rtl",
84
+ ariaLabel: "Vertical value", class: "showcase-slider-vertical"
85
+ }), group([text("p", "Unavailable range", "inline-note"), new SliderComponent({
86
+ start: [20, 80], range: { min: 0, max: 100 }, connect: true, enabled: false,
87
+ ariaLabel: ["Unavailable minimum", "Unavailable maximum"], class: "showcase-slider"
88
+ })], "slider-disabled-example")
93
89
  ], "slider-orientations")
94
90
  ])
95
91
  ], [
96
- code('noUiSlider.create(element, {\n start: [25, 75], connect: true, step: 1,\n range: { min: 0, max: 100 }\n});'),
97
- text("p", "The bundled noUiSlider API is preserved. Its CSS now uses the shared theme, including handles, tooltips, pips, and disabled states. Use aria-label on handles to describe their meaning. Override --jsl-slider-track and --jsl-slider-connect for a particular slider.")
92
+ code('new SliderComponent({\n start: [25, 75], connect: true, step: 1,\n range: { min: 0, max: 100 },\n ariaLabel: ["Minimum value", "Maximum value"],\n onChange: value => console.log(value)\n});'),
93
+ text("p", "SliderComponent wraps the bundled noUiSlider 14.6 API and destroys it when the component is removed. Values are numeric: getValue() returns a number for one handle and a number array for multiple handles. setValue(value) updates silently; pass true as its second argument to fire noUiSlider's set event. onUpdate runs on initialization and every update; onSlide runs while moving; onChange runs after user interaction. Each callback receives (value, handleIndex, allValues)."),
94
+ text("p", "Use ariaLabel for one handle or an ariaLabel array for multiple handles. enabled can be changed later with setEnabled(). getRef() exposes the underlying noUiSlider instance for its full API. Override --jsl-slider-track and --jsl-slider-connect to theme a slider.")
98
95
  ]);
99
-
100
96
  const dateStatus = status("Choose a day or enter a date.");
101
97
  const customStatus = status("Uses day/month/year formatting.");
102
98
  export const dateShowcase = section("dates", "05", "Dates",
@@ -0,0 +1,99 @@
1
+ import { IJSLComponent, IJSLVNode, JSLRender } from "jsl-render";
2
+ import { ButtonComponent } from "../../components/button/ButtonComponent";
3
+ import { DropdownMenuComponent } from "../../components/dropdown-menu/DropdownMenuComponent";
4
+
5
+ type Content = IJSLVNode | IJSLComponent;
6
+ const text = (tag: string, content: string, className?: string): IJSLVNode => ({ tag, content, attr: { class: className } });
7
+ const group = (children: Content[], className = "sample-row"): IJSLVNode => ({ tag: "div", attr: { class: className }, children });
8
+ const status = (content: string): IJSLVNode => ({ tag: "p", content, attr: { class: "sample-status", role: "status" } });
9
+ const action = (content: string, click: () => void) => new ButtonComponent({
10
+ content, type: "button", class: "jsl-button jsl-button-ghost", click
11
+ });
12
+ function card(id: string, number: string, title: string, description: string, children: Content[]): IJSLVNode {
13
+ return { tag: "article", attr: { class: "sample-card", id, "aria-labelledby": id + "-title" }, children: [
14
+ group([text("span", number, "sample-number"), { tag: "h3", content: title, attr: { id: id + "-title" } }], "card-heading"),
15
+ text("p", description, "card-description"), ...children
16
+ ] };
17
+ }
18
+ function updateMessage(target: IJSLVNode, message: string): void {
19
+ target.content = message;
20
+ JSLRender.lastCreatedRenderer?.refresh();
21
+ }
22
+
23
+ const previewStatus = status("Choose a menu action.");
24
+ const dynamicStatus = status("Open the menu to refresh its items.");
25
+ let openCount = 0;
26
+
27
+ export const dropdownMenuShowcase: IJSLVNode = {
28
+ tag: "section", attr: { id: "dropdown-menus", class: "component-section", "aria-labelledby": "dropdown-menus-title" }, children: [
29
+ { tag: "div", attr: { class: "hero" }, children: [
30
+ { tag: "div", attr: { class: "hero-copy" }, children: [
31
+ text("p", "THE ESSENTIALS / 23", "eyebrow"),
32
+ { tag: "h2", content: "Dropdown menus", attr: { id: "dropdown-menus-title", class: "section-title" } },
33
+ text("p", "A button opens a compact list of actions, positioned where the button lives.", "hero-description")
34
+ ] },
35
+ { tag: "div", attr: { class: "hero-preview" }, children: [
36
+ text("span", "INTERACTIVE PREVIEW", "preview-caption"),
37
+ new DropdownMenuComponent({
38
+ content: "Project actions", class: "jsl-button jsl-button-outline", menuLabel: "Project actions",
39
+ children: [
40
+ action("Edit project", () => updateMessage(previewStatus, "Edit project selected.")),
41
+ action("Duplicate project", () => updateMessage(previewStatus, "Project duplicated.")),
42
+ action("Archive project", () => updateMessage(previewStatus, "Project archived."))
43
+ ],
44
+ closeOnClick: true
45
+ }),
46
+ text("span", "Use Arrow Down to open and explore the menu.", "preview-note")
47
+ ] }
48
+ ] },
49
+ { tag: "div", attr: { class: "overview-bar" }, children: [
50
+ text("span", "Trigger-anchored", "overview-label"),
51
+ text("span", "Keyboard accessible", "overview-stat"),
52
+ text("span", "Dynamic items", "overview-stat")
53
+ ] },
54
+ { tag: "div", attr: { class: "sample-grid" }, children: [
55
+ card("dropdown-actions", "01", "Choose an action", "Menu items can be buttons or links. closeOnClick closes after selection and returns focus to the trigger.", [
56
+ group([new DropdownMenuComponent({
57
+ content: "More actions", class: "jsl-button jsl-button-outline", menuLabel: "More actions",
58
+ closeOnClick: true, placement: "bottom-start", width: "13rem",
59
+ children: [
60
+ action("Share", () => updateMessage(previewStatus, "Share selected.")),
61
+ action("Copy link", () => updateMessage(previewStatus, "Link copied.")),
62
+ action("Delete", () => updateMessage(previewStatus, "Delete selected."))
63
+ ]
64
+ }), previewStatus])
65
+ ]),
66
+ card("dropdown-dynamic", "02", "Refresh items when opened", "Use onOpen to build a menu from current application data. Return false from the callback to veto opening.", [
67
+ group([new DropdownMenuComponent({
68
+ content: "Recent reports", class: "jsl-button jsl-button-outline", menuLabel: "Recent reports",
69
+ children: [], closeOnClick: true,
70
+ onOpen: options => {
71
+ openCount++;
72
+ options.children = [
73
+ action("Report " + openCount, () => updateMessage(dynamicStatus, "Report " + openCount + " opened.")),
74
+ action("Refresh list", () => updateMessage(dynamicStatus, "Report list refreshed."))
75
+ ];
76
+ }
77
+ }), dynamicStatus])
78
+ ]),
79
+ card("dropdown-placement", "03", "Placement and disabled state", "The menu follows its trigger and adjusts near viewport edges. Custom trigger tags remain available for migration.", [
80
+ group([
81
+ new DropdownMenuComponent({
82
+ content: "Top aligned", class: "jsl-button jsl-button-outline", menuLabel: "Top aligned actions",
83
+ placement: "top-end", offsetY: -4, children: [action("First action", () => updateMessage(dynamicStatus, "First action selected."))]
84
+ }),
85
+ new DropdownMenuComponent({
86
+ content: "Unavailable", class: "jsl-button jsl-button-outline", disabled: true,
87
+ children: [action("Hidden action", () => undefined)]
88
+ })
89
+ ])
90
+ ])
91
+ ] },
92
+ { tag: "details", attr: { class: "usage" }, children: [
93
+ text("summary", "Dropdown menu usage & compatibility"),
94
+ text("pre", 'new DropdownMenuComponent({\n content: "Actions", class: "jsl-button jsl-button-outline",\n children: [new ButtonComponent({ content: "Edit", click: edit })],\n closeOnClick: true,\n onOpen: options => { options.children = buildCurrentItems(); }\n});', "sample-code"),
95
+ text("p", "The component retains the existing content, rawContent, children, tag, class, style, width, offsetX, disabled, title, closeOnClick, maxHeight, maxWidth, height, onOpen, and close() names. Menus are anchored to the trigger, flip and clamp to the viewport, and stay open by default to support multi-select actions."),
96
+ text("p", "Arrow Up/Down opens the menu and places focus on the first or last enabled item. Within the menu, Arrow keys, Home, End, type-ahead, Escape, and Tab are supported. Native button and anchor children receive menu item semantics; custom items can opt in with data-menu-item.")
97
+ ] }
98
+ ]
99
+ };
@@ -11,7 +11,7 @@
11
11
  <a class="skip-link" href="#main">Skip to components</a>
12
12
  <aside class="sidebar" aria-label="Component navigation">
13
13
  <a class="brand" href="#main"><span class="brand-mark">j.</span><span>JSL UI<span class="brand-caption">Component showcase</span></span></a>
14
- <p class="nav-label">COMPONENTS <span>22</span></p>
14
+ <p class="nav-label">COMPONENTS <span>23</span></p>
15
15
  <nav>
16
16
  <a class="nav-item" href="#buttons" aria-current="location"><span class="nav-icon" aria-hidden="true">▣</span> Buttons <span class="nav-number">01</span></a>
17
17
  <a class="nav-item" href="#dialogs"><span class="nav-icon" aria-hidden="true">▢</span> Dialogs <span class="nav-number">02</span></a>
@@ -35,6 +35,7 @@
35
35
  <a class="nav-item" href="#overlays"><span class="nav-icon" aria-hidden="true">&#9703;</span> Overlays <span class="nav-number">20</span></a>
36
36
  <a class="nav-item" href="#ribbons"><span class="nav-icon" aria-hidden="true">&#9637;</span> Ribbon menus <span class="nav-number">21</span></a>
37
37
  <a class="nav-item" href="#grids"><span class="nav-icon" aria-hidden="true">&#9638;</span> Grids <span class="nav-number">22</span></a>
38
+ <a class="nav-item" href="#dropdown-menus"><span class="nav-icon" aria-hidden="true">&#8942;</span> Dropdown menus <span class="nav-number">23</span></a>
38
39
  </nav>
39
40
  <div class="sidebar-note"><span class="status-dot"></span> A growing collection<p>Redesigned components will be added here as the library evolves.</p></div>
40
41
  </aside>
@@ -10,6 +10,7 @@ import { inputShowcase, numberShowcase, formFieldShowcase } from "./fields";
10
10
  import { notificationShowcase, loadingShowcase, navigationShowcase } from "./feedback-and-navigation";
11
11
  import { progressShowcase, stackShowcase, treeShowcase, overlayShowcase, ribbonShowcase } from "./layout-and-navigation";
12
12
  import { sliderShowcase, dateShowcase, dateRangeShowcase, dateTimeShowcase } from "./dates";
13
+ import { dropdownMenuShowcase } from "./dropdown-menus";
13
14
  import { IJSLComponent, IJSLVNode, JSLRender } from "jsl-render";
14
15
  import { ButtonComponent, IButtonOptions } from "../../components/button/ButtonComponent";
15
16
 
@@ -190,7 +191,7 @@ new JSLRender(document.getElementById("showcase"), true).render({
190
191
  group([node("span", "Overview", "overview-label"), node("span", "3 variants", "overview-stat"), node("span", "3 sizes", "overview-stat"), node("span", "Keyboard accessible", "overview-stat")], "overview-bar"),
191
192
  group(cards, "sample-grid"), usage
192
193
  ]
193
- }, dialogShowcase, tabsShowcase, sliderShowcase, dateShowcase, dateRangeShowcase, dateTimeShowcase, selectShowcase, checkboxShowcase, expanderShowcase, inputShowcase, numberShowcase, formFieldShowcase, notificationShowcase, loadingShowcase, navigationShowcase, progressShowcase, stackShowcase, treeShowcase, overlayShowcase, ribbonShowcase, gridShowcase]
194
+ }, dialogShowcase, tabsShowcase, sliderShowcase, dateShowcase, dateRangeShowcase, dateTimeShowcase, selectShowcase, checkboxShowcase, expanderShowcase, inputShowcase, numberShowcase, formFieldShowcase, notificationShowcase, loadingShowcase, navigationShowcase, progressShowcase, stackShowcase, treeShowcase, overlayShowcase, ribbonShowcase, gridShowcase, dropdownMenuShowcase]
194
195
  });
195
196
 
196
197
  const navigation = Array.from(document.querySelectorAll<HTMLAnchorElement>(".nav-item"));
@@ -0,0 +1,70 @@
1
+ import "./DropdownMenuStyle.scss";
2
+ import { IJSLComponent, IJSLVNode, JSLRender } from "jsl-render";
3
+ export interface IDropdownMenuComponentOptions {
4
+ children: Array<IJSLVNode | IJSLComponent>;
5
+ content: string | IJSLVNode;
6
+ rawContent?: boolean;
7
+ class?: string;
8
+ style?: string;
9
+ tag?: string;
10
+ width?: string;
11
+ offsetX?: number;
12
+ offsetY?: number;
13
+ disabled?: boolean;
14
+ title?: string;
15
+ ariaLabel?: string;
16
+ menuLabel?: string;
17
+ showIndicator?: boolean;
18
+ closeOnClick?: boolean;
19
+ maxHeight?: string;
20
+ maxWidth?: string;
21
+ height?: string;
22
+ placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
23
+ menuClass?: string;
24
+ menuStyle?: string;
25
+ type?: "button" | "submit" | "reset";
26
+ onOpen?: (options: IDropdownMenuComponentOptions) => void | boolean;
27
+ }
28
+ interface IDropdownMenuState {
29
+ id: string;
30
+ open: boolean;
31
+ trigger?: HTMLElement;
32
+ portal?: HTMLElement;
33
+ menu?: HTMLElement;
34
+ renderer?: JSLRender;
35
+ owner?: DropdownMenuComponent;
36
+ cleanup?: () => void;
37
+ typeahead?: string;
38
+ typeaheadTimer?: number;
39
+ }
40
+ type InitialFocus = "first" | "last" | "none";
41
+ /** A button-like trigger that opens an anchored, keyboard-accessible menu. */
42
+ export declare class DropdownMenuComponent implements IJSLComponent {
43
+ State: IDropdownMenuState;
44
+ private options;
45
+ constructor(options: IDropdownMenuComponentOptions);
46
+ isEqual(node: IJSLComponent): boolean;
47
+ render(): IJSLVNode;
48
+ onCreate(node: IJSLVNode): void;
49
+ onUpdate(node: IJSLVNode): void;
50
+ onRemove(): void;
51
+ /** Open the menu and move focus to its first or last enabled item. */
52
+ open(initialFocus?: InitialFocus): void;
53
+ toggle(): void;
54
+ /** Close the menu and restore focus to its trigger. */
55
+ close(): void;
56
+ private menuNode;
57
+ private onTriggerClick;
58
+ private onTriggerKeydown;
59
+ private onMenuKeydown;
60
+ private prepareMenuItems;
61
+ private menuItems;
62
+ private focusMenuItem;
63
+ private typeahead;
64
+ private moveFocusFromTrigger;
65
+ private positionMenu;
66
+ private inheritTheme;
67
+ private syncTrigger;
68
+ private closeInternal;
69
+ }
70
+ export { DropdownMenuComponent as DropdownMenuButtonComponent };