@tylertech/forge-ai 0.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.
- package/LICENSE +202 -0
- package/README.md +3 -0
- package/custom-elements.json +4963 -0
- package/dist/ai-actions-toolbar/ai-actions-toolbar.d.ts +25 -0
- package/dist/ai-actions-toolbar/ai-actions-toolbar.mjs +74 -0
- package/dist/ai-actions-toolbar/ai-actions-toolbar.scss.mjs +4 -0
- package/dist/ai-actions-toolbar/index.d.ts +1 -0
- package/dist/ai-actions-toolbar/index.mjs +5 -0
- package/dist/ai-artifact/ai-artifact.d.ts +20 -0
- package/dist/ai-artifact/ai-artifact.mjs +39 -0
- package/dist/ai-artifact/ai-artifact.scss.mjs +4 -0
- package/dist/ai-artifact/index.d.ts +1 -0
- package/dist/ai-artifact/index.mjs +5 -0
- package/dist/ai-button/ai-button.d.ts +21 -0
- package/dist/ai-button/ai-button.mjs +67 -0
- package/dist/ai-button/ai-button.scss.mjs +4 -0
- package/dist/ai-button/index.d.ts +1 -0
- package/dist/ai-button/index.mjs +5 -0
- package/dist/ai-chain-of-thought/ai-chain-of-thought.d.ts +17 -0
- package/dist/ai-chain-of-thought/ai-chain-of-thought.mjs +45 -0
- package/dist/ai-chain-of-thought/ai-chain-of-thought.scss.mjs +4 -0
- package/dist/ai-chain-of-thought/index.d.ts +5 -0
- package/dist/ai-chain-of-thought/index.mjs +17 -0
- package/dist/ai-chain-of-thought/thought-base/index.d.ts +1 -0
- package/dist/ai-chain-of-thought/thought-base/index.mjs +5 -0
- package/dist/ai-chain-of-thought/thought-base/thought-base.d.ts +20 -0
- package/dist/ai-chain-of-thought/thought-base/thought-base.mjs +43 -0
- package/dist/ai-chain-of-thought/thought-base/thought-base.scss.mjs +4 -0
- package/dist/ai-chain-of-thought/thought-detail/index.d.ts +1 -0
- package/dist/ai-chain-of-thought/thought-detail/index.mjs +5 -0
- package/dist/ai-chain-of-thought/thought-detail/thought-detail.d.ts +14 -0
- package/dist/ai-chain-of-thought/thought-detail/thought-detail.mjs +34 -0
- package/dist/ai-chain-of-thought/thought-detail/thought-detail.scss.mjs +4 -0
- package/dist/ai-chain-of-thought/thought-image/index.d.ts +1 -0
- package/dist/ai-chain-of-thought/thought-image/index.mjs +5 -0
- package/dist/ai-chain-of-thought/thought-image/thought-image.d.ts +16 -0
- package/dist/ai-chain-of-thought/thought-image/thought-image.mjs +43 -0
- package/dist/ai-chain-of-thought/thought-image/thought-image.scss.mjs +4 -0
- package/dist/ai-chain-of-thought/thought-search-result/index.d.ts +1 -0
- package/dist/ai-chain-of-thought/thought-search-result/index.mjs +5 -0
- package/dist/ai-chain-of-thought/thought-search-result/thought-search-result.d.ts +22 -0
- package/dist/ai-chain-of-thought/thought-search-result/thought-search-result.mjs +62 -0
- package/dist/ai-chain-of-thought/thought-search-result/thought-search-result.scss.mjs +4 -0
- package/dist/ai-chat-header/ai-chat-header.d.ts +54 -0
- package/dist/ai-chat-header/ai-chat-header.mjs +198 -0
- package/dist/ai-chat-header/ai-chat-header.scss.mjs +4 -0
- package/dist/ai-chat-header/index.d.ts +1 -0
- package/dist/ai-chat-header/index.mjs +5 -0
- package/dist/ai-chat-interface/ai-chat-interface.d.ts +38 -0
- package/dist/ai-chat-interface/ai-chat-interface.mjs +117 -0
- package/dist/ai-chat-interface/ai-chat-interface.scss.mjs +4 -0
- package/dist/ai-chat-interface/index.d.ts +1 -0
- package/dist/ai-chat-interface/index.mjs +5 -0
- package/dist/ai-dialog/ai-dialog.d.ts +56 -0
- package/dist/ai-dialog/ai-dialog.mjs +270 -0
- package/dist/ai-dialog/ai-dialog.scss.mjs +4 -0
- package/dist/ai-dialog/index.d.ts +1 -0
- package/dist/ai-dialog/index.mjs +5 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-item-group.d.ts +26 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-item-group.mjs +80 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-item-group.scss.mjs +4 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-item.d.ts +133 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-item.mjs +335 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-item.scss.mjs +4 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-separator.d.ts +18 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-separator.mjs +26 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu-separator.scss.mjs +4 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu.d.ts +143 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu.mjs +327 -0
- package/dist/ai-dropdown-menu/ai-dropdown-menu.scss.mjs +4 -0
- package/dist/ai-dropdown-menu/index.d.ts +4 -0
- package/dist/ai-dropdown-menu/index.mjs +10 -0
- package/dist/ai-dropdown-menu/navigation-controller.d.ts +79 -0
- package/dist/ai-dropdown-menu/navigation-controller.mjs +205 -0
- package/dist/ai-dropdown-menu/selection-manager.d.ts +145 -0
- package/dist/ai-dropdown-menu/selection-manager.mjs +183 -0
- package/dist/ai-embedded-chat/ai-embedded-chat.d.ts +47 -0
- package/dist/ai-embedded-chat/ai-embedded-chat.mjs +139 -0
- package/dist/ai-embedded-chat/ai-embedded-chat.scss.mjs +4 -0
- package/dist/ai-embedded-chat/index.d.ts +1 -0
- package/dist/ai-embedded-chat/index.mjs +5 -0
- package/dist/ai-empty-state/ai-empty-state.d.ts +19 -0
- package/dist/ai-empty-state/ai-empty-state.mjs +136 -0
- package/dist/ai-empty-state/ai-empty-state.scss.mjs +4 -0
- package/dist/ai-empty-state/index.d.ts +1 -0
- package/dist/ai-empty-state/index.mjs +5 -0
- package/dist/ai-fab/ai-fab.d.ts +23 -0
- package/dist/ai-fab/ai-fab.mjs +75 -0
- package/dist/ai-fab/ai-fab.scss.mjs +4 -0
- package/dist/ai-fab/index.d.ts +1 -0
- package/dist/ai-fab/index.mjs +5 -0
- package/dist/ai-file-picker/ai-file-picker.d.ts +77 -0
- package/dist/ai-file-picker/ai-file-picker.mjs +176 -0
- package/dist/ai-file-picker/ai-file-picker.scss.mjs +4 -0
- package/dist/ai-file-picker/index.d.ts +1 -0
- package/dist/ai-file-picker/index.mjs +4 -0
- package/dist/ai-floating-chat/ai-floating-chat.d.ts +65 -0
- package/dist/ai-floating-chat/ai-floating-chat.mjs +153 -0
- package/dist/ai-floating-chat/ai-floating-chat.scss.mjs +4 -0
- package/dist/ai-floating-chat/index.d.ts +1 -0
- package/dist/ai-floating-chat/index.mjs +5 -0
- package/dist/ai-gradient-container/ai-gradient-container.d.ts +26 -0
- package/dist/ai-gradient-container/ai-gradient-container.mjs +61 -0
- package/dist/ai-gradient-container/ai-gradient-container.scss.mjs +4 -0
- package/dist/ai-gradient-container/index.d.ts +1 -0
- package/dist/ai-gradient-container/index.mjs +5 -0
- package/dist/ai-icon/ai-icon.d.ts +22 -0
- package/dist/ai-icon/ai-icon.mjs +71 -0
- package/dist/ai-icon/ai-icon.scss.mjs +4 -0
- package/dist/ai-icon/index.d.ts +1 -0
- package/dist/ai-icon/index.mjs +5 -0
- package/dist/ai-modal/ai-modal.d.ts +49 -0
- package/dist/ai-modal/ai-modal.mjs +132 -0
- package/dist/ai-modal/ai-modal.scss.mjs +4 -0
- package/dist/ai-modal/index.d.ts +1 -0
- package/dist/ai-modal/index.mjs +4 -0
- package/dist/ai-prompt/ai-prompt.d.ts +42 -0
- package/dist/ai-prompt/ai-prompt.mjs +123 -0
- package/dist/ai-prompt/ai-prompt.scss.mjs +4 -0
- package/dist/ai-prompt/index.d.ts +1 -0
- package/dist/ai-prompt/index.mjs +5 -0
- package/dist/ai-prompt/prompt-button/index.d.ts +1 -0
- package/dist/ai-prompt/prompt-button/index.mjs +5 -0
- package/dist/ai-prompt/prompt-button/prompt-button.d.ts +16 -0
- package/dist/ai-prompt/prompt-button/prompt-button.mjs +40 -0
- package/dist/ai-prompt/prompt-button/prompt-button.scss.mjs +4 -0
- package/dist/ai-reasoning/ai-reasoning.d.ts +17 -0
- package/dist/ai-reasoning/ai-reasoning.mjs +44 -0
- package/dist/ai-reasoning/ai-reasoning.scss.mjs +4 -0
- package/dist/ai-reasoning/index.d.ts +2 -0
- package/dist/ai-reasoning/index.mjs +8 -0
- package/dist/ai-reasoning/reasoning-content/index.d.ts +1 -0
- package/dist/ai-reasoning/reasoning-content/index.mjs +5 -0
- package/dist/ai-reasoning/reasoning-content/reasoning-content.d.ts +22 -0
- package/dist/ai-reasoning/reasoning-content/reasoning-content.mjs +90 -0
- package/dist/ai-reasoning/reasoning-content/reasoning-content.scss.mjs +4 -0
- package/dist/ai-reasoning-header/ai-reasoning-header.d.ts +24 -0
- package/dist/ai-reasoning-header/ai-reasoning-header.mjs +68 -0
- package/dist/ai-reasoning-header/ai-reasoning-header.scss.mjs +4 -0
- package/dist/ai-reasoning-header/index.d.ts +1 -0
- package/dist/ai-reasoning-header/index.mjs +5 -0
- package/dist/ai-response-message/ai-response-message.d.ts +40 -0
- package/dist/ai-response-message/ai-response-message.mjs +137 -0
- package/dist/ai-response-message/ai-response-message.scss.mjs +4 -0
- package/dist/ai-response-message/index.d.ts +1 -0
- package/dist/ai-response-message/index.mjs +5 -0
- package/dist/ai-sidebar/ai-sidebar.d.ts +44 -0
- package/dist/ai-sidebar/ai-sidebar.mjs +105 -0
- package/dist/ai-sidebar/ai-sidebar.scss.mjs +4 -0
- package/dist/ai-sidebar/index.d.ts +1 -0
- package/dist/ai-sidebar/index.mjs +5 -0
- package/dist/ai-sidebar-chat/ai-sidebar-chat.d.ts +64 -0
- package/dist/ai-sidebar-chat/ai-sidebar-chat.mjs +170 -0
- package/dist/ai-sidebar-chat/ai-sidebar-chat.scss.mjs +4 -0
- package/dist/ai-sidebar-chat/index.d.ts +1 -0
- package/dist/ai-sidebar-chat/index.mjs +5 -0
- package/dist/ai-suggestions/ai-suggestions.d.ts +39 -0
- package/dist/ai-suggestions/ai-suggestions.mjs +96 -0
- package/dist/ai-suggestions/ai-suggestions.scss.mjs +4 -0
- package/dist/ai-suggestions/index.d.ts +1 -0
- package/dist/ai-suggestions/index.mjs +5 -0
- package/dist/ai-threads/ai-threads.d.ts +48 -0
- package/dist/ai-threads/ai-threads.mjs +203 -0
- package/dist/ai-threads/ai-threads.scss.mjs +4 -0
- package/dist/ai-threads/index.d.ts +1 -0
- package/dist/ai-threads/index.mjs +5 -0
- package/dist/ai-user-message/ai-user-message.d.ts +15 -0
- package/dist/ai-user-message/ai-user-message.mjs +43 -0
- package/dist/ai-user-message/ai-user-message.scss.mjs +4 -0
- package/dist/ai-user-message/index.d.ts +1 -0
- package/dist/ai-user-message/index.mjs +5 -0
- package/dist/ai-voice-input/ai-voice-input.d.ts +68 -0
- package/dist/ai-voice-input/ai-voice-input.mjs +107 -0
- package/dist/ai-voice-input/ai-voice-input.scss.mjs +4 -0
- package/dist/ai-voice-input/index.d.ts +1 -0
- package/dist/ai-voice-input/index.mjs +5 -0
- package/dist/core/drag-controller.d.ts +66 -0
- package/dist/core/drag-controller.mjs +219 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.mjs +4 -0
- package/dist/core/overlay/index.d.ts +1 -0
- package/dist/core/overlay/index.mjs +4 -0
- package/dist/core/overlay/overlay.d.ts +61 -0
- package/dist/core/overlay/overlay.mjs +142 -0
- package/dist/core/overlay/overlay.scss.mjs +4 -0
- package/dist/core/popover/index.d.ts +1 -0
- package/dist/core/popover/index.mjs +4 -0
- package/dist/core/popover/popover.d.ts +56 -0
- package/dist/core/popover/popover.mjs +71 -0
- package/dist/core/popover/popover.scss.mjs +4 -0
- package/dist/core/tooltip/index.d.ts +1 -0
- package/dist/core/tooltip/index.mjs +4 -0
- package/dist/core/tooltip/tooltip.d.ts +91 -0
- package/dist/core/tooltip/tooltip.mjs +243 -0
- package/dist/core/tooltip/tooltip.scss.mjs +4 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.mjs +98 -0
- package/dist/utils.d.ts +14 -0
- package/dist/utils.mjs +22 -0
- package/package.json +122 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ForgeAiDropdownMenuItemComponent } from './ai-dropdown-menu-item.js';
|
|
2
|
+
/**
|
|
3
|
+
* Controller class to handle keyboard navigation for dropdown menus.
|
|
4
|
+
*
|
|
5
|
+
* This controller manages all aspects of keyboard navigation within dropdown menus,
|
|
6
|
+
* including focus management, selection state, and keyboard event delegation.
|
|
7
|
+
* It supports both parent dropdown menus and nested submenus with proper
|
|
8
|
+
* navigation isolation and event bubbling control.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const controller = new DropdownNavigationController(
|
|
13
|
+
* hostElement,
|
|
14
|
+
* () => getMenuItems(),
|
|
15
|
+
* (index) => selectItem(index),
|
|
16
|
+
* () => closeDropdown(),
|
|
17
|
+
* () => openDropdown(),
|
|
18
|
+
* () => isSubmenu()
|
|
19
|
+
* );
|
|
20
|
+
*
|
|
21
|
+
* // Handle keyboard events
|
|
22
|
+
* controller.handleKeyDown(keyboardEvent, isOpen, triggerButton);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare class DropdownNavigationController {
|
|
26
|
+
private _host;
|
|
27
|
+
private _getMenuItems;
|
|
28
|
+
private _onItemSelect;
|
|
29
|
+
private _onClose;
|
|
30
|
+
private _onOpen;
|
|
31
|
+
private _isSubmenu;
|
|
32
|
+
private _selectedIndex;
|
|
33
|
+
private _openedViaKeyboard;
|
|
34
|
+
constructor(_host: HTMLElement, _getMenuItems: () => ForgeAiDropdownMenuItemComponent[], _onItemSelect: (index: number) => void, _onClose: () => void, _onOpen: () => void, _isSubmenu: () => boolean);
|
|
35
|
+
/**
|
|
36
|
+
* Gets the current selected index
|
|
37
|
+
*/
|
|
38
|
+
get selectedIndex(): number;
|
|
39
|
+
/**
|
|
40
|
+
* Sets the selected index
|
|
41
|
+
*/
|
|
42
|
+
set selectedIndex(value: number);
|
|
43
|
+
/**
|
|
44
|
+
* Gets whether the dropdown was opened via keyboard
|
|
45
|
+
*/
|
|
46
|
+
get openedViaKeyboard(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Sets whether the dropdown was opened via keyboard
|
|
49
|
+
*/
|
|
50
|
+
set openedViaKeyboard(value: boolean);
|
|
51
|
+
/**
|
|
52
|
+
* Handles keyboard events for the dropdown menu
|
|
53
|
+
*/
|
|
54
|
+
handleKeyDown(event: KeyboardEvent, isOpen: boolean, triggerButton: HTMLElement): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Moves the selection by the specified direction
|
|
57
|
+
*/
|
|
58
|
+
private _moveSelection;
|
|
59
|
+
/**
|
|
60
|
+
* Updates the focus state of menu items
|
|
61
|
+
*/
|
|
62
|
+
private _updateItemFocus;
|
|
63
|
+
/**
|
|
64
|
+
* Focuses the first item in the menu
|
|
65
|
+
*/
|
|
66
|
+
focusFirstItem(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Clears focus from all menu items
|
|
69
|
+
*/
|
|
70
|
+
clearItemFocus(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Resets the navigation state when dropdown opens
|
|
73
|
+
*/
|
|
74
|
+
onOpen(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Resets the navigation state
|
|
77
|
+
*/
|
|
78
|
+
reset(): void;
|
|
79
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
class DropdownNavigationController {
|
|
2
|
+
constructor(_host, _getMenuItems, _onItemSelect, _onClose, _onOpen, _isSubmenu) {
|
|
3
|
+
this._host = _host;
|
|
4
|
+
this._getMenuItems = _getMenuItems;
|
|
5
|
+
this._onItemSelect = _onItemSelect;
|
|
6
|
+
this._onClose = _onClose;
|
|
7
|
+
this._onOpen = _onOpen;
|
|
8
|
+
this._isSubmenu = _isSubmenu;
|
|
9
|
+
this._selectedIndex = -1;
|
|
10
|
+
this._openedViaKeyboard = false;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Gets the current selected index
|
|
14
|
+
*/
|
|
15
|
+
get selectedIndex() {
|
|
16
|
+
return this._selectedIndex;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Sets the selected index
|
|
20
|
+
*/
|
|
21
|
+
set selectedIndex(value) {
|
|
22
|
+
this._selectedIndex = value;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Gets whether the dropdown was opened via keyboard
|
|
26
|
+
*/
|
|
27
|
+
get openedViaKeyboard() {
|
|
28
|
+
return this._openedViaKeyboard;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Sets whether the dropdown was opened via keyboard
|
|
32
|
+
*/
|
|
33
|
+
set openedViaKeyboard(value) {
|
|
34
|
+
this._openedViaKeyboard = value;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Handles keyboard events for the dropdown menu
|
|
38
|
+
*/
|
|
39
|
+
handleKeyDown(event, isOpen, triggerButton) {
|
|
40
|
+
const target = event.target;
|
|
41
|
+
if (target && target !== this._host) {
|
|
42
|
+
const nestedDropdown = target.closest('forge-ai-dropdown-menu[data-submenu="true"]');
|
|
43
|
+
if (nestedDropdown && nestedDropdown !== this._host) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
switch (event.key) {
|
|
48
|
+
case "Enter":
|
|
49
|
+
case " ":
|
|
50
|
+
if (event.target === triggerButton) {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
this._onOpen();
|
|
53
|
+
return true;
|
|
54
|
+
} else if (isOpen && this._selectedIndex >= 0) {
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
this._onItemSelect(this._selectedIndex);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case "Escape":
|
|
61
|
+
if (isOpen) {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
this._onClose();
|
|
64
|
+
triggerButton.focus();
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case "ArrowDown":
|
|
69
|
+
if (isOpen) {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
this._moveSelection(1);
|
|
72
|
+
return true;
|
|
73
|
+
} else if (event.target === triggerButton) {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
this._openedViaKeyboard = true;
|
|
76
|
+
this._onOpen();
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
case "ArrowUp":
|
|
81
|
+
if (isOpen) {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
this._moveSelection(-1);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
case "ArrowRight":
|
|
88
|
+
if (isOpen && this._selectedIndex >= 0) {
|
|
89
|
+
const items = this._getMenuItems();
|
|
90
|
+
const selectedItem = items[this._selectedIndex];
|
|
91
|
+
if (selectedItem) {
|
|
92
|
+
selectedItem.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowRight", bubbles: false }));
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case "ArrowLeft":
|
|
98
|
+
if (isOpen && this._isSubmenu()) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
this._onClose();
|
|
101
|
+
const parentItem = this._host.parentElement;
|
|
102
|
+
if (parentItem && parentItem.tagName.toLowerCase() === "forge-ai-dropdown-menu-item") {
|
|
103
|
+
parentItem.focus();
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case "Home":
|
|
109
|
+
if (isOpen) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
this._selectedIndex = 0;
|
|
112
|
+
this._updateItemFocus();
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
case "End":
|
|
117
|
+
if (isOpen) {
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
this._selectedIndex = Math.max(0, this._getMenuItems().length - 1);
|
|
120
|
+
this._updateItemFocus();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Moves the selection by the specified direction
|
|
129
|
+
*/
|
|
130
|
+
_moveSelection(direction) {
|
|
131
|
+
const items = this._getMenuItems();
|
|
132
|
+
if (items.length === 0) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (this._selectedIndex === -1) {
|
|
136
|
+
if (direction > 0) {
|
|
137
|
+
this._selectedIndex = 0;
|
|
138
|
+
} else {
|
|
139
|
+
this._selectedIndex = items.length - 1;
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
let newIndex = this._selectedIndex + direction;
|
|
143
|
+
if (newIndex < 0) {
|
|
144
|
+
newIndex = items.length - 1;
|
|
145
|
+
} else if (newIndex >= items.length) {
|
|
146
|
+
newIndex = 0;
|
|
147
|
+
}
|
|
148
|
+
this._selectedIndex = newIndex;
|
|
149
|
+
}
|
|
150
|
+
this._updateItemFocus();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Updates the focus state of menu items
|
|
154
|
+
*/
|
|
155
|
+
_updateItemFocus() {
|
|
156
|
+
const items = this._getMenuItems();
|
|
157
|
+
items.forEach((item, index) => {
|
|
158
|
+
item.setAttribute("tabindex", index === this._selectedIndex ? "0" : "-1");
|
|
159
|
+
if (index === this._selectedIndex) {
|
|
160
|
+
item.focus();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Focuses the first item in the menu
|
|
166
|
+
*/
|
|
167
|
+
focusFirstItem() {
|
|
168
|
+
const items = this._getMenuItems();
|
|
169
|
+
if (items.length > 0) {
|
|
170
|
+
this._selectedIndex = 0;
|
|
171
|
+
this._updateItemFocus();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Clears focus from all menu items
|
|
176
|
+
*/
|
|
177
|
+
clearItemFocus() {
|
|
178
|
+
const items = this._getMenuItems();
|
|
179
|
+
items.forEach((item) => {
|
|
180
|
+
item.setAttribute("tabindex", "-1");
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Resets the navigation state when dropdown opens
|
|
185
|
+
*/
|
|
186
|
+
onOpen() {
|
|
187
|
+
this._selectedIndex = -1;
|
|
188
|
+
if (this._openedViaKeyboard) {
|
|
189
|
+
this.focusFirstItem();
|
|
190
|
+
this._openedViaKeyboard = false;
|
|
191
|
+
} else {
|
|
192
|
+
this.clearItemFocus();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Resets the navigation state
|
|
197
|
+
*/
|
|
198
|
+
reset() {
|
|
199
|
+
this._selectedIndex = -1;
|
|
200
|
+
this._openedViaKeyboard = false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
DropdownNavigationController
|
|
205
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { ForgeAiDropdownMenuItemComponent } from './ai-dropdown-menu-item.js';
|
|
2
|
+
export type DropdownMenuSelectionMode = 'none' | 'single' | 'multi';
|
|
3
|
+
/**
|
|
4
|
+
* Base interface for dropdown menu selection change events
|
|
5
|
+
*/
|
|
6
|
+
export interface BaseDropdownChangeEventDetail {
|
|
7
|
+
selectionMode: DropdownMenuSelectionMode;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Event detail for 'none' selection mode (actions only)
|
|
12
|
+
*/
|
|
13
|
+
export interface ActionEventDetail extends BaseDropdownChangeEventDetail {
|
|
14
|
+
selectionMode: 'none';
|
|
15
|
+
value: string;
|
|
16
|
+
selectedItem: ForgeAiDropdownMenuItemComponent;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Event detail for 'single' selection mode
|
|
20
|
+
*/
|
|
21
|
+
export interface SingleSelectionEventDetail extends BaseDropdownChangeEventDetail {
|
|
22
|
+
selectionMode: 'single';
|
|
23
|
+
value: string | null;
|
|
24
|
+
selectedItem: ForgeAiDropdownMenuItemComponent | null;
|
|
25
|
+
previousValue?: string | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Event detail for 'multi' selection mode
|
|
29
|
+
*/
|
|
30
|
+
export interface MultiSelectionEventDetail extends BaseDropdownChangeEventDetail {
|
|
31
|
+
selectionMode: 'multi';
|
|
32
|
+
value: string[];
|
|
33
|
+
selectedItem: ForgeAiDropdownMenuItemComponent[];
|
|
34
|
+
addedItems?: ForgeAiDropdownMenuItemComponent[];
|
|
35
|
+
removedItems?: ForgeAiDropdownMenuItemComponent[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Union type for all dropdown change event details
|
|
39
|
+
*/
|
|
40
|
+
export type DropdownChangeEventDetail = ActionEventDetail | SingleSelectionEventDetail | MultiSelectionEventDetail;
|
|
41
|
+
/**
|
|
42
|
+
* Configuration options for selection behavior
|
|
43
|
+
*/
|
|
44
|
+
export interface SelectionConfig {
|
|
45
|
+
mode: DropdownMenuSelectionMode;
|
|
46
|
+
closeOnSingleSelect?: boolean;
|
|
47
|
+
showSelectionCount?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Manages selection state and behavior for dropdown menus.
|
|
51
|
+
*
|
|
52
|
+
* The SelectionManager is responsible for handling all aspects of item selection
|
|
53
|
+
* within dropdown menus, supporting three distinct modes:
|
|
54
|
+
*
|
|
55
|
+
* - **none**: Items act as actions only, no persistent selection state
|
|
56
|
+
* - **single**: Single selection with radio button behavior
|
|
57
|
+
* - **multi**: Multiple selection with checkbox behavior
|
|
58
|
+
*
|
|
59
|
+
* It provides type-safe event dispatching with mode-specific event details
|
|
60
|
+
* and maintains consistent selection state across all menu items.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const manager = new SelectionManager(
|
|
65
|
+
* () => getAllMenuItems(),
|
|
66
|
+
* (detail) => handleSelectionChange(detail),
|
|
67
|
+
* { mode: 'single', closeOnSingleSelect: true }
|
|
68
|
+
* );
|
|
69
|
+
*
|
|
70
|
+
* // Handle item selection
|
|
71
|
+
* const result = manager.selectItem(menuItem);
|
|
72
|
+
* if (result.shouldClose) {
|
|
73
|
+
* closeDropdown();
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export declare class SelectionManager {
|
|
78
|
+
private _getMenuItems;
|
|
79
|
+
private _onSelectionChange;
|
|
80
|
+
private _value;
|
|
81
|
+
private _config;
|
|
82
|
+
constructor(_getMenuItems: () => ForgeAiDropdownMenuItemComponent[], _onSelectionChange: (detail: DropdownChangeEventDetail) => void, config: SelectionConfig);
|
|
83
|
+
/**
|
|
84
|
+
* Gets the current selection value
|
|
85
|
+
*/
|
|
86
|
+
get value(): string | string[] | null;
|
|
87
|
+
/**
|
|
88
|
+
* Sets the selection value and updates item states
|
|
89
|
+
*/
|
|
90
|
+
set value(newValue: string | string[] | null);
|
|
91
|
+
/**
|
|
92
|
+
* Gets the current selection mode
|
|
93
|
+
*/
|
|
94
|
+
get selectionMode(): DropdownMenuSelectionMode;
|
|
95
|
+
/**
|
|
96
|
+
* Updates the selection mode and reconfigures items
|
|
97
|
+
*/
|
|
98
|
+
set selectionMode(mode: DropdownMenuSelectionMode);
|
|
99
|
+
/**
|
|
100
|
+
* Updates the selection configuration
|
|
101
|
+
*/
|
|
102
|
+
updateConfig(config: Partial<SelectionConfig>): void;
|
|
103
|
+
/**
|
|
104
|
+
* Handles item selection based on the current mode
|
|
105
|
+
*/
|
|
106
|
+
selectItem(item: ForgeAiDropdownMenuItemComponent): {
|
|
107
|
+
shouldClose: boolean;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Gets the currently selected items
|
|
111
|
+
*/
|
|
112
|
+
getSelectedItems(): ForgeAiDropdownMenuItemComponent[];
|
|
113
|
+
/**
|
|
114
|
+
* Gets text representation of the current selection
|
|
115
|
+
*/
|
|
116
|
+
getSelectedText(): string;
|
|
117
|
+
/**
|
|
118
|
+
* Checks if selection text should be displayed
|
|
119
|
+
*/
|
|
120
|
+
shouldShowSelectedText(): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Updates the selection state of all menu items
|
|
123
|
+
*/
|
|
124
|
+
private _updateItemSelectionState;
|
|
125
|
+
/**
|
|
126
|
+
* Handles action-only selection (no state change)
|
|
127
|
+
*/
|
|
128
|
+
private _handleActionSelection;
|
|
129
|
+
/**
|
|
130
|
+
* Handles single selection mode
|
|
131
|
+
*/
|
|
132
|
+
private _handleSingleSelection;
|
|
133
|
+
/**
|
|
134
|
+
* Handles multi-selection mode
|
|
135
|
+
*/
|
|
136
|
+
private _handleMultiSelection;
|
|
137
|
+
/**
|
|
138
|
+
* Gets the selected values as an array
|
|
139
|
+
*/
|
|
140
|
+
private _getSelectedValuesArray;
|
|
141
|
+
/**
|
|
142
|
+
* Resets the selection state
|
|
143
|
+
*/
|
|
144
|
+
reset(): void;
|
|
145
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
class SelectionManager {
|
|
2
|
+
constructor(_getMenuItems, _onSelectionChange, config) {
|
|
3
|
+
this._getMenuItems = _getMenuItems;
|
|
4
|
+
this._onSelectionChange = _onSelectionChange;
|
|
5
|
+
this._value = null;
|
|
6
|
+
this._config = { closeOnSingleSelect: true, showSelectionCount: true, ...config };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Gets the current selection value
|
|
10
|
+
*/
|
|
11
|
+
get value() {
|
|
12
|
+
return this._value;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sets the selection value and updates item states
|
|
16
|
+
*/
|
|
17
|
+
set value(newValue) {
|
|
18
|
+
this._value = newValue;
|
|
19
|
+
this._updateItemSelectionState();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Gets the current selection mode
|
|
23
|
+
*/
|
|
24
|
+
get selectionMode() {
|
|
25
|
+
return this._config.mode;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Updates the selection mode and reconfigures items
|
|
29
|
+
*/
|
|
30
|
+
set selectionMode(mode) {
|
|
31
|
+
this._config.mode = mode;
|
|
32
|
+
this._updateItemSelectionState();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Updates the selection configuration
|
|
36
|
+
*/
|
|
37
|
+
updateConfig(config) {
|
|
38
|
+
this._config = { ...this._config, ...config };
|
|
39
|
+
this._updateItemSelectionState();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Handles item selection based on the current mode
|
|
43
|
+
*/
|
|
44
|
+
selectItem(item) {
|
|
45
|
+
const itemValue = item.getAttribute("value") || "";
|
|
46
|
+
switch (this._config.mode) {
|
|
47
|
+
case "none":
|
|
48
|
+
return this._handleActionSelection(item, itemValue);
|
|
49
|
+
case "single":
|
|
50
|
+
return this._handleSingleSelection(item, itemValue);
|
|
51
|
+
case "multi":
|
|
52
|
+
return this._handleMultiSelection(item, itemValue);
|
|
53
|
+
default:
|
|
54
|
+
return { shouldClose: false };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Gets the currently selected items
|
|
59
|
+
*/
|
|
60
|
+
getSelectedItems() {
|
|
61
|
+
const items = this._getMenuItems();
|
|
62
|
+
const selectedValues = this._getSelectedValuesArray();
|
|
63
|
+
return items.filter((item) => {
|
|
64
|
+
const itemValue = item.getAttribute("value") || "";
|
|
65
|
+
return selectedValues.includes(itemValue);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Gets text representation of the current selection
|
|
70
|
+
*/
|
|
71
|
+
getSelectedText() {
|
|
72
|
+
if (this._config.mode === "none") {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
const selectedItems = this.getSelectedItems();
|
|
76
|
+
if (selectedItems.length === 0) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
if (this._config.mode === "single") {
|
|
80
|
+
return selectedItems[0].labelText || "";
|
|
81
|
+
}
|
|
82
|
+
if (this._config.mode === "multi" && this._config.showSelectionCount) {
|
|
83
|
+
return selectedItems.length === 1 ? "1 option selected" : `${selectedItems.length} options selected`;
|
|
84
|
+
}
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Checks if selection text should be displayed
|
|
89
|
+
*/
|
|
90
|
+
shouldShowSelectedText() {
|
|
91
|
+
return (this._config.mode === "single" || this._config.mode === "multi") && this.getSelectedText() !== "";
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Updates the selection state of all menu items
|
|
95
|
+
*/
|
|
96
|
+
_updateItemSelectionState() {
|
|
97
|
+
const items = this._getMenuItems();
|
|
98
|
+
const selectedValues = this._getSelectedValuesArray();
|
|
99
|
+
items.forEach((item) => {
|
|
100
|
+
item.selectionMode = this._config.mode;
|
|
101
|
+
const itemValue = item.getAttribute("value") || "";
|
|
102
|
+
item.selected = selectedValues.includes(itemValue);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Handles action-only selection (no state change)
|
|
107
|
+
*/
|
|
108
|
+
_handleActionSelection(item, itemValue) {
|
|
109
|
+
const eventDetail = {
|
|
110
|
+
selectionMode: "none",
|
|
111
|
+
value: itemValue,
|
|
112
|
+
selectedItem: item,
|
|
113
|
+
timestamp: Date.now()
|
|
114
|
+
};
|
|
115
|
+
this._onSelectionChange(eventDetail);
|
|
116
|
+
return { shouldClose: true };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Handles single selection mode
|
|
120
|
+
*/
|
|
121
|
+
_handleSingleSelection(item, itemValue) {
|
|
122
|
+
const previousValue = typeof this._value === "string" ? this._value : null;
|
|
123
|
+
this._value = itemValue;
|
|
124
|
+
this._updateItemSelectionState();
|
|
125
|
+
const eventDetail = {
|
|
126
|
+
selectionMode: "single",
|
|
127
|
+
value: this._value,
|
|
128
|
+
selectedItem: item,
|
|
129
|
+
previousValue,
|
|
130
|
+
timestamp: Date.now()
|
|
131
|
+
};
|
|
132
|
+
this._onSelectionChange(eventDetail);
|
|
133
|
+
return { shouldClose: !!this._config.closeOnSingleSelect };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Handles multi-selection mode
|
|
137
|
+
*/
|
|
138
|
+
_handleMultiSelection(item, itemValue) {
|
|
139
|
+
const currentValues = this._getSelectedValuesArray();
|
|
140
|
+
const itemIndex = currentValues.indexOf(itemValue);
|
|
141
|
+
let addedItems = [];
|
|
142
|
+
let removedItems = [];
|
|
143
|
+
if (itemIndex >= 0) {
|
|
144
|
+
currentValues.splice(itemIndex, 1);
|
|
145
|
+
removedItems = [item];
|
|
146
|
+
} else {
|
|
147
|
+
currentValues.push(itemValue);
|
|
148
|
+
addedItems = [item];
|
|
149
|
+
}
|
|
150
|
+
this._value = currentValues;
|
|
151
|
+
this._updateItemSelectionState();
|
|
152
|
+
const selectedItems = this.getSelectedItems();
|
|
153
|
+
const eventDetail = {
|
|
154
|
+
selectionMode: "multi",
|
|
155
|
+
value: currentValues,
|
|
156
|
+
selectedItem: selectedItems,
|
|
157
|
+
addedItems: addedItems.length > 0 ? addedItems : void 0,
|
|
158
|
+
removedItems: removedItems.length > 0 ? removedItems : void 0,
|
|
159
|
+
timestamp: Date.now()
|
|
160
|
+
};
|
|
161
|
+
this._onSelectionChange(eventDetail);
|
|
162
|
+
return { shouldClose: false };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Gets the selected values as an array
|
|
166
|
+
*/
|
|
167
|
+
_getSelectedValuesArray() {
|
|
168
|
+
if (Array.isArray(this._value)) {
|
|
169
|
+
return [...this._value];
|
|
170
|
+
}
|
|
171
|
+
return this._value ? [this._value] : [];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Resets the selection state
|
|
175
|
+
*/
|
|
176
|
+
reset() {
|
|
177
|
+
this._value = null;
|
|
178
|
+
this._updateItemSelectionState();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
SelectionManager
|
|
183
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { LitElement, TemplateResult } from 'lit';
|
|
2
|
+
declare global {
|
|
3
|
+
interface HTMLElementTagNameMap {
|
|
4
|
+
'forge-ai-embedded-chat': AiEmbeddedChatComponent;
|
|
5
|
+
}
|
|
6
|
+
interface HTMLElementEventMap {
|
|
7
|
+
'forge-ai-embedded-chat-expand': CustomEvent<void>;
|
|
8
|
+
'forge-ai-embedded-chat-collapse': CustomEvent<void>;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export declare const AiEmbeddedChatComponentTagName: keyof HTMLElementTagNameMap;
|
|
12
|
+
/**
|
|
13
|
+
* @tag forge-ai-embedded-chat
|
|
14
|
+
*
|
|
15
|
+
* @slot - Default slot for messages (ai-user-message, ai-response-message components)
|
|
16
|
+
* @slot suggestions - Slot for AI suggestions component
|
|
17
|
+
* @slot prompt - Slot for custom AI prompt component. If not provided, a default forge-ai-prompt will be used.
|
|
18
|
+
*
|
|
19
|
+
* @event {CustomEvent<void>} forge-ai-embedded-chat-expand - Fired when the chat is expanded to modal view
|
|
20
|
+
* @event {CustomEvent<void>} forge-ai-embedded-chat-collapse - Fired when the chat is collapsed from modal view
|
|
21
|
+
*
|
|
22
|
+
* @description A form factor component that embeds ai-chat-interface within ai-gradient-container
|
|
23
|
+
* for inline page usage with optional modal expansion functionality.
|
|
24
|
+
*/
|
|
25
|
+
export declare class AiEmbeddedChatComponent extends LitElement {
|
|
26
|
+
#private;
|
|
27
|
+
static styles: import('lit').CSSResult;
|
|
28
|
+
/**
|
|
29
|
+
* Controls whether the modal view is open when expanded.
|
|
30
|
+
*/
|
|
31
|
+
expanded: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Controls the gradient variant applied to the container.
|
|
34
|
+
*/
|
|
35
|
+
gradientVariant: 'low' | 'medium' | 'high';
|
|
36
|
+
private _isModalFullscreen;
|
|
37
|
+
updated(): void;
|
|
38
|
+
render(): TemplateResult;
|
|
39
|
+
/**
|
|
40
|
+
* Expands the chat to modal view.
|
|
41
|
+
*/
|
|
42
|
+
expand(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Collapses the chat from modal view back to embedded view.
|
|
45
|
+
*/
|
|
46
|
+
collapse(): void;
|
|
47
|
+
}
|